From e220b040a4b0bf2f5bfa733ba43e805b6cf2c1c3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 16 Apr 2026 23:36:18 -0500 Subject: [PATCH 01/11] Improve worker capture latency and AI playback flow --- .env.example | 7 + .gitignore | 3 + README.md | 16 +- .../CameraAccess.xcodeproj/project.pbxproj | 32 +- .../AppIcon.appiconset/Contents.json | 2 +- .../imagine_a_film_camera_in_the_style.jpeg | Bin 268775 -> 0 bytes .../AppIcon.appiconset/logo.png | Bin 0 -> 1007333 bytes .../CameraAccess/CameraAccessApp.swift | 6 +- .../CameraAccess/Gemini/GeminiConfig.swift | 53 +- .../Gemini/GeminiLiveService.swift | 104 +- .../Gemini/GeminiLiveSpotter.swift | 142 + .../Gemini/GeminiSessionViewModel.swift | 405 +- .../CameraAccess/Gemini/SopRelayClient.swift | 1637 +++++++ samples/CameraAccess/CameraAccess/Info.plist | 59 +- .../OpenClaw/OpenClawBridge.swift | 9 +- .../OpenClaw/ToolCallModels.swift | 38 +- .../OpenClaw/ToolCallRouter.swift | 2 +- .../CameraAccess/Secrets.swift.example | 26 +- .../Settings/SettingsManager.swift | 235 +- .../CameraAccess/Settings/SettingsView.swift | 98 +- .../ViewModels/StreamSessionViewModel.swift | 4176 ++++++++++++++++- .../CameraAccess/Views/CaptureView.swift | 468 ++ .../Views/Components/CustomButton.swift | 27 +- .../Views/Components/GeminiOverlayView.swift | 66 +- .../CameraAccess/Views/DesignSystem.swift | 105 + .../CameraAccess/Views/HistoryView.swift | 42 + .../CameraAccess/Views/HomeScreenView.swift | 94 +- .../CameraAccess/Views/HomeView.swift | 296 ++ .../CameraAccess/Views/MainAppView.swift | 10 +- .../Views/StreamSessionView.swift | 33 +- .../CameraAccess/Views/StreamView.swift | 185 +- .../WebRTC/CustomVideoCapturer.swift | 282 +- .../CameraAccess/WebRTC/SignalingClient.swift | 22 +- .../CameraAccess/WebRTC/WebRTCClient.swift | 120 +- .../CameraAccess/WebRTC/WebRTCConfig.swift | 81 +- .../WebRTC/WebRTCSessionViewModel.swift | 169 +- .../iPhone/IPhoneCameraManager.swift | 217 +- .../CameraAccessTests/CameraAccessTests.swift | 674 ++- samples/CameraAccess/server/index.js | 69 +- .../CameraAccessAndroid/app/build.gradle.kts | 33 +- .../cameraaccess/Secrets.kt.example | 18 +- .../cameraaccess/gemini/GeminiConfig.kt | 18 + .../cameraaccess/gemini/GeminiLiveService.kt | 15 + .../gemini/GeminiSessionViewModel.kt | 175 +- .../cameraaccess/gemini/SopRelayClient.kt | 144 + .../cameraaccess/openclaw/ToolCallModels.kt | 42 +- .../cameraaccess/openclaw/ToolCallRouter.kt | 2 +- .../cameraaccess/settings/SettingsManager.kt | 224 +- .../cameraaccess/ui/GeminiOverlayView.kt | 2 +- .../cameraaccess/ui/SettingsScreen.kt | 58 +- .../cameraaccess/webrtc/SignalingClient.kt | 16 +- test_connection.sh | 28 + 52 files changed, 10014 insertions(+), 771 deletions(-) create mode 100644 .env.example delete mode 100644 samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/imagine_a_film_camera_in_the_style.jpeg create mode 100644 samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/logo.png create mode 100644 samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift create mode 100644 samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift create mode 100644 samples/CameraAccess/CameraAccess/Views/CaptureView.swift create mode 100644 samples/CameraAccess/CameraAccess/Views/DesignSystem.swift create mode 100644 samples/CameraAccess/CameraAccess/Views/HistoryView.swift create mode 100644 samples/CameraAccess/CameraAccess/Views/HomeView.swift create mode 100644 samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/SopRelayClient.kt create mode 100755 test_connection.sh diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..e84a250a --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# VisionClaw external state machine relay +# Copy to .env and fill values before building Android sample. + +OPENCLAW_TAILSCALE_IP=100.64.30.99 +OPENCLAW_TAILNET_ID=YOUR_TAILNET_ID +OPENCLAW_BEARER_TOKEN= +GEMINI_API_KEY=YOUR_GEMINI_API_KEY diff --git a/.gitignore b/.gitignore index 530f7155..438b66ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .DS_Store +# Environment +.env + # Android samples/CameraAccessAndroid/app/src/main/java/**/Secrets.kt samples/CameraAccessAndroid/local.properties diff --git a/README.md b/README.md index 1e66a649..d3ea0bd2 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,20 @@ git clone https://github.com/sseanliu/VisionClaw.git Open `samples/CameraAccessAndroid/` in Android Studio. +### 1.5 Configure environment variables (.env) + +At repository root, copy the example and set your values: + +```bash +cp .env.example .env +``` + +Set: +- `GEMINI_API_KEY` +- `OPENCLAW_TAILSCALE_IP` + +These are injected into Android `BuildConfig` at build time and used as defaults by the app settings layer. + ### 2. Configure GitHub Packages (DAT SDK) The Meta DAT Android SDK is distributed via GitHub Packages. You need a GitHub Personal Access Token with `read:packages` scope. @@ -285,7 +299,7 @@ All source code is in `samples/CameraAccessAndroid/app/src/main/java/.../cameraa ### Tool Calling -Gemini Live supports function calling. Both apps declare a single `execute` tool that routes everything through OpenClaw: +Gemini Live supports function calling. Both apps declare `execute` for OpenClaw actions. Android also declares `log_sop_step` to forward SOP step logs to an external state machine endpoint. 1. User says "Add eggs to my shopping list" 2. Gemini speaks "Sure, adding that now" (verbal acknowledgment before tool call) diff --git a/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj b/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj index 1e7dbda4..3d465458 100644 --- a/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj +++ b/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj @@ -38,6 +38,10 @@ 9DD6CB0E2F3C64F400ED7098 /* WebRTCOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD6CB0D2F3C64F400ED7098 /* WebRTCOverlayView.swift */; }; 9DD894B22F4047630090B9B9 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD894AF2F4047630090B9B9 /* SettingsManager.swift */; }; 9DD894B32F4047630090B9B9 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD894B02F4047630090B9B9 /* SettingsView.swift */; }; + B10000012F50000100AA0001 /* DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000100AA0002 /* DesignSystem.swift */; }; + B10000012F50000200AA0001 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000200AA0002 /* HomeView.swift */; }; + B10000012F50000300AA0001 /* CaptureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000300AA0002 /* CaptureView.swift */; }; + B10000012F50000400AA0001 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000400AA0002 /* HistoryView.swift */; }; 9DD895962F405E0E0090B9B9 /* RTCVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD895952F405E0E0090B9B9 /* RTCVideoView.swift */; }; 9DD895972F405E0E0090B9B9 /* PiPVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD895942F405E0E0090B9B9 /* PiPVideoView.swift */; }; A1B2C3D42F0A000200000001 /* GeminiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000001 /* GeminiConfig.swift */; }; @@ -45,6 +49,8 @@ A1B2C3D42F0A000200000003 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000003 /* AudioManager.swift */; }; A1B2C3D42F0A000200000004 /* GeminiSessionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000004 /* GeminiSessionViewModel.swift */; }; A1B2C3D42F0A000200000005 /* GeminiOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000005 /* GeminiOverlayView.swift */; }; + A1B2C3D42F0A000200000006 /* SopRelayClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000006 /* SopRelayClient.swift */; }; + A1B2C3D42F0A000200000007 /* GeminiLiveSpotter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42F0A000100000007 /* GeminiLiveSpotter.swift */; }; E66D30242E7DA71900470B48 /* MockDeviceKitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D30232E7DA71900470B48 /* MockDeviceKitButton.swift */; }; E6A188482EB918740097D0E1 /* StreamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A188472EB918740097D0E1 /* StreamView.swift */; }; E6DA451D2E79A63100E3F688 /* MockDeviceCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6DA45182E79A63100E3F688 /* MockDeviceCardView.swift */; }; @@ -108,6 +114,10 @@ 9DD6CB0D2F3C64F400ED7098 /* WebRTCOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCOverlayView.swift; sourceTree = ""; }; 9DD894AF2F4047630090B9B9 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; }; 9DD894B02F4047630090B9B9 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + B10000012F50000100AA0002 /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = ""; }; + B10000012F50000200AA0002 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + B10000012F50000300AA0002 /* CaptureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureView.swift; sourceTree = ""; }; + B10000012F50000400AA0002 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; 9DD895942F405E0E0090B9B9 /* PiPVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPVideoView.swift; sourceTree = ""; }; 9DD895952F405E0E0090B9B9 /* RTCVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCVideoView.swift; sourceTree = ""; }; A1B2C3D42F0A000100000001 /* GeminiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiConfig.swift; sourceTree = ""; }; @@ -115,6 +125,8 @@ A1B2C3D42F0A000100000003 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; A1B2C3D42F0A000100000004 /* GeminiSessionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiSessionViewModel.swift; sourceTree = ""; }; A1B2C3D42F0A000100000005 /* GeminiOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiOverlayView.swift; sourceTree = ""; }; + A1B2C3D42F0A000100000006 /* SopRelayClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SopRelayClient.swift; sourceTree = ""; }; + A1B2C3D42F0A000100000007 /* GeminiLiveSpotter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiLiveSpotter.swift; sourceTree = ""; }; E66D30232E7DA71900470B48 /* MockDeviceKitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDeviceKitButton.swift; sourceTree = ""; }; E699CC952E8150670052C240 /* CameraAccessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CameraAccessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E6A188472EB918740097D0E1 /* StreamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamView.swift; sourceTree = ""; }; @@ -188,6 +200,10 @@ children = ( 8FFD5FF42E8422580035E446 /* Components */, E6DA451B2E79A63100E3F688 /* MockDeviceKit */, + B10000012F50000100AA0002 /* DesignSystem.swift */, + B10000012F50000200AA0002 /* HomeView.swift */, + B10000012F50000300AA0002 /* CaptureView.swift */, + B10000012F50000400AA0002 /* HistoryView.swift */, 8FFD605E2E84A2F70035E446 /* DebugMenuView.swift */, 8FD96B722E6F0A9800F56AB1 /* HomeScreenView.swift */, 8FFD605F2E84A2F70035E446 /* MainAppView.swift */, @@ -272,7 +288,9 @@ A1B2C3D42F0A000100000003 /* AudioManager.swift */, A1B2C3D42F0A000100000001 /* GeminiConfig.swift */, A1B2C3D42F0A000100000002 /* GeminiLiveService.swift */, + A1B2C3D42F0A000100000007 /* GeminiLiveSpotter.swift */, A1B2C3D42F0A000100000004 /* GeminiSessionViewModel.swift */, + A1B2C3D42F0A000100000006 /* SopRelayClient.swift */, ); path = Gemini; sourceTree = ""; @@ -398,6 +416,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B10000012F50000100AA0001 /* DesignSystem.swift in Sources */, + B10000012F50000200AA0001 /* HomeView.swift in Sources */, + B10000012F50000300AA0001 /* CaptureView.swift in Sources */, + B10000012F50000400AA0001 /* HistoryView.swift in Sources */, 8FD96B7F2E6F0A9800F56AB1 /* CameraAccessApp.swift in Sources */, 8FD96B812E6F0A9800F56AB1 /* HomeScreenView.swift in Sources */, 8F2D23802E856711002D0588 /* DebugMenuViewModel.swift in Sources */, @@ -432,8 +454,10 @@ E6FD3BCE2EB4D53A00E7FE5D /* NonStreamView.swift in Sources */, A1B2C3D42F0A000200000001 /* GeminiConfig.swift in Sources */, A1B2C3D42F0A000200000002 /* GeminiLiveService.swift in Sources */, + A1B2C3D42F0A000200000007 /* GeminiLiveSpotter.swift in Sources */, A1B2C3D42F0A000200000003 /* AudioManager.swift in Sources */, A1B2C3D42F0A000200000004 /* GeminiSessionViewModel.swift in Sources */, + A1B2C3D42F0A000200000006 /* SopRelayClient.swift in Sources */, 9DD894B22F4047630090B9B9 /* SettingsManager.swift in Sources */, 9DD894B32F4047630090B9B9 /* SettingsView.swift in Sources */, A1B2C3D42F0A000200000005 /* GeminiOverlayView.swift in Sources */, @@ -469,7 +493,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = WY253UX7FC; + DEVELOPMENT_TEAM = CVABQ59BK3; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ""; INFOPLIST_FILE = CameraAccess/Info.plist; @@ -479,7 +503,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.xiaoanliu.VisionClaw; + PRODUCT_BUNDLE_IDENTIFIER = com.lucascunha.VisionClaw.SOP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -498,7 +522,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = WY253UX7FC; + DEVELOPMENT_TEAM = CVABQ59BK3; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = CameraAccess/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 17.0; @@ -507,7 +531,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.xiaoanliu.VisionClaw; + PRODUCT_BUNDLE_IDENTIFIER = com.lucascunha.VisionClaw.SOP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; diff --git a/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/Contents.json index f7757d41..f4344003 100644 --- a/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "imagine_a_film_camera_in_the_style.jpeg", + "filename" : "logo.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/imagine_a_film_camera_in_the_style.jpeg b/samples/CameraAccess/CameraAccess/Assets.xcassets/AppIcon.appiconset/imagine_a_film_camera_in_the_style.jpeg deleted file mode 100644 index b91090c87ad741739b43602669f0b99a280d2356..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 268775 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%0DY4K~(GGe4VdFx~}2=We|+n`==N zqs=>?d!s$Kj>EsZakUNrT`WEXCiu8P_)jxZHkR77shs7T6no$N)Kw54ASda`whTV; z;?^!KhPQVXbbp2)fJRGucbw!Lw7?hlf4<+DNlY9v9AEo>p6z@{soyGp4G|f&T*t)v z0<99A!eyeNk@nM2YWhxKZ>Ylc%`8@qi7%IIpq+@hiSCD9x~o(-oS{M;bZ@6YZf{Gs zHkMT=P5QKWehIol`s+u#ekGBl@bw>i+-8j8`aMYZcQ0VIQ;L1LT6GS7A7&S%Z$xW% zg!!G^$mjaWeGf-9t%i>`fVf)(Ps2q?Lv$6l^JCuHYR@fE81H&6Q867&u6KZ)^2a)| zb_%2m%PV9gj*-jaDl6;>ed}{2*FRg-C?#mb#gZ;PiuK7=jf1mi&Ok_+#opiB2_TE6 z`e3Kz`dHHiP{*AY<&P|FJnk12b2K=Rj=E7Py3lUlP|GIj^V;y~5`)Z$N=p{yr`G?m zWY!cgHgeyeyTqP|LtHYHzTqdv6xgwn7FoM+1@mF}Zl+d)lKcI>LiQJ27Jdqpyh`{K z-Xrzzd^Sv-;Beg59tYFSm}=Yo$#jk6=a%OByi4Oofj?89o%Z*{_baz9c!D^9 zPNyLMuS@U^n%1<8#Mc=9iI-ns#TJ8U9b3>--bitn50QI5v_o8RoOzx)Jz^8r*;I_B z_;!qEI=px0UXC?O7df`=6`qORd=<&i2HIzhyxWv(&?MjtrcE~KO2-ubXpQe(dcq>` zUbi-$LT|ro0f^~ofPx*3c?W(DIzf#dA@DhiP3^VlRpDjO>*SPag8rbD^pg9{*~qEO ze8Zxcdq>}e97+!Lx31sgyyI<4omkz+^Ev}S&EP)O`Y3{PuK^Xg1_%IsE-2g^Djn7u zXkdm2`WqaJXONL^q!^X@{y+|-Y1z;hs^u~4to25UF#5_GI>A+|AV_fN$H5c@()%Ba zAalA+hb$PI?nEky`m(K91*Qa(u=!6LT)YCB)r2MoTeVdJwKg=#rExGpAlxyxHzy;t zn>*g@Tw;6e=grJ_-c=_Z&+xDi`h|C#rvggP{cLU|8n>-=Tsn1(m-7jM{G23U@4AoXpyd9<|@*&_nCSe&0ic z^CqS)`h|F0eyw}{g!2~bZZc`cW=j7IxO>qbGW8)+lKE?cX7$+1CBU)sqYA~n&Gk4F1RM&0Tigc`D3FI;kZS5wTH zBmA<{&6o2+PqHjRduL%~`t|IMuIo_-0j~|ir|H@>S=e<(u_mqRW(zp7cy2RC&)#il zI?u1?C(}QZ^|Rfg)|)r!&W*&XQxpT>(u0gFt<4XU zSHE6?U5I}p6A_TJuFngU6v?z3-YAj>VIyDxKkvnJN!)*2)l=0x=Yp7b^n zN(M;qZ_CuoUS*5>b>Y=$i`~S#s!t9$8>XL0_9fSUOApv!O_*SdJG!2>;U=Y_S<|XcUf94Zf+EG*6&FZ6DRd9v4LzAJy6bp1d-FPi%#o;%2 zkE!r4*g-Mp*yphgbiVY?bHvYNpeM4JG&5PQhEDQ4$g^$qM$Ni(Y?-FK@O~7{%9ne` zm+ex9O^o?{?%igjg`@?8Wr~{{(brf?0n~{Yz|q6UCOG9tYDi$bv0)vn)&|3?46vB#HU)WS0~_;k+X z2i@nnCOEz~uf1C*H7tCj)$WbzsR-O=zXy6Kx~_BdElE=?quNyDs3Knbk~`!xqEn9O zp<^gN30_g3M=|@Ya0Ic)2gV7((1GRuSdu5oGY)m7gyyZr1PqPu9lVtQdzu!2!l?GV z|FNWx+bq*1bLh!`!b+`$DZqfOI#uA5^;L=7R8O-vw%1yZ}zkYL{*FwNahNz5I; zvscGU0*-jb-X=Nyj?_B)K&OQ8LgWXCdb{OTtG-qFXT%KfEh68LU4}A?F=U%^^-X0g zb;!$n@lu9wB=_xAe0tQDa|rMkEdGgV`VY(lrWNC*F)eqw>B%`Y^#A=|0p-B_=*P%= zE{EdyP{apaJz^xmN$Nv?(K~LvBC!Ypj(cxw=_u^i-evGtRaoQo^44M>$^% zzWk=-CO!FO>I#yl-r_RZ`I}mDyO}fGs)I3efRbS3x5%SFQQ{YTxOunLy7A^Uw%sAK z4p$^Kme zy4+o*vsdcc#?cJ-dUw@2zFj;-SePw^*jO`*e995)5S=es zTgvwew~M6d15>8s9>Rx~tlj;Sy8{{(`rV>RE9fkmC@ub6OND!2nsVy-gvT3+B*ZCp zIvUu$H-Qh>1gio=yb6>kwIl6=lm@CEY5w8U<{$5}U8XfTAZ!~y9M3jjo+_R^sB9mM zYS03gAxJjM$!%i5=K)f+Lrn)0A2r!VQre_!2f0w?@^jxJ^T>?iZ)QqQc!I(e zcOg2L>vRa`P-`2VP;TFsc`74aIWU%6Sn2Aj?&PEf2rYq?V=MyF^Qd%0+tPSfEm>cT zU+8mhOvyV{ed-Af-CuKJ0p}k^rRo5_NMGyrTi#Gye5DfIKbGA0fYn9*f=7JAjY8@) zlTy7gsvJN4=iF}x9~>2Vcn2D;NuhqAu9IbI?d?sDKJ+)(JpJ&b{aStvK4pOWOsk@O z@}P4Z^5&nx?EE|57ERe(%&zced@w!^IS$Cr23&;1MG5(0ttFb7JSJkpyt3lU1+a7TIIRY3bZYUbDhc(1%;<^w~b%Sn25}v<<`Ss+QcL#Yc;kZk# z`@1At1@5)lDYon*&Rs*+^+kA0& z&qgZjRd6b1NBaPQ4N^pCRfZSUeV0|%_Ye4wMfu!5v+uCVfTK%0V#{Z+@l0zatOYe1 z$r^#E_th3}{P8dl>3bx2!3j*k3UG$sVfo*pVH_sZpSxJ5F+LlPd?G2Gj zIM$lT?ly2acd3hu^KToid)2Z8IyKrF70h89hOH|j+}$ptFaNgYgO__1&UI zC9GRx{JV>CgBREaCyv=%+pP zO=9#>4zu{*D+g=tY9kzqI3=Ybp|!gY2X>;*v7&>}4+7CvBNgUBUNy{CYz-j#AX`zq zs5pOMN$)a#ptUA7i=&Y3Z5mBLYiGJ`_dI+g2(#{FIc*T_`$c+(Hv3w1HI;G-`sV&r z{qfI zYzR)YY;3FrZ~p5^k3y^X=00hBE1#SZk9T{acm#M5urR9V2(tg}8Nk;a|6oZOOg!rB zjm34Vb(iR%&q<%Q_ojH#bM>ZfS)GYCLGie5wnzAIiaMXIS-;oEGzPDrkfaB>sZ_a8 z+>l^5f>Y)9A$M%P?6l$XivLAB-d|Ly?j*u=ciO<#k#hP)ASFCC1FXWr_`t|yDu)>} zIZ|no3$QcPzlvdUly_8cs6UtzgX|A?tDHZntXaXbfUcSz7Y;Y&tM_=ZLu;m40GP2c z&56!I2JP;V>mHafSi2kieUv=(-lrXyin_^5Y6;r`z8wFtfH_5I6o46y_JhxBgvtJ0 zvPcA<8ObeOl*jA)uT;FlXx9Cn&!)$xbkQ|rPdv5CSGq^>IdYM|R^0$@1T+^%d$~kZ z3R9RY*bc&)HXeLYcwzO`dRC=_qH5uY!b*qgeIaM zg_NSS5J#2)fzAk-Pd*UGeRhMiuoZGQMJu&ipYL;UM#$^rZ}Zauvnq3ES&}DHS2-rB zZp3(eF`y2|$^tUG==Pg1)pkd#TO|2Unp3{UfBJ4jF;SDE^?F<)yoyqdc(V_|@r!eA zS#nC3j|4SO(wW;bOJwe`7aNP^tj986`c^7*?Xhx(_Qc|i6KT7FzKGM`$(HQx+tEDe^cPn0BCLg|0|dQ@q(Z4V&S_+lsS>P z%@E66Pz3z)-06F1{I@dU_5412Oh#*cCygVob_>(X;55rjt(lT1@>aFrrK~r!R}^)s zIc1%}wneptnUI`Nu0%3x8xc%)Dx~LNs}j||2M7O%IX6>qMdY>Dd%=70Y4@Q=X1D*B z9pS3T2{A64rwCX~)<5b(ctCGp3-|FY^9w>^BW>_Ez~eOxewNa?|FfMHIMFo5$b|wY zbqXt(oTYo2H!yGbsvg#RT`uPbxJl(I9&Y>>a~ddzxtST`2 zmJ^}ESwSf$TT%*(V31M&w3cZG_hzxlq?G6H=A>+%@?I5<9cZNQ?p>rb+BtJ|rBbV2$PMkuhGpcWaX&#Tn=28gG>dJtIor zuF~wy?CV>+xWQfyyHAM*h0Dp;NDVhe5h^S54M+L~=FsUTFGlXzUYjBJc>P;s15)Ly zWP2#3_`>?{suJw7g*QQb^)Is+ z1`CoNg|(40XBQez%3muVjLh2Jx_SSs7QokN#CeM|{uk@ZX|S}|P4T0%c46vR>V}K3 z?siuC+1?2Ex!ik>DGI^q8qN`vOFnhn=AZ^m!bOs=#pvRa+dI}-Z}_;tocdPml@yRv zT&#WXHPnh%3CBRUKXW160ltW0!FYP|b_p93pd?UlKyyy%68j)DQKidrJabIekZNe@ zdgk6XpQCkwRQpnYkIzeY;_5$S2_?>oy$b;YEFTyZN9#;iB z@Y14et`D4GB%1J}@k!|-;M^+`P4)HmI-D71K`+vJ(DYn_ab z;E~qoE%s7QVnW9uo>@4L0D~5Bh7?U~qM_R7sYq;?XnCKND3!K_ZD_$gn?DTMi zD~7!jC0_qm)^J+FHG>M61#0`RZ}4B14*eo6aoGheh`u!nAUAk}$~&jK$RLWNLK+#Q zaAuNbAvo$;=sP*wWYmUo-mgzP6Ae-t;k9GGVL8_jLNT2b)2ZZMx58WW^?)zY?oa?? z3zqB1GONc)VqCuU9=g!MgtKMDYf#r|xn97lvBfLf|$%TBE2#K#RkBmJ` zRFUjb}U?|u*f$}WrJAzcragh@H*ug^Yr_tzor#vinvo% z9*rsPIajleYWqtpG?BVCy{}I-NbXnot?T)m)A$#{nOFT)riu3mCC8VHa}p=rJpz>8 zc^Gb8&yrW`j~l1A*NGDuQz2&-K%-bQpEaXqKRec;FPGf>3aWj>i$%Hpf$c-loLSy1 zMF*$dWTI9XG8y4g@b*{Sv0jucjvKqcY0wE0XcvH)Psxzf7tpLR@>9629_=B!j9RsX zeDJe|6tmE4fGlZ5S$wVY6W<9yT4b@Q6)xT`E!(vAuY1`1fPp`)d~mw4*s@^xT8?W- zS@z>OTVT)~TG4~cHdxj>OsO`&>FJ%$`H!hdMgrSzK}p6XvYG?uRW(nk?Y0jjkI*Fa z#f`t${G8Jp=c$oM-7ACj1>6lmBbfN5wKhMLx0^NJ?Hpe-8GuOOtY%s9(Q#l`1Xi^NToc%V z7Nj5yh=i(YY)(ARN|5pvfcECB8@x90bsGx z>LjziqX&N-Ov*+oVA>S;iD!Io4_3#=x2*qdZWiX#?RkNZ`VWqQt}07!Dt4n*5}Yf%iqH`Yz1)}IKQXQ z@e5YE(?v96fOKp}21rohmHH7>qbKhAvP4w8TET?;uD@1(sJp+!XPzZF!wR0czDuu| zpCv?0O!EPs@;L=WOkGC(999LUR-EmELHJH5$x;7!6I3_j@&w^f>7R*mmmIQW;b}`j zMJ50J8_D56VfAZ(dd52rb+(?CO6i* z(f@e;4A0Hq`e}!Y@r={|Wbt73MDA|=E8*Acd)LHXd3-|To8M-a=zqs{8UtzPnor%-r&fCxP;XwKdF}^mdF>wGaEZK`mCy-oL(?si6G* zzCq>zn47{*POc#pc4vAc4+f_k*q-#}=jN+QCiLyzR?{&a+;CFC)MC0~+vV-;gLG`C zF58aFT%Or_@aaD$2SH{!luETj{{u7YIu^t3OkM#-B;DK>9_wnPqe6lddZ+ePeq>7I z+*%?%W#_9ZhHIE~NWNJ(i@-iG9j&rwfZ1Q2qhm^i9!mX9{`IkHRd%h6$SQ~{p+NdT z{0E+ z3kJp`;%$KgK^^*+f~uaUu(uAnRdijUuXfA_^ou^jZ#h0j2c+1F^DAgJ>v+fR!8;bN zH;?bs18qYeQ~&rEE{Nj(7ptB6TrXl($C>%w6TO#Ywmord^|LTp|H3$+rh&Jy2uSPxrCVG|B@nD>3xASN%L}&tsb3qtW^=H2oW!b z5vG%KefqpDiHnZyU7K(*)T-;JYsX{!*1xtg2NLLWwdX0(QZEDSLECM4*@3;90Ak4CFo~5x?3zsphG&vZ-8c*o-ny6M8MdF_4?ru{)l5063}H^o18_szmvt z4>U-Hf#g)RiLiap`?<@#Z_QuY(ph|QmzSk{zYqVZvoaZj2~kZq7Qho@i+!1{B$UZk zXx@zmTmLigwTGZUY*kSBBDrF`XU;q zcy}u5Kr#9y!1E6XRhuSU!+bwN zsv{|u2Xs@}O4C`J4nL-sgTunX2CthuM5>tbKD;Oj-naIxmrt@>?8ej;pe`L`Wg*Z1 zgND*GR%$icbi%U5HIDz@&6!V4xW0va1?XiKH0V{8fJ7mRD8}Ad4Xp_Zb)w4k*)=!S z2c=c&9>4H8t>m)@dv|dcf_OR$ALY-HY0X^hEA=mM^3V?1t|uNy3A_BJ&pu;u4!n>uN9 z-hpqjiNyAZze4WYyqA0T7(%olipO%+ZgjI%7l?&|bPR=X%o~mr{p*|_{`l#bG43Z}Y@JlL zE+n)R*G8=ELDWe%GuRNE6qOOP=EVA}7|@f6Y}#a6$H%s|A2%vgJEJ6$WVM!?L>|a5 zkBy3YC+V7z@*hz=@Qx}7&N&a;Kn7~Qmi(jhtnI4YJm3ealMctWCDFf54^?&OAID{(fD#K<1 zm#g3S9Q*WmoGHkheY~1Kcz(3eFGM=ye_O?|L(JKK;M#!b(DJ8u%k}i9qd+}W?T$qdH{^RlxQd7bOfNeDRT-!7zIRkXJ%Q**C`MBtk(9u z6mvY{5zK2ne1U`*I!2%0!Ed50e1z$ouL;_vwNk=)s=ak5*=%8fof9DeAIV7l_hw_3Q` za+?h!nGyo@YY)Jmjb+HiaT<5GE2f|It(ezCN2^i%W)>cVgsd6Dv(sDDL%XP!4-<1uA1siuw{& zUSl5!;nXA7i*%Hh%?F_#1y!c3zD$hePiH)|SicTd81iwt=}+?Q)EA#r6JsYsh!30N zHx}eF*B0hX#?NNC20Nd;D07b6TQW?tHE&_+TNS%5_uxaYjdE70%|K4yUkve{8`W+b zTwt{l^W`w?*(YYEmnRR9U4`&31U;V~xGAFJ+7gg#27WO@xaOZ+#7a6_jXTq560p+} z@VqI8p#tRd6^sRW_pQL%Z=}J~fyd{>^u(%)*9Qf-3F@w~fV%j4e#y-q^9V`1bPy-( zj_e20i~pD?=R6sgWInV@nBPGO%yUzH8vp~>;fu^Lv*yb-HCgB$w z?0G3-F&@lNx344QDV61=ue(*`0@w)sk7oZdeG`u8j7Oc{_<$HG0G-I$p`dVX5-d}1 z3W#uh_4$Iy$1AlWQRDwHnU~9UEU=Ma3m3+UAplx6s`gz>cfbdkojzuzb-p&MNMY-^ zH`505aY~}N{EJIuv<8GFs6#tw9JqEETLkS@o<1iDMd!*y(@%irKVJR1bW-qK=3nmn zx)*LRgKk!x2D1jWxQ`^NBdCXNz{$T7YURTfJ7S1mw!q;}kgJ?`wYEyX<}{cYj1nW- z?j}3=3%rNjV%u+Ztk7W7&3aDDMH}7kxaDj$l@c57(AW{$tX+ zu@#0bR-6Z~*Yj@Q<=jU6Hwvge3ctNkz6)PD0<)A~yftD9U(DbW#GzUzemf?)irtc_ zG_x+}?I@H@C78di-%&Y7btt-EMx6Tj2Xm5j2B4U z6LJp5@ja{aL}%!mp?6WN!l6TtBtieFli{hFG}n)I!zfXPw8tU~z~e2)t>vd9yRfdq z|86G^k&H|)|GxS1CtoVCF*w%>BqwBIh<6Y`$D?N9%bPzzz->@oGF5ft`DvsS(LMO& z>(Y*6$Iz>@cfMRWRp;CeiBYytco=rX_RKF0k^;^d!HSTR0gnGJ^*QVoy{6TiKz6h} zn5Dec{I%y-?o7hoAZmmst~K1cQuzGO`J}lnBE7*1eB|+J=m>q`clV=#UJ>MBR3XK6 zIZIxk>%Il_OmCMF>(Py~8}&xoHl-k^{?~=yTZU4ao+w;>Jjc-YO0wIO zu*^y8_|W;ILn8pUwf}q2w+DSNxAgTmg31f+I4L_awX(wJD#|p2kL4>@zG4E zr8|vsQj~M&_kOF=cZuk43^s}p0?XWIL3;4iF-Lq%5*jA`x9_A6(xxQ-jg<4Z{3B(S zTsP{+9WH+Aly6C}-B_cr5Kqtr0yVZX{QqGLw@ITC?T9TqA%ng&$S}wQQoGb>DI2r{UvF8 zYhRi2sWtdj>%-lrQ|}A2?W`ZKc-EjtBm&NB1IekiMc*1#yKFr6`-Sbr-;0t7<(n&g z9zq*`eueR;b_>&|@@&U+CGROE7vIt&O-yDZ9E7`<$3a#hWKh&1w|r%5 zw(-Nn7tI-5?|W_3K;3u!m)`8Hepf=YW-t8OLcBoDWZpDHnHocIEVy7?zB3Pz`Z%1^HcZd zj6I8=cuYm`&c8vcdVNW`7V%3uxk zKm;}x1OUEZrNI4hnqv9v5bEFeqAf(dqr}Sv4_XtpQ-)0*&K}3aXte$vusOW?tTrg(p0dC;=@P?omx-zDER%Qy za~!T+l6HgQOA75c!f>}=`^uk>qyS(G7hMTet2jSpu zW^xc^ouos4K^XKDKqJe9-79v@sT~4I94d+n{^MRx*Ld>jPkG>5f_IgZjB#L^ z2r%Se5tza(Wg{9nP^a3}Tc^a&Q{IOOMEg@^hqvgc(z_B4*!j=Mi>+@_wN^imh0Mfi z9vbmg?reKTckavn0yL7lkstvY2yhc3qTAJA8WT4EG2JG~Cuk=o{6nmM`XzSz!1XuV zRx%_3L3D)7Kp)U60gT9&6%xdp8^n-@U3HsMGr7`B-3lTeX>h?EM+YvcRBa2~;vRTn z^{1V$8cF7%*QRlbyaZFu{Nnj`T>YNFbP>LLF5A-RP!C=pOKlZr&)}1%ha%-2r|>e zPCvziWU7M_plK(+OO&f(MiMNvGE1crGvvwDlIR`W(nyZdZDv-3MJAg%H64A^CycL2_EnTRGJ zsCpf8kc$k^ynWE1K_l|IFIPE%N~@woKL;Sh~B9; z;Gh<~P98NDq(P5?%|rMO<2M<@0Wc^6$w$P*m@JEI)~Hrbuaba47r!~rs=3A!RoDkS z<>&>AI(37p3Lk?D(#rsA`gvFwQ7*?79sBx9)9RazE7ohW*Z&##L_9)xfIiqO5`>80 z9uN!6tJJqRD8e#lz%Vr-xb)pm?wtm<6T8X#aTFUZ_lI>&R!NAZbOfjp4u%ig7jMs! zoLquKmp(BDK91~%)!u7Lpr_n9Cv|ko+{=noitYnZWZd4<45+&BSp*Mw>2vdR4En%= z)fCe-O;c!KJccGJ1Pu9<|B!@l7u^%dy!&Z-7(%o_A)OTH85nm9kO}48SmE#YWlw{c zdpPbVlH?eU^>f_I5T)yPj5NS}CqlqopZn*~YL<%c>%dU10n8R>2cIRn7pK5uvjA-E zTj;<9>~lZh1hL5rIvQ# zhR=?7Pnn}!+hb+kk2cC0ZVGXQJS!EGT|>@^{A^rMqi|LXf2+>2C!Q2F7M5rYFXX^h zka{2DghW7{_sUkATQ7D+zEbCo#Vmbs(j2E*0Qn|IWK+uE+ffrD`XR~~_2#NhqBv3D zyJu^5xo8wK5@%%qNm@L=3wB3G<;an&wgZW~W`A-UDNo9*o)^DjTxt68C1vdO@H65p z%jsK}UkIt7Z$J+uj7ddlL`}x|n}Gg*Bzgxn!cGew6S>+^H) zD+lRejZ$F+@{WJ$M8=Jo)z=v3kr)(@R6`pO&@Kgax*CV~&0;@i-`nTQ{pE}XmGu+b zs$;4gZ4>IPgUP zYDK#m4|#!)Co<8jWLuP|jrb#b4Na~%t=GS_#B}V;+xh6Rh!;>n36e(bGF@>cjCfol zHS6DNF!ZD#_2$ohPJ-dfEd{9i^P5IrC&uLG;j_RMOdLi=>tBHBOdKlfeM!Mmt$QO} z_C(Km-{jclu6wlK*t^Th7zYV5L^qq=`bJ`G#m^HT5o*QmbaiF=&X+rA^`*)WbsDER z{t&k}dJKQG048)S5gakq`WzHyTT)_O%4f;8HueG!pEQu-ZZUl&LiFXe8K1FM-)@MQ zKA6&|FG?JVcP28Xj{Oi(&GrimWhKXXnGBks&p0&L=NZycRsq^kF5a3Un}9@PmKGM1 zj+)Wi^?1by;dSh46D1IL?rMF+fRE~vR~&9JpHWchsRfM-rxU_hnSs7J3k=*&q&Ief zup^zJ`4cd)%J`T2TpGv2Vm;+5NiA-XcA}!nOw+)0jcD%8Pia{K(JoQDotR0SMWw}{ zIQ#1@ahw60H|zN`Cyr~>=Q_<_ny`Mob?4SyCOuLA^XIXtfH~euasY=*`Zyfl*47r|1=f0V9K@W55`Hlw@Jc- zW<)ajj6i^`zHlqbe7s5?RgkJ?nyQS2FMk=05#{hgk0EEb7;KGHSsE+J5fu-!E3$_Q zAl#&hP>s>e3t5RMwd9H~01stXD!{8d5IumJiD$4!o0dw9k2G9gXOZp>1cgz#-P4n z!rcNo+LGk;#rwjobmHd6&o3hH@Z=h%Z~x@*q=2? z(_~2sb>A%|%@Wydcyc(Za}z(}tNmETCyz!VXy+;^Bjmj{%98?0R<}K0agyKXorT3t zl^AJE%5+qmUGJgMQ_jtY=o0jAG?stL8<;-m8o0TnFhohK!<|0wc-FQD1m-<6MD6}* z_Q?)AO#}Q`F#6w7W=@hYpNYMHT|mdNSdh{28d zrAeGveo!U=8Ooesd_X+g<<$*ILP6ia9eVC6qi{M#Of*>{D;Yr%aLos#Os4I+0DA$n zOf;MatAG;#R5`a`z|#ZsVqNKeC;O+A7pfAR_reXy=7egB)Gtb zov#g7-aB!|+<;%(1l6X;11rW5rLll9CaymL1b-Vl#!2{=dW7Wv>vi}gPoVvfZiy++ ztL{I341zgnsCN?jbsCF)v22HULS8ibL{l=A5H|m$*47uR=MUEkrsp z)v7m@j9+bij>?xXs>;u`KA#TIL4@h4826F$6e!t*I2BO|^Y9cgSMF?V=O9{r!0s^~ z0J0aa7jZXNL~0_g9=jE>m7^N^B7j@@qdfI8&3sDt94QzOoQU>3_mzpRo1{dSY++ut z+@*J!K-GpXRXI@3SEC~l&X9NvCmaMH5x^?OwtC=OGTQcBfD}YT$I-imnWGp*)|Iy6YrOiR(8UmPT4J2h0WdN2$0jG-q@raWvlKz-4N2xF;hE7uFcdBm-AIm z!1OGPN1`I*f@>SeDFPQ{BX4JE?L(H#VRxPXVsmwnhEEb`gO_%ZqRBQS;{@0>@2a{{ z_E+ALHJfBKP}RsuKG+-KIgkvs+n=Or19!wVA|pZ;%HV1Zn%XxH2!*tLdePDK#-#5* zrZS8->^gwow*Q}d;zCAGyEus}J^7U{V+U`}E1~#t^L&i3-H3j6`AFr5G~@Q@#OR|h zM-D+C@-32G5@Fh~o;col@`7p+Ipl*s8cngvxaxw2r|mk4(K8trH!&9v1zTn14?hdT zbCxzvhKH_y9Z+h2N>@&p?#E7dbI%HN!}S|lr}1Mrqp=rAhKw*Q{fEyrnX8ZOK#KN9 zVXVu$+j=;w*afF7@8v+wg4vh_kr=#4WB~70wE408WYcA5S z{ZG`|Rf8PoY~`N!`@KKhMjFa=CnTfQxN#4W6-L`j?pHh6!C&Vh+6qp&K9vuAzW@Or|V)7zQRJ6?4> zk3W2&D5NHw)~40ZJ-I%*?RI_Oe$yc&ks;0abK;r)8N1juC{jx=^@u3i3Qf{&=u*mI zBZ5Ju%Wx6KSo3OHRJleAv9|kPWhF!|YH+(TxpY4yVy#pbbr;tr(`SdM7mJRR^Lhfi zUU{jx=J<}|3=^t5SoO5eIFnr(OoI|Z!kbt;-b8T`s$4vMtwH}Xn~AWF6#jb;FBoe{ zw{Ds$I=uF9vMs)pmSnUJm^tqH;4z|Ictx(D^a-hXAP3-2QP#`X_j$~(ZPjP6-Xu6V zEOCn>I>5W_pdeFx_nQrVRk@41>Fbu6e}8qH@m5ycN9Xx*CoV{j!@TiLy0?jo-8xGx z^|nw0Prwq^9SMpRSnUmc;Zo=pBtcH3Jdo)EHYfnH!6FYjRWjPUW>ObBWGpl?6!Y`t zSCZ3c;U)h&Hj&~ld@_v^sjN`UMfIDsn-%VVjOj6P77h85ryf34)PZ&X+;H??@7SFk z?6wVr%rdU$s5^QHWiC2q>fXD2{74x*FFV%;@lF7LXN_%_d9@GK6b@Aj5q-6Y)$kU2 zOk)@XSEC0k7yg!WJJ!)7VRpq<{Q2NWC#<}ejJM9fLk}KNnfqr0h%6qqg#6<3e@$(Z zaR3nbB@lBAbj{-0)jjOJ;}6kN_1S92)mwY19OW(-Z%C?$CcIC3oiu|^0s6C`S$szW ziUkHCN!%u7#K_lYS-i#GTXzf|Q17-edRHMr`)0cGf_tE?WvQ_XLXlCTyeExVOkX9! z>|K9jiY?3b^5%fyzS}K*tIr%?1itM;BpC}B_WzikIe=ydd#6M6&>!Fn>dC9EUh&sK z9>}%jMi(z*=E6YhGW4LcURHDyORjM@sz&ptBdEtacY|?dI=>;yD#9jnL6O&~LgBgd z@(Q5Yo!9ib^2Uhub`Q)-aT`fhp)ixbQG#-=ksguJ%LF%55=+mYNtlIC@Fmsv(3SV9 z^R>PD#2LY}JF;7eZa=|9%T>~y%}fyOinn*XmV&2E*xr0_%>Yx9{Gtk42#3)^1I1Vd zupZ^J#oly?xg0*>n$Q2c<=q65fW`VWLV+^|%F;?XMl^ z0Dk#X^oUko5_9Jf8~~twFnh4q>LC0|xlhM&s3GzBn|6n@9-7>mP|!13e47AXX1jiA zgm>mIO}(*U;tWH0`VZo{N0DQqRJZ=a=Y0Au!r?&0K$)Hc3ozsuBY;8^=!W1a#>E@o zu}>)JQ$|SB8ik?6=e~P}hPQa^IQyQ3+V!7!6Rz{upo0PdP!8a3j5t~lopOGdJ|HON zA(b<)7W?IroKNI>z}Q=@s$;x#Zl3P8L1tubo}NSpqAoC@KHe(Y-tL_~|5oPL5bx;S z$|*;a{F;WQGloTvV_c5js8r-%COyS;$$Dc4dqPIKR>ocucRvu;Gy^oKV;1%s5;t~z zS{Vc$$1e9XU!U0(1Xc1;eNwo2(s6^^umkLX!XuIXv3%&;@c7y*h@c|X32E}_heiL;;om37@p$HIs#2jo= z2tC^auo~~zlFHpQ(Xz|pZRHz~Im{zh;CM!k9!Cu}!2lBJ_^&yn)x()RA#A6u#UIy_ zZK7?d@y}6LH|?>;Zt0dU%#>cJe|d|+JhrQ=4z!%3|AggEVRue~53|zuDnR{T2-eQ` z8kyd-Ki@p1pX1J8BOzlE9+(S3Zn%tY_Q4-3#H0)zPvb&k1I{ zVNFf2D>on(u30SNY|G<4T^GsIr&^m{QUcA%)f?uBU3GX~jxJBNLsP8tTy6Kiw)B4d? zzGXcps+skhj{rC-{)X(^d34Pdq@=tVqXhgPHJc3Yg}fLo3?@jB_=~OvGjToa|KY`)u>V{3fV#6Z zJ+pc!($%|(1tqOtdaC@0cgMu`^3&>`+X)Yozry*ZSAyRd51tIkfj$57JPV*uLT)!gH8k^exaA_>ZZWt$f3O`Bv2{KFQr7e;1FpdncSX4-2UIt7;mj zmLA9!imKe@yw6pALNwfXk-c$HOw4dS14DaE=m z$j%&munuhJ@_M8TCnmwZX<~vtx6LOW64qjm1xsc=K7-XM{pz{9hL5w-T*zJg_UZ2B zxO4cNiQ%P88x`e;ADik=0m%YZ@sfy6Z~^iJxdA!RwaZ^YezMD?+2QYuA-44j5gWHk zO3)%=egz@%hehKkk>A&ICJW4465(k!kRQg!8@7x8OLBN*)ubl$-`I(B+%@#PYZFEN zenDYg^1Ico2aiHRc)vdEzFIHW+oZm-XIzUPo(lvomy-IeD5}5b{+Q_X zTp&enJd!!-ny767IoT`2yX0y+f}SBo6(uSEX&gMY?j z7+cKJ*AO`;ZD8SO{u@+A=IA1#mg}a4v~I|dSeDLr{cw%zPEzimVY0G?%|?I{t!}Y< z1gU>DZVy5Lq!v)&m@B5i4e^0;8n;fXxa>*9UP80FueEC`Wz_dvlQ`rkQ%E|!YnEMD z9L>^d+x*bZMyfIgH?$&QS@W)p;cfEPoR1oU6y}SIYT^cA}q$xSy&uznW zjlMjW;0R8=^pvp8;i5|=L9&7FBtT*W{#L{Fo>IK6vf!xYG%5#_rXSStHNoeo8hoA( zrDv`}-UH%;eVFOg>jtgy-NUW)P1wf!>pxs522A-_OqQ7C$&uZJ%%s1j%G0O!#s>Pz zb+U&LD$v1UWzUWWNJq;GzD=tyuVdsTj6X?!y#{PrBK^q?i)OaRFY>yPKWx&{p%V)u6fGzLzLg?2B#qIiph58i%eKIMEiN(ys4L* zh-=B4?j5=i;NNG_S0dh3cxxkwViSPbiB9eJ>$jnw-Z(`<7?hurSyv40fGY=Xh|0M$C zc3uX%8}WuB|6N~T{bbq*Jb9VTg{l~N%>jAgp>EbAS3yD)J$2|{6Z*ZNR6t_nVBDg# zCOv;%e0Tc&JLNgPrRg^xnrzqVHq@HFSde$tN;d7LgztX97UipSczZ>eNP6)-GBTl+ zo4mKx+v=cM$iekUKmdg*L0B_7;lX0kxMSnU8JP(~*36}&8Cg(BsIYZH6%0Udu$-b7 z({K$2m(G{@&{!%1s?Avm>V&;Oxw7&Oix8nl}!lP_vqaWwMC*ZBV ziVigvv5gdrptS7QpXJGn)(~e*)f^_;76TZIQ6$ikZon2-D()LBaQV;XCC>?I*D25E zeGlXIUlYbb9?V*N!A7dd&A#({J`ZuBD9Z9=FHD~k#)l-zd_(rKKE|Hlb z)68B9-n&rEf*-u_rlRE&zNQE6hv?wIywZ`^#Pv22Epbnx-jld~1?`%>Ygkmg4_F+G zrz^gmhMRAH>SdA|%n9y0un2}LSoG5GaDN{Z@BUDvA^O&Yp`guu8`8Vp*T#jIQ6&E( zvBv01JA;T3LE+Ol{lx1}-3%v%zD;>bKydfbs-8|+88db*Umhh0=q6Na#Y{a-0e=Hi zq^C-Y8NU&Ah@QonfGM2N!8xz!B$&V(fx(y!`}yxC@^zQr3EKY_G8&DvX+1>GIJC65 zKlVCnb!27Li{VEs|*cl>)P>C5k*X zNCWLEM-EZ!wYakIL~vmZ?O z)%z)4bDMcTz!VAY-kX44UJ&UEzDnXt@V<7F@y9@v9 zvr8WxdrQ3r?C9GG&*B)N0P{mQ0zL=?sVic^E_X}5{1ksl5IdACdrf~3xD9aT+F18WA!wlM`mfO|zLc~(mcR&-MViH&~vvJ~` zc#s%<%C&Y@H!io_<@_S-YL^;g2mFQH<;aN<3NGzP^l}MztjKUwmwFZsf zs%~fhA4%UGPUZjquZYS@R)|v}Wy{PuO`}MXJx-GBm345OV`YyhPPXiQY?;TNA)6!X z2pNZS%)=S)-~IXi{&8K$xen*P@7H*~9?!=R>qo47H!CbyTb>i9G82FI13oR6z4sbd zuRMDAwxKai>zyG3@tN@DwM2`i{4K(vS5qhFhY?u&1 zW^-D{@mFehqSH6i-D}$*&R1y;`l6mEB@GqgD5>7+D zGhVk^2J@cb>tSE``RF`9ioaRuEPlcE{&gmAK9x_pP`7yWN)EVYzw2i{9?|vv2Z5!G z?z2tJ@Apny-%*Qa63D-ca~U+!Dl^QpWcJ$Cz0wKj!buq++3>h$9q#e%i@v6;Y{K_|(%|2<|lugH_aM@^R_rK8;(fg-na| zFveta5slyemXEn-TiaBVGHqf3PTLWEkQjUEk>`FeWT*((uF;rmY*E)SVL1}UTtxKk zR_AJpv3MH~$S|k4Od8HUx+F%Ppuo2zLLQHCQl3G=PUf{$#tXUrM*3g zl5bg0YGutwTZG3DgY!o0tJ zRa&BV_HxuZ!`Afo;}!Tg`7xy#VJ8*N8=mY`xP7`L)zo@;OWMA=d^}39$*^ z8=~4@SYJ?0U=K<6p$ls$GKUHl+zE;ZnS2t1YeZs#rhd(G87z?FFI1ZfM!yMvL-YQv zqWUz+FBj1%-O5<$aIR2IvVfKu5~IDQNaAeUvfp=ef}tiWlPYGnF)v zr}jH-`&Etrg~>~pvJcx*9YVjmPt=g;g(?$5|I2G0t* zv^uW<&BYL~i9Cv9@lUMd*N`79z0R!P{jZYOfhQeqK^=o!q;f;;RwKGeZ6ew;=DlY0 zMK*;I@qA6wQmdvZH*7jzS6?sJy6G_zXjpFPkd}$YxK4;1y!!VAJ%X-&EjB$Y4G=jO z$3$d`a{q!;5DVL!0J98!hti-CYvI_?C`!{mYLk%Q)&hA+b|-L^6hfb_F!Zvr4AV@@ zkroiQD<2;inbJAN0zJVL`&Z`U{$414O7n++WehErrL_8!c!lI;_X`^jn)*?k6ELfm z`bqRhM3{2@%He3bU!k6=LAAM06wi$^MSK}#?zN#;)yqRs-4)HL)ECGrZS|mXguV%P-b< zO64M&uHw{JD^oNYVqQ;YnMUmM9IN!P-nO2G<;pI#YXCh_Tq>dVB&aH)6G01zb@wAM z;$IS-^2w6b)cGk_Aqx-B;>9NoMmm3J+wVXJTxG0c@RKJZgu8iIlXfYx*5tx7f?+|o zMSmrb70RtlnJy9iM0ot^Qf4iGkF~~cX$G3BgyJsNlyi%nJg|e$BDp~w0C__asS}VI zw*<7^7#CTdc-wDnpJ&^B;W^qEIU3siMVuvUfRM<+qV*q*k&V%2;luKM;NHIesz+&Q zy+iA!*iIK^noz2=b2P@eg}}c?#XxEipTV|LH_6O^J2J7=9INY~9UykkCRMld3y<*6 zpPvUFcm`p4F5QSU`8Qru9 zHNcP8PSszyE#O_hLRMOUv^b5KV`2lY5siz=GQaj^XdBZNkQ`I|PzHbqY zH#Kplw&lT(rooc7{b|M%m)d2wT- z(!V0`P*uAqv`~)=j)~9-mZ;`zsJy;PKVLKPOrhlna}h80bd4)51$3d6!B$r8y@XwN z>95d@e$4wVD(8Bl(@dH%POTUNsc6IDR%SL7-^uxL9p)^=vL#*KntQu;c==6!Nw@FM zs!`dDqQ8iz=<1L^JG>DQt^1O!Rl{z)-47v(?)SPyqdDPs zx4>7t#Ns>+xzea*bOv3n5~?jqKN4`Fzg?|eKk#+l=)ZxQ_>YE*I<<6xl8E@=qPM0I z=jAQ&Vtd-z%R3o9YCG+U73E}*Snq1Gd#9wIHGKvQCc2Q>{tiZ68|+w5LKs|y(8^0` zm++LA^SSm{CG&v6y?@iB${6@z@gqC}8>@BS70WaVDF1XbJ}S=F^$Iqizk1x%5= zP^=Ekk>LmCj1o-S&3~Ri(l=W&XXr5S56nD<{AyyrVN96xe}R#8cHO{n491H${){@3 z;%KjCDJa>-^ncciurpVIMq{PlMHMpb$_8mLOrS*oW8ipFH@I|_>7+=>=v z?h9SrDY;hA1Gbr}6d%@Z5_F+{cmv`NYLvR5^ADchm45!8|E z_J{i+F4Dg`QrS1HS-(r?0Q=V^5bgr$E(G?-RMoEV?3#AL{Ca&HulL#TfWdx;#{XQH z15f+hV*N#8B`f5YX%;PCVdPrSwen8py=?i>&_npBuSbv!adu`2L@D5G^(PsVD!lVj)_SM4Xo7&(7UigpdrnZRa$ zK2nbhFQUn|n1%&=xtUX33UhTOC=K zJEwV@^4IB93#F{|u^_j)!vDpK@hAY!01n%B*%3&V28;r#%)Fm@aiagk(&tlY@YqO# zd;~na)#))&vCJIapJe4=B|(tL3k)IMS`zAD6GSlfX%_LH8#@5o-wI;a94jc22-M$; z-NNgW8Z)(;d^9({2358u6hN25!M5OwjsQ^APZqMe*)-!~%L~sLa6WeW4BD|Myf(Og ze&GPyCjO+Jj}xhvY@bk1_QvGzHw}j}@3g^lfA3xo(+2~dH zjUt>u5wpc}-_;7s_p{#DY*LoCM~GjT_M-BwGOH~$V-R-B{Ps&M6Wm>Z+X7j?sHC~d zN0GhPFLK_`vi8~BuJX~Al>`2?6J76|?uWd=;j;o^V8w?&2cYVW89Hqo&t z8z*R(_vt;JDKAxj>n*@XSnkW@?x%v46N1a7I4sR_0wm1sZ#n#Xi%YqA__g-~;59T- z(u+rTy6-?(TOYf2%_#arnvdPMpT!}b9v}D_-02Idmxzdg-?sQpmME2V)ECu`DN`G0 zh6Owo^$T?$1WjbFfIzUo2{=FT(IS-nC0gd`wWluS(wh$b!@@u5Koy>SX<@U1cN;q# zHA#>>EKN)Ay$Sj+itz z@3U3asaO~0$Ves51qU=QEWm|eW8OI3e$x>x8Rz(@uBpEH2@@(f1=<3rA&tEe2`NU+ zHh;FM7YR%Vf~%}D2)gk&C}&NG$#@C)0%)8yzN;Gx>KChkbXkeilfaR?E}PM6EDm=q9I7rkTo+Pe zlJ0)EmPTwj`HzN@r_QuQzDAHU+l#{5-vU~jg}-6P1kGH1xod#B+WYr2&4i9pK#sf>V-3v4Nwc+PlI?BUJ56mg0oKu^(1C% zOp}H4pw;D-yZH`3)NeK~Em=GrqG@%iE4t+w5BSlZN8p%q7O7KcR#m+fw+qmkjf3vz z1SY$e$XD@taLYx`DiGA+!8EX*R0mq}StKSDA(TuzE-0&wmo%;@JN|hDa#^{*SWrBDM zBcJ94r1jNLj~-&%EeN#m$@8(eTo>Is$YsbDm3cZ9QF{mZ+Vp*(#s#tBZ<4m~HW^H_ zyEhgSe0bsGvqMyLnh1>1Mszl{wFVL~6; z@r`5&M5O-_hM5u(^@q|<CS`fD$lEiQ4%zh!%3sW@~*)QidrKANm$S1z%0< zqp~|stG`Xk&1SA=2?om{FWo5aBrSvoc%kzDqj6ov#kdiL>SiWhB$<^fvuS;ktHH@W zq5tELx*0D!cCi(_me7N~zDyk@waTuvy}(YxL4UZXB?T}Tp$7__xt}eDV_68F)oQH{ zNzgebfDliYFeR0Aa*unr`9*1tq3^+ZRM6P!sLYnx;=Lg&jT_M6EY-zdZJMj-T}>d% zLVyR%srn{MDa+iv$#$~!X@$kk#pepqI_@TZNjq(9R(rF+wt|XSvpZ&5ai2!Svt15&?qJ@!m-k4~VA~jg!h{z%3}1l|-%J8Z z=K77iPxvuU1b28{5TJSbx@$8UZNBh>1MbxHNBf)CV3|K_G+YA6Tg;wr)mvS@Y_?^5 z3g`W8O9Dk6U))%tOWAZ9G!#!}lP~Sd81cXJEb2Sw3f27`-Iv!TKHNB)HUy>U> zi(@P7>8;5A#`^u$#rBm)V~*!`Jp%NAP#VWgy^i2^lyLyiwQ1MSm7kgX`OEJz2Dv5{ z3U7$ccl3l7y*LBCAs}}b@!b$})MqMt13@wBc}f9WghnIMto7UQbxO+1g{{`kgO$CH z&Y@Y*%6hAe+wb9xxp8WGfTnpR{=t?nqI&9JOU93DttTxZ<$E7Tm6AZWl0b*?HdOB` z1`RyamH0*^GP~w(o_e0!iK%(7f2zHP3?G-}PJiA9VXG4!3mr)GxqEzj>Z^>|UyT+2(eU&ZY&Hf<5&%-o z<(;igizBOld;J=t>XD!0b%h_cA1k!nndwhA6Xt)RhH)7<4*#EJJ%~vJxtb{h-WmlA z+n-lBEU!2;3j7Ax206SXIZ|YUG#_O~6*me;eQ5f|y4dbK@3R$brY=S{&D(xDGWz!1 z6eW}c>n@g*Xxv%r;sqera-cWQsf@-ni*ud&yM_R$1T`sSP@|IeAeP1ryB5E0$Y zOAoxXUK9h+=jfrlI&b>0Bj~AZb+j*fW_v}jE&4jMj!LM$83zPrRyrCee$riIk znx{xSO5rQ@rTipfiXLd=PLF(C-xu;aQkmM)D+D^WuH&O!UycfLzeLiEy+}0X`0;4# zRq@xV-}>=WcqO?~nVAw!h7vVkk9z^>KQkUh)S4yYS-Z5r|M+i!f*sEd^su-~ur-oG zwOh~Dx1*{-Bs?1jn+Gmw&kjgY7c;tK@|4IEfBQ- zUJtD8p^6*~D0P`SC^UQrKjkz2o4q@~>fps1K?UKDV*}oW4~-4Ir;+i~uY5dkw7&&m zKrTU>w6lwC>;@y^gxAjnGR~cm+(_1siMf5V3L7qC8)CadtWs?s9WjAzGf@Yc8@e}Y zLO&}dGtxc~EgsziUp+RDlU1CxavABVsm!Tyi@X%O*RiHD^C4#M=@6in@ILk2zVosV+b$7=)APfg+ZrwYu3yH62&l z)g2~@ZYOr_<45TT;Y#c3DsO|h4pAKV)vM>e*^NCup8t$yPIgr4d;7kJ4Su;Xtej#2 zGB5?Y&+D1Vn-MUT+@6kb0{wS>b?Zab$O4ASOR)C)@B_Djy)y3I+%X8H=@;NV^S-3c zMbtF(jQ8W0A4-v$(niS^zDfoOFR1Qx;@0f7?!VmClPMs8#B9Entx3sp-W5C)4BzQ7 z8jc`UtS@MFNd3M_^>pKS>gK}nl%aqde}fKa`6SX2aoPV*1MkvnWls&)ekM zT}g9vT=wQJ(duc$VWIDUnj!so2d>w$F2OaMa|qUJlVno|TgX23Nce#>s+8NWog3lZ zHw+hfE9?E zPi=l=#L|N&VGOqdl{eZg#&h2iSTZ@Hq;JnhseJgSd||0QieO1*UY%DTE6_O2yXT`0scV!!QI`b}>>KRW-LM}mnMH1J?Puo9Kn zsaLH@^h;(?w`YV!AO+^({`XFY%4TiWh!SBUuUk9 z)_B3E-JA@jq=4BFn7LLs%He2Y@7irrzS?eWInuntC5z%4bX!?L>0is`vq~~ zBGmJw1Yfb)E#X`_)f)1&_}_u{Z?_ClUFX<#=S%m5J@gBkB(EP zCtOOY#wOh8@}e*r)TKSN{yAL_3-u5HNpRNL<|~cPt*sS`kqgLttKaJvjrJz)=SO?j zWMO}6CZySalYW{N6nk`3>A&9xIP~CgjiHtDjK~QWVZW7HS?v@1{BPDvN{W73zm(pW zUfH*T{YT?fuUVmCaWokpQM1M7=$mz^t!*O7sl@vwCH!g9f;A15P#rF5?&-f?HU(3k zX}^VkJ?e()KAr9o!>cCQ`WVObu!!LYX^H+ z`v&g6e=e$9o}qRpsm%Q|a{tjRh*7UR{U0kuoowfO(qewMm87cWKI~Fys*){rGvR1p z{aqm8*}eN%LH$e@2Xhh!)fS+CN8+}M6_#z-&t_Nv;q>n3ZnH&B~sDr3NE8@CQ2 zq6>m~Z^BYgWael<)H4g}yEtrHe!IJUb>iV`8~(8ZI1=6q=8}ZV!w0SC3H|Yxnw<_D zJf0UX^p!LDf37)GytneTy0!OKiBM=S6iw#zT&+YA0HgW;Xb800=;)U*6mC7`X=Ktu zrf}6A`)M`}M1(;7vCog@D46w! zy5yLC@AdXA1U=~TTjP14yPhle&gN?`yae6_;&G)aQ~R?n5Rvej^fKJtMai`R!|)9{ zwxPBQ1GYeN))xp~3lW|4lpxhdrNiTX0mcd)?NcSB;b!diuyQlW4J}r`*v&O%1!IMZ#ORl|27sdzW+qq zT=U0R98v{&2do2^BbsXgetw?4VCLh2e3E)?p(REpQ4>iT_|E#xPBozdaSx4uiH@CB zXP^|RGNrZQTHjdCZ^H#z-n5%t`O2bo^W6r+t;Sc$6zASjak2GYArFl|*NmOsWw0$5 z;~>3by=}@QAhNdz8({g6iH+#nKU{Jvd%l#Zg_$vFOY4v=z_GO{5~~?T>neQ%qnz3Z)Q76~(l8_YRu8-^TmS znA5)fYr{fDW(wJ>Q(x~sl-kR0O?UXG!$l~i7YKJYUIF}zYbH*gHxTa=hWJwNAef*H z^NO{(AW4_nYr)`{Yp`xoJ>^DaQ;~&yzLh-?;d);mF`xMKVjvBD6aM%%{lA`B&L{4% zc9KQwXk0oTjg`!HTN#UMmnL{qcH6HLnClRB6S)zu8qIZIQm%W{GY%{Vnl`qpo8c4Y zl(!c;Kn-X>uMqY{VbZ(Up?SKNoy=|~?mV}ZG@j+OpS@5z=5San_tt~_)T=kLepYVpU{7%< zLhzCl9kW!YvX{;qYbTy5x0yTYx{VLQf>|lG)(f#8n-Va?Js5BoAb;kn2=H4a?Q;Qc zrd{XI#)-Pcmh}MB$_szyFhLBoBJmGzH1q7Sr+Rzhfm@Xb$;f)X!g z>lQ=Hb-^cjMBDslUZDA+E=)qgoddye&~CtEn}Uv=)6D&ix8YhH9d{)D8ntb9jm@{L zz#DA|F>BdT9};@FqFp79S+8v;czSGTeZCTo(Uk7#kpVSDJubaYol)M5It2<5N1{`i zlY1My_qN1087aop8SqMzT2J0oYENu{Bp8+A*ZgeMUvSTx_+~FIf!~E&~@h# z{?%uJbPwO+f6dUO6Re4~?of@D((^u=(H`0cUHBk%P+g{;V7FVbg1F8XnHRqEs9MB_ z+f!VS) z0ByPi6RjUdG@_0OP8?~AwN_>tB9Ujcb@p$Oad*xh*&Yonrcv4n1x4L00nZ8h{mZ}= zFJ;kcO8ap?|84w!4?4m42_@>Ee}FC& zN9zBM&ZE!LPf92i)Ei`NN|vetUIY+*i8qghyScoXeLd@ZJHB71I$E1Uwo|Ayd>goO zolu*ZdU+{cx#kxIjk11PMuG>ZN6NDmNTZFfu?m`8502k8H2_w-F5qi}WiTtk@osQ6 zCDTs8^AAI>e`6o1q#SKK7S6ZEe#Y$|a7IV2T`Ag2Aa%H?2h9$E>rQ@Rvg2SL(27H~ zk>twpfi)t+?EG+TrL`K*+6iIaN_n#PZy7U?^2`0J>ZPlF{WXnG+}&j@l>S^r$|~Dl z5_$EFGm&xurbL`3y@46wnd^%>^r>q=2O<6Tkq4cZH~vB6U?)L zhx9R<&at9^czDuCh^=<;!Pq=kONc!Frd5=Qo_T?>t&YF+hGD3(TvNvg5&XaH+6`M7 zVQp9LG?RT*&Z*u_eWBCGebD{0)>WY?$yup3_@Vd<G+3z3-itMDNNU8}^+<$7=r zvz(;^vc{_0&xcT1E%s1F2brXzE7O0KUS_i1PY%EL>2G)xi
Fn>ZY!VMjhO)IZA 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

@$AN|D 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

2|eHVhrfYszw~@b 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

UOSU~ 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

<)uetMns2ZZLRi0y4Cv6)FQ|n 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

pnr)dOA^8&|oq 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%0D6K69@Eo{L4Z$49;_`13$44 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� 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 `GZ%q{NYg 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
Cannot POST /api/worker/media/upload-target
".utf8) + return (try XCTUnwrap(response), body) + } + + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [RequestCaptureURLProtocol.self] + let client = OpsAPIClient(session: URLSession(configuration: configuration)) + + do { + _ = try await client.requestWorkerMediaUploadTarget( + sessionID: "11111111-1111-1111-1111-111111111111", + assetType: "video", + filename: "recording.mp4", + contentType: "video/mp4", + byteSize: 256, + source: "stream-capture" + ) + XCTFail("Expected admin ingest request to fail") + } catch { + XCTAssertTrue(error.localizedDescription.contains("Admin ingest returned HTTP 404")) + XCTAssertFalse(error.localizedDescription.localizedCaseInsensitiveContains("ops-api")) + XCTAssertTrue(error.localizedDescription.contains("/api/worker/media/upload-target")) + } + } +} diff --git a/samples/CameraAccess/server/index.js b/samples/CameraAccess/server/index.js index dbb9149d..b1d6f5fa 100644 --- a/samples/CameraAccess/server/index.js +++ b/samples/CameraAccess/server/index.js @@ -4,38 +4,71 @@ const path = require("path"); const { WebSocketServer } = require("ws"); const PORT = process.env.PORT || 8080; +const NODE_ENV = process.env.NODE_ENV || "development"; +const IS_PRODUCTION = NODE_ENV === "production"; const rooms = new Map(); // roomCode -> { creator: ws, viewer: ws, destroyTimer: timeout|null } // Grace period (ms) before destroying a room when creator disconnects. // Allows the iOS user to switch apps (e.g. copy room code, send via WhatsApp) and come back. const ROOM_GRACE_PERIOD_MS = 60_000; -// TURN: ExpressTURN (1000 GB/month free, reliable) -// Ports 3478 (standard), 80, 443 (firewall bypass) -const EXPRESSTURN_SERVER = process.env.EXPRESSTURN_SERVER || "free.expressturn.com"; -const EXPRESSTURN_USER = process.env.EXPRESSTURN_USER || "efPU52K4SLOQ34W2QY"; -const EXPRESSTURN_PASS = process.env.EXPRESSTURN_PASS || "1TJPNFxHKXrZfelz"; +const STUN_SERVER = process.env.STUN_SERVER || "stun:stun.l.google.com:19302"; +const EXPRESSTURN_SERVER = process.env.EXPRESSTURN_SERVER || ""; +const EXPRESSTURN_USER = process.env.EXPRESSTURN_USER || ""; +const EXPRESSTURN_PASS = process.env.EXPRESSTURN_PASS || ""; +const HAS_TURN_CREDENTIALS = Boolean( + EXPRESSTURN_SERVER && EXPRESSTURN_USER && EXPRESSTURN_PASS +); + +if (IS_PRODUCTION && !HAS_TURN_CREDENTIALS) { + throw new Error("TURN credentials are required when NODE_ENV=production"); +} + +if (!HAS_TURN_CREDENTIALS) { + console.warn("[TURN] No TURN credentials configured; serving STUN-only ICE config."); +} function getTurnCredentials() { + const iceServers = []; + + if (STUN_SERVER) { + iceServers.push({ urls: [STUN_SERVER] }); + } + + if (HAS_TURN_CREDENTIALS) { + iceServers.push({ + urls: [ + `turn:${EXPRESSTURN_SERVER}:3478`, + `turn:${EXPRESSTURN_SERVER}:3478?transport=tcp`, + `turn:${EXPRESSTURN_SERVER}:80`, + `turn:${EXPRESSTURN_SERVER}:80?transport=tcp`, + `turns:${EXPRESSTURN_SERVER}:443?transport=tcp`, + ], + username: EXPRESSTURN_USER, + credential: EXPRESSTURN_PASS, + }); + } + return { - iceServers: [ - { - urls: [ - `turn:${EXPRESSTURN_SERVER}:3478`, - `turn:${EXPRESSTURN_SERVER}:3478?transport=tcp`, - `turn:${EXPRESSTURN_SERVER}:80`, - `turn:${EXPRESSTURN_SERVER}:80?transport=tcp`, - `turns:${EXPRESSTURN_SERVER}:443?transport=tcp`, - ], - username: EXPRESSTURN_USER, - credential: EXPRESSTURN_PASS, - }, - ], + iceServers, }; } // HTTP server for serving the web viewer const httpServer = http.createServer((req, res) => { + if (req.url === "/health") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + ok: true, + environment: NODE_ENV, + roomCount: rooms.size, + turnConfigured: HAS_TURN_CREDENTIALS, + }) + ); + return; + } + // TURN credentials API endpoint if (req.url === "/api/turn") { const creds = getTurnCredentials(); diff --git a/samples/CameraAccessAndroid/app/build.gradle.kts b/samples/CameraAccessAndroid/app/build.gradle.kts index c20937c8..27d6dbbb 100644 --- a/samples/CameraAccessAndroid/app/build.gradle.kts +++ b/samples/CameraAccessAndroid/app/build.gradle.kts @@ -12,6 +12,27 @@ plugins { alias(libs.plugins.compose.compiler) } +fun loadEnvFile(projectRoot: File): Map { + val envFile = projectRoot.resolve(".env") + if (!envFile.exists()) return emptyMap() + + return envFile.readLines() + .map { it.trim() } + .filter { it.isNotEmpty() && !it.startsWith("#") && it.contains("=") } + .associate { + val splitIndex = it.indexOf('=') + val key = it.substring(0, splitIndex).trim() + val value = it.substring(splitIndex + 1).trim().removeSurrounding("\"") + key to value + } +} + +fun envValue(envMap: Map, key: String, defaultValue: String): String { + return envMap[key] ?: System.getenv(key) ?: defaultValue +} + +val repoEnv = loadEnvFile(rootProject.projectDir) + android { namespace = "com.meta.wearable.dat.externalsampleapps.cameraaccess" compileSdk = 35 @@ -27,6 +48,10 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } + + val openClawTailscaleIp = envValue(repoEnv, "OPENCLAW_TAILSCALE_IP", "") + + buildConfigField("String", "OPENCLAW_TAILSCALE_IP", "\"${openClawTailscaleIp}\"") } buildTypes { @@ -44,14 +69,6 @@ android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } - signingConfigs { - getByName("debug") { - storeFile = file("sample.keystore") - storePassword = "sample" - keyAlias = "sample" - keyPassword = "sample" - } - } } dependencies { diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/Secrets.kt.example b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/Secrets.kt.example index c231b5e5..80ed3a4a 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/Secrets.kt.example +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/Secrets.kt.example @@ -4,6 +4,12 @@ package com.meta.wearable.dat.externalsampleapps.cameraaccess object Secrets { + // OPTIONAL: Stable device ID for SOP/heartbeat telemetry + const val deviceId = "YOUR_DEVICE_UUID" + + // Prototype worker identity used during ops-api bootstrap + const val workerLoginCode = "EMBC-0001" + // REQUIRED: Get your key at https://aistudio.google.com/apikey const val geminiAPIKey = "YOUR_GEMINI_API_KEY" @@ -11,9 +17,17 @@ object Secrets { // Use your Mac's Bonjour hostname (run: scutil --get LocalHostName) const val openClawHost = "http://YOUR_MAC_HOSTNAME.local" const val openClawPort = 18789 + const val openClawTailscaleIP = "srv1338555" + const val openClawBearerToken = "" const val openClawHookToken = "YOUR_OPENCLAW_HOOK_TOKEN" const val openClawGatewayToken = "YOUR_OPENCLAW_GATEWAY_TOKEN" - // OPTIONAL: WebRTC signaling server URL (for live POV streaming) - const val webrtcSignalingURL = "wss://YOUR_SIGNALING_SERVER" + // Operations API URL for worker bootstrap, sessions, events, and media registration. + const val opsBaseURL = "https://ops.embarcaderolabs.cloud" + + // Signaling service URL for live supervisor jump-in. + const val signalBaseURL = "https://signal.embarcaderolabs.cloud" + + // Backward-compatible legacy signaling URL used by older code paths. + const val webrtcSignalingURL = "wss://signal.embarcaderolabs.cloud" } diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiConfig.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiConfig.kt index 10ba908e..17431608 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiConfig.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiConfig.kt @@ -21,6 +21,15 @@ object GeminiConfig { val apiKey: String get() = SettingsManager.geminiAPIKey + val workerLoginCode: String + get() = SettingsManager.workerLoginCode + + val opsBaseURL: String + get() = SettingsManager.opsBaseURL + + val signalBaseURL: String + get() = SettingsManager.signalBaseURL + val openClawHost: String get() = SettingsManager.openClawHost @@ -33,6 +42,15 @@ object GeminiConfig { val openClawGatewayToken: String get() = SettingsManager.openClawGatewayToken + val openClawTailscaleIP: String + get() = SettingsManager.openClawTailscaleIP + + val openClawBearerToken: String + get() = SettingsManager.openClawBearerToken + + val deviceId: String + get() = SettingsManager.deviceId + fun websocketURL(): String? { if (apiKey == "YOUR_GEMINI_API_KEY" || apiKey.isEmpty()) return null return "$WEBSOCKET_BASE_URL?key=$apiKey" diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiLiveService.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiLiveService.kt index d046d306..6643a894 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiLiveService.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiLiveService.kt @@ -50,6 +50,8 @@ class GeminiLiveService { var onOutputTranscription: ((String) -> Unit)? = null var onToolCall: ((GeminiToolCall) -> Unit)? = null var onToolCallCancellation: ((GeminiToolCallCancellation) -> Unit)? = null + var onSocketOpened: (() -> Unit)? = null + var onSocketClosed: ((String?) -> Unit)? = null // Latency tracking private var lastUserSpeechEnd: Long = 0 @@ -59,6 +61,10 @@ class GeminiLiveService { private val sendExecutor = Executors.newSingleThreadExecutor() private var connectCallback: ((Boolean) -> Unit)? = null private var timeoutTimer: Timer? = null + @Volatile private var latestVideoFrameBase64: String? = null + + val lastVideoFrameBase64: String? + get() = latestVideoFrameBase64 private val client = OkHttpClient.Builder() .readTimeout(0, TimeUnit.MILLISECONDS) @@ -80,6 +86,7 @@ class GeminiLiveService { webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { Log.d(TAG, "WebSocket opened") + onSocketOpened?.invoke() _connectionState.value = GeminiConnectionState.SettingUp sendSetupMessage() } @@ -98,6 +105,7 @@ class GeminiLiveService { _connectionState.value = GeminiConnectionState.Error(msg) _isModelSpeaking.value = false resolveConnect(false) + onSocketClosed?.invoke(msg) onDisconnected?.invoke(msg) } @@ -106,6 +114,7 @@ class GeminiLiveService { _connectionState.value = GeminiConnectionState.Disconnected _isModelSpeaking.value = false resolveConnect(false) + onSocketClosed?.invoke("Connection closing (code $code: $reason)") onDisconnected?.invoke("Connection closed (code $code: $reason)") } @@ -113,6 +122,7 @@ class GeminiLiveService { Log.d(TAG, "WebSocket closed: $code $reason") _connectionState.value = GeminiConnectionState.Disconnected _isModelSpeaking.value = false + onSocketClosed?.invoke("Connection closed (code $code: $reason)") } }) @@ -134,10 +144,13 @@ class GeminiLiveService { fun disconnect() { timeoutTimer?.cancel() timeoutTimer = null + onSocketClosed?.invoke("Disconnected") webSocket?.close(1000, null) webSocket = null onToolCall = null onToolCallCancellation = null + onSocketOpened = null + onSocketClosed = null _connectionState.value = GeminiConnectionState.Disconnected _isModelSpeaking.value = false resolveConnect(false) @@ -165,6 +178,7 @@ class GeminiLiveService { val baos = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, GeminiConfig.VIDEO_JPEG_QUALITY, baos) val base64 = Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP) + latestVideoFrameBase64 = base64 val json = JSONObject().apply { put("realtimeInput", JSONObject().apply { put("video", JSONObject().apply { @@ -269,6 +283,7 @@ class GeminiLiveService { val seconds = goAway.optJSONObject("timeLeft")?.optInt("seconds", 0) ?: 0 _connectionState.value = GeminiConnectionState.Disconnected _isModelSpeaking.value = false + onSocketClosed?.invoke("Server closing (time left: ${seconds}s)") onDisconnected?.invoke("Server closing (time left: ${seconds}s)") return } diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiSessionViewModel.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiSessionViewModel.kt index 31567442..03e2cffd 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiSessionViewModel.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/GeminiSessionViewModel.kt @@ -12,12 +12,18 @@ import com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.ToolCallRo import com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.ToolCallStatus import com.meta.wearable.dat.externalsampleapps.cameraaccess.stream.StreamingMode import kotlinx.coroutines.Job +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.json.JSONArray +import org.json.JSONObject +import java.time.Instant +import java.util.UUID data class GeminiUiState( val isGeminiActive: Boolean = false, @@ -39,12 +45,18 @@ class GeminiSessionViewModel : ViewModel() { val uiState: StateFlow = _uiState.asStateFlow() private val geminiService = GeminiLiveService() + private val sopRelayClient = SopRelayClient() private val openClawBridge = OpenClawBridge() private var toolCallRouter: ToolCallRouter? = null private val audioManager = AudioManager() private val eventClient = OpenClawEventClient() private var lastVideoFrameTime: Long = 0 private var stateObservationJob: Job? = null + private var heartbeatJob: Job? = null + private var heartbeatTimeoutJob: Job? = null + private var currentSopSessionId: String? = null + private var isSopSessionTerminated = true + private var isFinalizingSession = false var streamingMode: StreamingMode = StreamingMode.GLASSES @@ -93,14 +105,23 @@ class GeminiSessionViewModel : ViewModel() { } geminiService.onDisconnected = { reason -> - if (_uiState.value.isGeminiActive) { - stopSession() + if (_uiState.value.isGeminiActive && !isFinalizingSession) { + resetToIdle("Connection lost: ${reason ?: "Unknown error"}") _uiState.value = _uiState.value.copy( errorMessage = "Connection lost: ${reason ?: "Unknown error"}" ) } } + geminiService.onSocketOpened = { + startSopHeartbeatSession() + } + + geminiService.onSocketClosed = { + if (!_uiState.value.isGeminiActive || isFinalizingSession) return@onSocketClosed + finalizeSessionWithReceipt(status = "terminated") + } + // Check OpenClaw and start session viewModelScope.launch { openClawBridge.checkConnection() @@ -111,6 +132,11 @@ class GeminiSessionViewModel : ViewModel() { geminiService.onToolCall = { toolCall -> for (call in toolCall.functionCalls) { + if (call.name == "log_sop_step") { + handleSopLogToolCall(call) + continue + } + toolCallRouter?.handleToolCall(call) { response -> geminiService.sendToolResponse(response) } @@ -182,13 +208,7 @@ class GeminiSessionViewModel : ViewModel() { fun stopSession() { eventClient.disconnect() - toolCallRouter?.cancelAll() - toolCallRouter = null - audioManager.stopCapture() - geminiService.disconnect() - stateObservationJob?.cancel() - stateObservationJob = null - _uiState.value = GeminiUiState() + finalizeSessionWithReceipt(status = "terminated") } fun sendVideoFrameIfThrottled(bitmap: Bitmap) { @@ -209,4 +229,141 @@ class GeminiSessionViewModel : ViewModel() { super.onCleared() stopSession() } + + private fun handleSopLogToolCall(call: com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.GeminiFunctionCall) { + val stepName = call.args["step_name"]?.toString()?.trim().orEmpty() + if (stepName.isEmpty()) { + geminiService.sendToolResponse(buildToolResponse( + callId = call.id, + name = call.name, + result = com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.ToolResult.Failure("Missing required argument: step_name") + )) + return + } + + val sessionId = currentSopSessionId ?: UUID.randomUUID().toString().also { + currentSopSessionId = it + isSopSessionTerminated = false + } + + val imageBase64 = call.args["frame_data"]?.toString()?.takeIf { it.isNotBlank() } + ?: call.args["image_base64"]?.toString()?.takeIf { it.isNotBlank() } + ?: geminiService.lastVideoFrameBase64.orEmpty() + + viewModelScope.launch(Dispatchers.IO) { + sopRelayClient.postSopLog( + tailscaleIp = GeminiConfig.openClawTailscaleIP, + sessionId = sessionId, + stepName = stepName, + timestampIso8601 = Instant.now().toString(), + imageBase64 = imageBase64 + ) + } + + geminiService.sendToolResponse(buildToolResponse( + callId = call.id, + name = call.name, + result = com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.ToolResult.Success("SOP step forwarded") + )) + } + + private fun startSopHeartbeatSession() { + if (currentSopSessionId != null && !isSopSessionTerminated) return + + val sessionId = UUID.randomUUID().toString() + currentSopSessionId = sessionId + isSopSessionTerminated = false + + heartbeatJob?.cancel() + heartbeatTimeoutJob?.cancel() + + heartbeatJob = viewModelScope.launch(Dispatchers.IO) { + sopRelayClient.postHeartbeat( + tailscaleIp = GeminiConfig.openClawTailscaleIP, + sessionId = sessionId, + status = "active" + ) + + while (isActive && !isSopSessionTerminated) { + sopRelayClient.postHeartbeat( + tailscaleIp = GeminiConfig.openClawTailscaleIP, + sessionId = sessionId, + status = "active" + ) + delay(3_000) + } + } + + heartbeatTimeoutJob = viewModelScope.launch { + delay(60_000) + finalizeSessionWithReceipt(status = "terminated") + } + } + + private fun finalizeSessionWithReceipt(status: String) { + if (isFinalizingSession) return + + val sessionId = currentSopSessionId + if (sessionId == null) { + resetToIdle(null) + return + } + if (isSopSessionTerminated) { + resetToIdle(null) + return + } + + isFinalizingSession = true + isSopSessionTerminated = true + heartbeatJob?.cancel() + heartbeatJob = null + heartbeatTimeoutJob?.cancel() + heartbeatTimeoutJob = null + + viewModelScope.launch(Dispatchers.IO) { + val receiptMessage = sopRelayClient.postHeartbeatForReceipt( + tailscaleIp = GeminiConfig.openClawTailscaleIP, + sessionId = sessionId, + status = status + ) + + withContext(Dispatchers.Main) { + resetToIdle(receiptMessage) + } + } + } + + private fun resetToIdle(receiptMessage: String?) { + eventClient.disconnect() + geminiService.onDisconnected = null + geminiService.onSocketClosed = null + geminiService.onSocketOpened = null + + toolCallRouter?.cancelAll() + toolCallRouter = null + audioManager.stopCapture() + geminiService.disconnect() + stateObservationJob?.cancel() + stateObservationJob = null + + _uiState.value = GeminiUiState(errorMessage = receiptMessage) + isFinalizingSession = false + currentSopSessionId = null + } + + private fun buildToolResponse( + callId: String, + name: String, + result: com.meta.wearable.dat.externalsampleapps.cameraaccess.openclaw.ToolResult + ): JSONObject { + return JSONObject().apply { + put("toolResponse", JSONObject().apply { + put("functionResponses", JSONArray().put(JSONObject().apply { + put("id", callId) + put("name", name) + put("response", result.toJSON()) + })) + }) + } + } } diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/SopRelayClient.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/SopRelayClient.kt new file mode 100644 index 00000000..d309a55c --- /dev/null +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/gemini/SopRelayClient.kt @@ -0,0 +1,144 @@ +package com.meta.wearable.dat.externalsampleapps.cameraaccess.gemini + +import android.util.Log +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.util.concurrent.TimeUnit +import org.json.JSONObject + +class SopRelayClient { + companion object { + private const val TAG = "SopRelayClient" + private const val JSON_MEDIA_TYPE = "application/json; charset=utf-8" + private const val MAX_RETRIES = 3 + } + + private val client = OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .build() + + fun postSopLog( + tailscaleIp: String, + sessionId: String, + stepName: String, + timestampIso8601: String, + imageBase64: String + ) { + if (tailscaleIp.isBlank()) { + Log.w(TAG, "OPENCLAW_TAILSCALE_IP is empty, skipping SOP log relay") + return + } + + val payload = JSONObject().apply { + put("session_id", sessionId) + put("step_name", stepName) + put("timestamp", timestampIso8601) + put("image_base64", stripDataUriPrefix(imageBase64)) + } + + postJson("http://$tailscaleIp:8000/api/v1/sop-log", payload) + } + + fun postHeartbeat( + tailscaleIp: String, + sessionId: String, + status: String + ) { + if (tailscaleIp.isBlank()) { + Log.w(TAG, "OPENCLAW_TAILSCALE_IP is empty, skipping heartbeat relay") + return + } + + val payload = JSONObject().apply { + put("session_id", sessionId) + put("status", status) + } + + postJson("http://$tailscaleIp:8000/api/v1/heartbeat", payload) + } + + fun postHeartbeatForReceipt( + tailscaleIp: String, + sessionId: String, + status: String + ): String? { + if (tailscaleIp.isBlank()) { + Log.w(TAG, "OPENCLAW_TAILSCALE_IP is empty, skipping heartbeat relay") + return null + } + + val payload = JSONObject().apply { + put("session_id", sessionId) + put("status", status) + } + + val rawBody = postJsonWithResponse("http://$tailscaleIp:8000/api/v1/heartbeat", payload) ?: return null + + return try { + JSONObject(rawBody).optString("message").ifBlank { rawBody } + } catch (_: Exception) { + rawBody + } + } + + private fun postJson(url: String, payload: JSONObject) { + postJsonWithResponse(url, payload) + } + + private fun postJsonWithResponse(url: String, payload: JSONObject): String? { + var attempt = 0 + var backoffMs = 500L + + while (attempt < MAX_RETRIES) { + attempt += 1 + + try { + val requestBody = payload.toString().toRequestBody(JSON_MEDIA_TYPE.toMediaType()) + val request = Request.Builder() + .url(url) + .post(requestBody) + .build() + + client.newCall(request).execute().use { response -> + val responseBody = response.body?.string() + + if (response.isSuccessful) { + return responseBody + } + + if (response.code in 500..599 && attempt < MAX_RETRIES) { + Thread.sleep(backoffMs) + backoffMs *= 2 + continue + } + + Log.w(TAG, "POST $url failed with HTTP ${response.code}") + return responseBody + } + } catch (e: Exception) { + if (attempt < MAX_RETRIES) { + Thread.sleep(backoffMs) + backoffMs *= 2 + continue + } + + Log.e(TAG, "POST failed for $url after $attempt attempts: ${e.message}") + return null + } + } + + return null + } + + private fun stripDataUriPrefix(value: String): String { + return if (value.startsWith("data:image/jpeg;base64,")) { + value.removePrefix("data:image/jpeg;base64,") + } else { + value + } + } +} diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallModels.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallModels.kt index 696a0c8a..4ae0b322 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallModels.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallModels.kt @@ -103,7 +103,9 @@ sealed class OpenClawConnectionState { object ToolDeclarations { fun allDeclarationsJSON(): JSONArray { - return JSONArray().put(executeJSON()) + return JSONArray() + .put(executeJSON()) + .put(logSopStepJSON()) } private fun executeJSON(): JSONObject { @@ -123,4 +125,42 @@ object ToolDeclarations { put("behavior", "BLOCKING") } } + + private fun logSopStepJSON(): JSONObject { + return JSONObject().apply { + put("name", "log_sop_step") + put("description", "Log a standard operating procedure step to the external SOP processor.") + put("parameters", JSONObject().apply { + put("type", "object") + put("properties", JSONObject().apply { + put("step_number", JSONObject().apply { + put("type", "integer") + put("description", "Current SOP step number (1-based).") + }) + put("step_name", JSONObject().apply { + put("type", "string") + put("description", "The SOP step label that should be logged.") + }) + put("action", JSONObject().apply { + put("type", "string") + put("description", "Step action state: started, completed, failed, or skipped.") + }) + put("total_steps", JSONObject().apply { + put("type", "integer") + put("description", "Total number of SOP steps.") + }) + put("frame_data", JSONObject().apply { + put("type", "string") + put("description", "Optional current frame image in base64. If omitted, the app uses the latest captured frame.") + }) + put("notes", JSONObject().apply { + put("type", "string") + put("description", "Optional operator/model notes for this step.") + }) + }) + put("required", JSONArray().put("step_name")) + }) + put("behavior", "NON_BLOCKING") + } + } } diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallRouter.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallRouter.kt index 35337e14..1e0d05f6 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallRouter.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/openclaw/ToolCallRouter.kt @@ -33,7 +33,7 @@ class ToolCallRouter( Log.d(TAG, "Circuit breaker open ($consecutiveFailures consecutive failures), rejecting $callId") val errorResult = ToolResult.Failure( "Tool execution is temporarily unavailable after $consecutiveFailures consecutive failures. " + - "Please tell the user you cannot complete this action right now and suggest they check their OpenClaw gateway connection." + "Please tell the user you cannot complete this action right now and suggest they check their Video AI Analyst gateway connection." ) sendResponse(buildToolResponse(callId, callName, errorResult)) return diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt index dd8d2d26..11ce560f 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt @@ -2,25 +2,67 @@ package com.meta.wearable.dat.externalsampleapps.cameraaccess.settings import android.content.Context import android.content.SharedPreferences +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import com.meta.wearable.dat.externalsampleapps.cameraaccess.BuildConfig import com.meta.wearable.dat.externalsampleapps.cameraaccess.Secrets +import java.security.KeyStore +import java.util.UUID +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec object SettingsManager { private const val PREFS_NAME = "visionclaw_settings" + private const val SECURE_PREFS_NAME = "visionclaw_secure_settings" + private const val SECURE_KEY_ALIAS = "visionclaw.secure.settings" private lateinit var prefs: SharedPreferences + private lateinit var secureStore: AndroidSecureStringStore fun init(context: Context) { prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + secureStore = AndroidSecureStringStore( + context.getSharedPreferences(SECURE_PREFS_NAME, Context.MODE_PRIVATE), + SECURE_KEY_ALIAS + ) } var geminiAPIKey: String - get() = prefs.getString("geminiAPIKey", null) ?: Secrets.geminiAPIKey - set(value) = prefs.edit().putString("geminiAPIKey", value).apply() + get() { + val stored = getSecureString("geminiAPIKey") + if (!stored.isNullOrBlank()) return stored + + return Secrets.geminiAPIKey + } + set(value) = putSecureString("geminiAPIKey", value) var geminiSystemPrompt: String get() = prefs.getString("geminiSystemPrompt", null) ?: DEFAULT_SYSTEM_PROMPT set(value) = prefs.edit().putString("geminiSystemPrompt", value).apply() + var workerLoginCode: String + get() = prefs.getString("workerLoginCode", null) ?: Secrets.workerLoginCode + set(value) = prefs.edit().putString("workerLoginCode", value).apply() + + var opsBaseURL: String + get() = prefs.getString("opsBaseURL", null) ?: Secrets.opsBaseURL + set(value) = prefs.edit().putString("opsBaseURL", value).apply() + + var signalBaseURL: String + get() { + val stored = prefs.getString("signalBaseURL", null) + if (!stored.isNullOrBlank()) return stored + + val legacy = prefs.getString("webrtcSignalingURL", null) + if (!legacy.isNullOrBlank()) return normalizeSignalBaseURL(legacy) + + return Secrets.signalBaseURL + } + set(value) = prefs.edit().putString("signalBaseURL", normalizeSignalBaseURL(value)).apply() + var openClawHost: String get() = prefs.getString("openClawHost", null) ?: Secrets.openClawHost set(value) = prefs.edit().putString("openClawHost", value).apply() @@ -33,16 +75,60 @@ object SettingsManager { set(value) = prefs.edit().putInt("openClawPort", value).apply() var openClawHookToken: String - get() = prefs.getString("openClawHookToken", null) ?: Secrets.openClawHookToken - set(value) = prefs.edit().putString("openClawHookToken", value).apply() + get() = getSecureString("openClawHookToken") ?: Secrets.openClawHookToken + set(value) = putSecureString("openClawHookToken", value) var openClawGatewayToken: String - get() = prefs.getString("openClawGatewayToken", null) ?: Secrets.openClawGatewayToken - set(value) = prefs.edit().putString("openClawGatewayToken", value).apply() + get() = getSecureString("openClawGatewayToken") ?: Secrets.openClawGatewayToken + set(value) = putSecureString("openClawGatewayToken", value) var webrtcSignalingURL: String - get() = prefs.getString("webrtcSignalingURL", null) ?: Secrets.webrtcSignalingURL - set(value) = prefs.edit().putString("webrtcSignalingURL", value).apply() + get() = normalizeWebsocketURL(signalBaseURL) + set(value) { + val normalized = normalizeSignalBaseURL(value) + prefs.edit() + .putString("signalBaseURL", normalized) + .putString("webrtcSignalingURL", normalized) + .apply() + } + + var openClawTailscaleIP: String + get() { + val stored = prefs.getString("openClawTailscaleIP", null) + if (!stored.isNullOrBlank()) return stored + + val buildValue = BuildConfig.OPENCLAW_TAILSCALE_IP + if (buildValue.isNotBlank()) return buildValue + + return Secrets.openClawTailscaleIP + } + set(value) = prefs.edit().putString("openClawTailscaleIP", value).apply() + + var openClawBearerToken: String + get() { + val stored = getSecureString("openClawBearerToken") + if (!stored.isNullOrBlank()) return stored + + return Secrets.openClawBearerToken + } + set(value) = putSecureString("openClawBearerToken", value) + + var deviceId: String + get() { + val stored = prefs.getString("deviceId", null) + if (!stored.isNullOrBlank()) return stored + + val secret = Secrets.deviceId + if (secret.isNotBlank() && secret != "YOUR_DEVICE_UUID") { + prefs.edit().putString("deviceId", secret).apply() + return secret + } + + val generated = UUID.randomUUID().toString() + prefs.edit().putString("deviceId", generated).apply() + return generated + } + set(value) = prefs.edit().putString("deviceId", value).apply() var videoStreamingEnabled: Boolean get() = prefs.getBoolean("videoStreamingEnabled", true) @@ -54,6 +140,54 @@ object SettingsManager { fun resetAll() { prefs.edit().clear().apply() + secureStore.clear() + } + + private fun normalizeSignalBaseURL(raw: String): String { + val trimmed = raw.trim() + if (trimmed.isEmpty()) return trimmed + return when { + trimmed.startsWith("wss://") -> "https://${trimmed.removePrefix("wss://")}" + trimmed.startsWith("ws://") -> "http://${trimmed.removePrefix("ws://")}" + trimmed.startsWith("https://") || trimmed.startsWith("http://") -> trimmed + else -> "https://$trimmed" + } + } + + private fun normalizeWebsocketURL(raw: String): String { + val trimmed = raw.trim() + if (trimmed.isEmpty()) return trimmed + return when { + trimmed.startsWith("wss://") || trimmed.startsWith("ws://") -> trimmed + trimmed.startsWith("https://") -> "wss://${trimmed.removePrefix("https://")}" + trimmed.startsWith("http://") -> "ws://${trimmed.removePrefix("http://")}" + else -> "wss://$trimmed" + } + } + + private fun getSecureString(key: String): String? { + val stored = secureStore.getString(key) + if (!stored.isNullOrBlank()) return stored + + val legacy = prefs.getString(key, null) + if (!legacy.isNullOrBlank()) { + secureStore.putString(key, legacy) + prefs.edit().remove(key).apply() + return legacy + } + + return null + } + + private fun putSecureString(key: String, value: String) { + val trimmed = value.trim() + prefs.edit().remove(key).apply() + + if (trimmed.isEmpty()) { + secureStore.remove(key) + } else { + secureStore.putString(key, trimmed) + } } const val DEFAULT_SYSTEM_PROMPT = """You are an AI assistant for someone wearing Meta Ray-Ban smart glasses. You can see through their camera and have a voice conversation. Keep responses concise and natural. @@ -82,3 +216,77 @@ Never call execute silently -- the user needs verbal confirmation that you heard For messages, confirm recipient and content before delegating unless clearly urgent.""" } + +private class AndroidSecureStringStore( + private val prefs: SharedPreferences, + private val keyAlias: String, +) { + fun getString(key: String): String? { + val encoded = prefs.getString(key, null) ?: return null + return runCatching { decrypt(encoded) }.getOrNull() + } + + fun putString(key: String, value: String) { + prefs.edit().putString(key, encrypt(value)).apply() + } + + fun remove(key: String) { + prefs.edit().remove(key).apply() + } + + fun clear() { + prefs.edit().clear().apply() + } + + private fun encrypt(value: String): String { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, getOrCreateSecretKey()) + val encrypted = cipher.doFinal(value.toByteArray(Charsets.UTF_8)) + val iv = cipher.iv + return "${encode(iv)}:${encode(encrypted)}" + } + + private fun decrypt(value: String): String { + val parts = value.split(":", limit = 2) + require(parts.size == 2) { "Invalid secure value format" } + + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init( + Cipher.DECRYPT_MODE, + getOrCreateSecretKey(), + GCMParameterSpec(TAG_LENGTH_BITS, decode(parts[0])) + ) + return String(cipher.doFinal(decode(parts[1])), Charsets.UTF_8) + } + + private fun getOrCreateSecretKey(): SecretKey { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + val existingKey = keyStore.getKey(keyAlias, null) as? SecretKey + if (existingKey != null) return existingKey + + val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) + val spec = KeyGenParameterSpec.Builder( + keyAlias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256) + .setRandomizedEncryptionRequired(true) + .build() + keyGenerator.init(spec) + return keyGenerator.generateKey() + } + + private fun encode(value: ByteArray): String = + Base64.encodeToString(value, Base64.NO_WRAP) + + private fun decode(value: String): ByteArray = + Base64.decode(value, Base64.NO_WRAP) + + private companion object { + const val ANDROID_KEYSTORE = "AndroidKeyStore" + const val TRANSFORMATION = "AES/GCM/NoPadding" + const val TAG_LENGTH_BITS = 128 + } +} diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/GeminiOverlayView.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/GeminiOverlayView.kt index 8cfa09cf..ec3c55f2 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/GeminiOverlayView.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/GeminiOverlayView.kt @@ -98,7 +98,7 @@ fun GeminiStatusBar( if (openClawState !is OpenClawConnectionState.NotConfigured) { StatusPill( - label = "OpenClaw", + label = "Video AI Analyst", color = when (openClawState) { is OpenClawConnectionState.Connected -> Color(0xFF4CAF50) is OpenClawConnectionState.Checking -> Color(0xFFFF9800) diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/SettingsScreen.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/SettingsScreen.kt index dd913363..bdf8aa41 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/SettingsScreen.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/ui/SettingsScreen.kt @@ -33,6 +33,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import com.meta.wearable.dat.externalsampleapps.cameraaccess.settings.SettingsManager @@ -42,8 +44,11 @@ fun SettingsScreen( onBack: () -> Unit, modifier: Modifier = Modifier, ) { + var workerLoginCode by remember { mutableStateOf(SettingsManager.workerLoginCode) } var geminiAPIKey by remember { mutableStateOf(SettingsManager.geminiAPIKey) } var systemPrompt by remember { mutableStateOf(SettingsManager.geminiSystemPrompt) } + var opsBaseURL by remember { mutableStateOf(SettingsManager.opsBaseURL) } + var signalBaseURL by remember { mutableStateOf(SettingsManager.signalBaseURL) } var openClawHost by remember { mutableStateOf(SettingsManager.openClawHost) } var openClawPort by remember { mutableStateOf(SettingsManager.openClawPort.toString()) } var openClawHookToken by remember { mutableStateOf(SettingsManager.openClawHookToken) } @@ -54,8 +59,11 @@ fun SettingsScreen( var showResetDialog by remember { mutableStateOf(false) } fun save() { + SettingsManager.workerLoginCode = workerLoginCode.trim() SettingsManager.geminiAPIKey = geminiAPIKey.trim() SettingsManager.geminiSystemPrompt = systemPrompt.trim() + SettingsManager.opsBaseURL = opsBaseURL.trim() + SettingsManager.signalBaseURL = signalBaseURL.trim() SettingsManager.openClawHost = openClawHost.trim() openClawPort.trim().toIntOrNull()?.let { SettingsManager.openClawPort = it } SettingsManager.openClawHookToken = openClawHookToken.trim() @@ -66,8 +74,11 @@ fun SettingsScreen( } fun reload() { + workerLoginCode = SettingsManager.workerLoginCode geminiAPIKey = SettingsManager.geminiAPIKey systemPrompt = SettingsManager.geminiSystemPrompt + opsBaseURL = SettingsManager.opsBaseURL + signalBaseURL = SettingsManager.signalBaseURL openClawHost = SettingsManager.openClawHost openClawPort = SettingsManager.openClawPort.toString() openClawHookToken = SettingsManager.openClawHookToken @@ -98,6 +109,30 @@ fun SettingsScreen( .navigationBarsPadding(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { + SectionHeader("Worker") + MonoTextField( + value = workerLoginCode, + onValueChange = { workerLoginCode = it }, + label = "Login Code", + placeholder = "EMBC-0001", + ) + + SectionHeader("Operations Backend") + MonoTextField( + value = opsBaseURL, + onValueChange = { opsBaseURL = it }, + label = "Ops Base URL", + placeholder = "https://ops.embarcaderolabs.cloud", + keyboardType = KeyboardType.Uri, + ) + MonoTextField( + value = signalBaseURL, + onValueChange = { signalBaseURL = it }, + label = "Signal Base URL", + placeholder = "https://signal.embarcaderolabs.cloud", + keyboardType = KeyboardType.Uri, + ) + // Gemini section SectionHeader("Gemini API") MonoTextField( @@ -105,6 +140,7 @@ fun SettingsScreen( onValueChange = { geminiAPIKey = it }, label = "API Key", placeholder = "Enter Gemini API key", + sensitive = true, ) SectionHeader("System Prompt") @@ -117,7 +153,7 @@ fun SettingsScreen( ) // OpenClaw section - SectionHeader("OpenClaw") + SectionHeader("Video AI Analyst") MonoTextField( value = openClawHost, onValueChange = { openClawHost = it }, @@ -137,22 +173,14 @@ fun SettingsScreen( onValueChange = { openClawHookToken = it }, label = "Hook Token", placeholder = "Hook token", + sensitive = true, ) MonoTextField( value = openClawGatewayToken, onValueChange = { openClawGatewayToken = it }, label = "Gateway Token", placeholder = "Gateway auth token", - ) - - // WebRTC section - SectionHeader("WebRTC") - MonoTextField( - value = webrtcSignalingURL, - onValueChange = { webrtcSignalingURL = it }, - label = "Signaling URL", - placeholder = "wss://your-server.example.com", - keyboardType = KeyboardType.Uri, + sensitive = true, ) // Video @@ -186,7 +214,7 @@ fun SettingsScreen( Column { Text("Proactive Notifications", style = MaterialTheme.typography.bodyLarge) Text( - "Receive updates from OpenClaw spoken through glasses.", + "Receive updates from Video AI Analyst spoken through glasses.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -245,6 +273,7 @@ private fun MonoTextField( label: String, placeholder: String, keyboardType: KeyboardType = KeyboardType.Text, + sensitive: Boolean = false, ) { OutlinedTextField( value = value, @@ -254,6 +283,9 @@ private fun MonoTextField( modifier = Modifier.fillMaxWidth(), textStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace), singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + keyboardOptions = KeyboardOptions( + keyboardType = if (sensitive) KeyboardType.Password else keyboardType + ), + visualTransformation = if (sensitive) PasswordVisualTransformation() else VisualTransformation.None, ) } diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/webrtc/SignalingClient.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/webrtc/SignalingClient.kt index 6dedb61b..7bdee461 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/webrtc/SignalingClient.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/webrtc/SignalingClient.kt @@ -79,11 +79,11 @@ class SignalingClient { } fun joinRoom(code: String) { - sendJSON(JSONObject().put("type", "join").put("room", code)) + sendJSON(JSONObject().put("type", "join").put("room", code).put("room_code", code)) } fun rejoinRoom(code: String) { - sendJSON(JSONObject().put("type", "rejoin").put("room", code)) + sendJSON(JSONObject().put("type", "rejoin").put("room", code).put("room_code", code)) } fun sendSdp(sdp: SessionDescription) { @@ -120,7 +120,7 @@ class SignalingClient { when (type) { "room_created" -> { - val room = json.optString("room", "") + val room = readRoomCode(json) if (room.isNotEmpty()) { onMessageReceived?.invoke(SignalingMessage.RoomCreated(room)) } @@ -129,15 +129,15 @@ class SignalingClient { onMessageReceived?.invoke(SignalingMessage.RoomJoined) } "room_rejoined" -> { - val room = json.optString("room", "") + val room = readRoomCode(json) if (room.isNotEmpty()) { onMessageReceived?.invoke(SignalingMessage.RoomRejoined(room)) } } - "peer_joined" -> { + "peer_joined", "viewer_joined" -> { onMessageReceived?.invoke(SignalingMessage.PeerJoined) } - "peer_left" -> { + "peer_left", "viewer_left" -> { onMessageReceived?.invoke(SignalingMessage.PeerLeft) } "offer" -> { @@ -184,4 +184,8 @@ class SignalingClient { Log.e(TAG, "Failed to parse signaling message: ${e.message}") } } + + private fun readRoomCode(json: JSONObject): String { + return json.optString("room", json.optString("room_code", "")) + } } diff --git a/test_connection.sh b/test_connection.sh new file mode 100755 index 00000000..48605dcc --- /dev/null +++ b/test_connection.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="http://100.64.30.99:8000" + +echo "[1/3] Start session heartbeat..." +curl -sS -X POST "${BASE_URL}/api/v1/heartbeat" \ + -H "Content-Type: application/json" \ + -d '{"session_id":"test-e2e-001","status":"active"}' + +echo +sleep 1 + +echo "[2/3] Send SOP log..." +curl -sS -X POST "${BASE_URL}/api/v1/sop-log" \ + -H "Content-Type: application/json" \ + -d '{"session_id":"test-e2e-001","step_name":"network_validation","timestamp":"2026-03-02T12:00:00Z","image_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}' + +echo +sleep 1 + +echo "[3/3] Terminate session heartbeat..." +curl -sS -X POST "${BASE_URL}/api/v1/heartbeat" \ + -H "Content-Type: application/json" \ + -d '{"session_id":"test-e2e-001","status":"terminated"}' + +echo +echo "Done." From 63bb6048648dcabc75b26cc0663e26a1718839f7 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 11 May 2026 18:32:56 -0500 Subject: [PATCH 02/11] Clean up Gemini audio telemetry merge warnings --- .../CameraAccess/Gemini/AudioManager.swift | 2 +- .../Gemini/GeminiLiveService.swift | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift index 3c6c38d4..184f848e 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift @@ -44,7 +44,7 @@ class AudioManager { try session.setCategory( .playAndRecord, mode: .voiceChat, - options: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers] + options: [.defaultToSpeaker, .allowBluetoothHFP, .mixWithOthers] ) } else { try session.setCategory( diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift index 0c7bb2da..738af80c 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift @@ -165,15 +165,16 @@ class GeminiLiveService: ObservableObject { ] ] ] - self.videoFrameSendCount += 1 - self.logVideoSendStatsIfNeeded( - payloadBytes: jpegData.count, - encodeDurationMs: encodeDurationMs, - totalDurationMs: (CACurrentMediaTime() - frameStartedAt) * 1000 - ) Task { @MainActor [weak self] in - self?.latestVideoFrameBase64 = base64 - self?.sendJSON(json) + guard let self else { return } + self.videoFrameSendCount += 1 + self.logVideoSendStatsIfNeeded( + payloadBytes: jpegData.count, + encodeDurationMs: encodeDurationMs, + totalDurationMs: (CACurrentMediaTime() - frameStartedAt) * 1000 + ) + self.latestVideoFrameBase64 = base64 + self.sendJSON(json) } } } @@ -196,7 +197,9 @@ class GeminiLiveService: ObservableObject { ] ] ] - self?.sendJSON(msg) + Task { @MainActor [weak self] in + self?.sendJSON(msg) + } } } From 3ac276e690cc759f700d80006be46870c44a9adc Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 30 May 2026 20:50:51 -0500 Subject: [PATCH 03/11] Prepare signal service for Cloud Run --- samples/CameraAccess/server/Dockerfile | 2 +- samples/CameraAccess/server/index.js | 92 ++++++++++++++++---------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/samples/CameraAccess/server/Dockerfile b/samples/CameraAccess/server/Dockerfile index 0b977be1..54eed006 100644 --- a/samples/CameraAccess/server/Dockerfile +++ b/samples/CameraAccess/server/Dockerfile @@ -1,7 +1,7 @@ FROM node:20-alpine WORKDIR /app COPY package*.json ./ -RUN npm ci --production +RUN npm install --omit=dev COPY . . EXPOSE 8080 CMD ["node", "index.js"] diff --git a/samples/CameraAccess/server/index.js b/samples/CameraAccess/server/index.js index b1d6f5fa..91b5a06d 100644 --- a/samples/CameraAccess/server/index.js +++ b/samples/CameraAccess/server/index.js @@ -1,3 +1,4 @@ +const crypto = require("crypto"); const http = require("http"); const fs = require("fs"); const path = require("path"); @@ -12,20 +13,16 @@ const rooms = new Map(); // roomCode -> { creator: ws, viewer: ws, destroyTimer: // Allows the iOS user to switch apps (e.g. copy room code, send via WhatsApp) and come back. const ROOM_GRACE_PERIOD_MS = 60_000; -const STUN_SERVER = process.env.STUN_SERVER || "stun:stun.l.google.com:19302"; -const EXPRESSTURN_SERVER = process.env.EXPRESSTURN_SERVER || ""; -const EXPRESSTURN_USER = process.env.EXPRESSTURN_USER || ""; -const EXPRESSTURN_PASS = process.env.EXPRESSTURN_PASS || ""; -const HAS_TURN_CREDENTIALS = Boolean( - EXPRESSTURN_SERVER && EXPRESSTURN_USER && EXPRESSTURN_PASS -); - -if (IS_PRODUCTION && !HAS_TURN_CREDENTIALS) { - throw new Error("TURN credentials are required when NODE_ENV=production"); -} +const STUN_SERVER = process.env.STUN_SERVER || ""; +const TURN_HOST = (process.env.TURN_HOST || "").trim(); +const TURN_PORT = Number(process.env.TURN_PORT || 3478); +const TURN_TLS_PORT = Number(process.env.TURN_TLS_PORT || 5349); +const TURN_TTL_SECONDS = Number(process.env.TURN_TTL_SECONDS || 86400); +const TURN_SHARED_SECRET = (process.env.TURN_SHARED_SECRET || process.env.TURN_SECRET || "").trim(); +const HAS_TURN_CREDENTIALS = Boolean(TURN_HOST && TURN_SHARED_SECRET); if (!HAS_TURN_CREDENTIALS) { - console.warn("[TURN] No TURN credentials configured; serving STUN-only ICE config."); + console.warn("[TURN] No TURN credentials configured; /api/turn will return 503."); } function getTurnCredentials() { @@ -36,16 +33,21 @@ function getTurnCredentials() { } if (HAS_TURN_CREDENTIALS) { + const username = `${Math.floor(Date.now() / 1000) + TURN_TTL_SECONDS}:support`; + const credential = crypto + .createHmac("sha1", TURN_SHARED_SECRET) + .update(username) + .digest("base64"); + iceServers.push({ urls: [ - `turn:${EXPRESSTURN_SERVER}:3478`, - `turn:${EXPRESSTURN_SERVER}:3478?transport=tcp`, - `turn:${EXPRESSTURN_SERVER}:80`, - `turn:${EXPRESSTURN_SERVER}:80?transport=tcp`, - `turns:${EXPRESSTURN_SERVER}:443?transport=tcp`, + `stun:${TURN_HOST}:${TURN_PORT}`, + `turn:${TURN_HOST}:${TURN_PORT}?transport=udp`, + `turn:${TURN_HOST}:${TURN_PORT}?transport=tcp`, + `turns:${TURN_HOST}:${TURN_TLS_PORT}?transport=tcp`, ], - username: EXPRESSTURN_USER, - credential: EXPRESSTURN_PASS, + username, + credential, }); } @@ -61,6 +63,7 @@ const httpServer = http.createServer((req, res) => { res.end( JSON.stringify({ ok: true, + status: "ok", environment: NODE_ENV, roomCount: rooms.size, turnConfigured: HAS_TURN_CREDENTIALS, @@ -71,6 +74,15 @@ const httpServer = http.createServer((req, res) => { // TURN credentials API endpoint if (req.url === "/api/turn") { + if (!HAS_TURN_CREDENTIALS) { + res.writeHead(503, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }); + res.end(JSON.stringify({ error: "TURN credentials are not configured." })); + return; + } + const creds = getTurnCredentials(); res.writeHead(200, { "Content-Type": "application/json", @@ -135,18 +147,26 @@ wss.on("connection", (ws, req) => { switch (msg.type) { case "create": { - const code = generateRoomCode(); + const requested = + typeof msg.room_code === "string" && msg.room_code.trim() + ? msg.room_code.trim().toUpperCase() + : null; + const code = requested && !rooms.has(requested) ? requested : generateRoomCode(); rooms.set(code, { creator: ws, viewer: null, destroyTimer: null }); currentRoom = code; role = "creator"; - ws.send(JSON.stringify({ type: "room_created", room: code })); + ws.send(JSON.stringify({ type: "room_created", room: code, room_code: code })); console.log(`[Room] Created: ${code}`); break; } case "rejoin": { // Creator reconnects to an existing room (after app backgrounding) - const room = rooms.get(msg.room); + const code = + typeof msg.room_code === "string" && msg.room_code.trim() + ? msg.room_code.trim().toUpperCase() + : String(msg.room || "").trim().toUpperCase(); + const room = rooms.get(code); if (!room) { ws.send( JSON.stringify({ type: "error", message: "Room not found" }) @@ -157,23 +177,27 @@ wss.on("connection", (ws, req) => { if (room.destroyTimer) { clearTimeout(room.destroyTimer); room.destroyTimer = null; - console.log(`[Room] Creator rejoined, cancelled destroy timer: ${msg.room}`); + console.log(`[Room] Creator rejoined, cancelled destroy timer: ${code}`); } room.creator = ws; - currentRoom = msg.room; + currentRoom = code; role = "creator"; - ws.send(JSON.stringify({ type: "room_rejoined", room: msg.room })); + ws.send(JSON.stringify({ type: "room_rejoined", room: code, room_code: code })); // If viewer is already waiting, trigger a new offer if (room.viewer && room.viewer.readyState === 1) { - ws.send(JSON.stringify({ type: "peer_joined" })); - console.log(`[Room] Viewer already present, notifying rejoined creator: ${msg.room}`); + ws.send(JSON.stringify({ type: "peer_joined", room: code, room_code: code })); + console.log(`[Room] Viewer already present, notifying rejoined creator: ${code}`); } - console.log(`[Room] Creator rejoined: ${msg.room}`); + console.log(`[Room] Creator rejoined: ${code}`); break; } case "join": { - const room = rooms.get(msg.room); + const code = + typeof msg.room_code === "string" && msg.room_code.trim() + ? msg.room_code.trim().toUpperCase() + : String(msg.room || "").trim().toUpperCase(); + const room = rooms.get(code); if (!room) { ws.send( JSON.stringify({ type: "error", message: "Room not found" }) @@ -185,14 +209,14 @@ wss.on("connection", (ws, req) => { return; } room.viewer = ws; - currentRoom = msg.room; + currentRoom = code; role = "viewer"; - ws.send(JSON.stringify({ type: "room_joined" })); + ws.send(JSON.stringify({ type: "room_joined", room: code, room_code: code })); // Notify creator that viewer joined (only if creator is connected) if (room.creator && room.creator.readyState === 1) { - room.creator.send(JSON.stringify({ type: "peer_joined" })); + room.creator.send(JSON.stringify({ type: "peer_joined", room: code, room_code: code })); } - console.log(`[Room] Viewer joined: ${msg.room}`); + console.log(`[Room] Viewer joined: ${code}`); break; } @@ -228,7 +252,7 @@ wss.on("connection", (ws, req) => { const room = rooms.get(currentRoom); const otherPeer = role === "creator" ? room.viewer : room.creator; if (otherPeer && otherPeer.readyState === 1) { - otherPeer.send(JSON.stringify({ type: "peer_left" })); + otherPeer.send(JSON.stringify({ type: "peer_left", room: currentRoom, room_code: currentRoom })); } if (role === "creator") { // Don't destroy immediately -- give the creator a grace period to reconnect From 7f7fa32ebdc6401ac6405682527147e115a72e09 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 30 May 2026 23:21:33 -0500 Subject: [PATCH 04/11] Route VisionClaw SOP spotting through admin --- .../CameraAccess/Gemini/GeminiConfig.swift | 32 +- .../Gemini/GeminiLiveService.swift | 74 ++- .../Gemini/GeminiLiveSpotter.swift | 190 +++--- .../Gemini/GeminiSessionViewModel.swift | 72 ++- .../CameraAccess/Gemini/SopRelayClient.swift | 557 +++++++++++++++++- .../OpenClaw/ToolCallRouter.swift | 57 ++ .../CameraAccess/Secrets.swift.example | 4 +- .../Settings/SettingsManager.swift | 52 +- .../CameraAccess/Settings/SettingsView.swift | 2 +- .../ViewModels/StreamSessionViewModel.swift | 259 ++++++-- .../WebRTC/WebRTCSessionViewModel.swift | 138 +++++ .../iPhone/IPhoneCameraManager.swift | 68 +++ .../CameraAccessTests/CameraAccessTests.swift | 349 ++++++++++- 13 files changed, 1658 insertions(+), 196 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift index 560d9758..c319a6e6 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift @@ -1,9 +1,28 @@ import Foundation +struct GeminiLiveCredential: Equatable { + let token: String + let queryParameterName: String + let websocketBaseURL: String + let model: String + + static func apiKey(_ apiKey: String = GeminiConfig.apiKey) -> GeminiLiveCredential? { + let trimmed = apiKey.trimmingCharacters(in: .whitespacesAndNewlines) + guard trimmed != "YOUR_GEMINI_API_KEY", !trimmed.isEmpty else { return nil } + return GeminiLiveCredential( + token: trimmed, + queryParameterName: "key", + websocketBaseURL: GeminiConfig.websocketBaseURL, + model: GeminiConfig.model + ) + } +} + enum GeminiConfig { static let websocketBaseURL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent" + static let ephemeralTokenWebsocketBaseURL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained" // Use a model that exists for this API key and supports native audio interactions. - static let model = "models/gemini-2.5-flash-native-audio-latest" + static let model = "models/gemini-live-2.5-flash-native-audio" static let inputAudioSampleRate: Double = 16000 static let outputAudioSampleRate: Double = 24000 @@ -44,9 +63,14 @@ enum GeminiConfig { static var openClawHookToken: String { SettingsManager.shared.openClawHookToken } static var openClawGatewayToken: String { SettingsManager.shared.openClawGatewayToken } - static func websocketURL() -> URL? { - guard apiKey != "YOUR_GEMINI_API_KEY" && !apiKey.isEmpty else { return nil } - return URL(string: "\(websocketBaseURL)?key=\(apiKey)") + static func websocketURL(credential: GeminiLiveCredential) -> URL? { + guard var components = URLComponents(string: credential.websocketBaseURL) else { return nil } + var queryItems = components.queryItems ?? [] + queryItems.append( + URLQueryItem(name: credential.queryParameterName, value: credential.token) + ) + components.queryItems = queryItems + return components.url } static var isConfigured: Bool { diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift index 738af80c..a6c69472 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift @@ -38,6 +38,7 @@ class GeminiLiveService: ObservableObject { private let sendQueue = DispatchQueue(label: "gemini.send", qos: .userInitiated) private var latestVideoFrameBase64: String? private var setupSystemInstruction: String = GeminiConfig.systemInstruction + private var setupModel: String = GeminiConfig.model private var videoFrameSendCount: Int64 = 0 private var videoFrameStatsWindowStart = CACurrentMediaTime() @@ -49,13 +50,17 @@ class GeminiLiveService: ObservableObject { self.urlSession = URLSession(configuration: config, delegate: delegate, delegateQueue: nil) } - func connect(systemInstruction: String? = nil) async -> Bool { - guard let url = GeminiConfig.websocketURL() else { - connectionState = .error("No API key configured") + func connect( + systemInstruction: String? = nil, + credential: GeminiLiveCredential + ) async -> Bool { + guard let url = GeminiConfig.websocketURL(credential: credential) else { + connectionState = .error("Gemini Live credential is invalid") return false } setupSystemInstruction = resolvedSystemInstruction(systemInstruction) + setupModel = credential.model connectionState = .connecting let result = await withCheckedContinuation { (continuation: CheckedContinuation) in @@ -64,6 +69,14 @@ class GeminiLiveService: ObservableObject { self.delegate.onOpen = { [weak self] protocol_ in guard let self else { return } Task { @MainActor in + Task { + await WorkerTelemetry.shared.record( + "gemini_socket_open", + source: "gemini_live", + stage: "connected", + payload: ["protocol": protocol_ ?? NSNull()] + ) + } self.onSocketOpened?() self.connectionState = .settingUp self.sendSetupMessage() @@ -75,6 +88,17 @@ class GeminiLiveService: ObservableObject { guard let self else { return } let reasonStr = reason.flatMap { String(data: $0, encoding: .utf8) } ?? "no reason" Task { @MainActor in + Task { + await WorkerTelemetry.shared.record( + "gemini_socket_closed", + source: "gemini_live", + stage: "closed", + payload: [ + "code": code.rawValue, + "reason": reasonStr + ] + ) + } self.resolveConnect(success: false) self.connectionState = .disconnected self.isModelSpeaking = false @@ -87,6 +111,14 @@ class GeminiLiveService: ObservableObject { guard let self else { return } let msg = error?.localizedDescription ?? "Unknown error" Task { @MainActor in + Task { + await WorkerTelemetry.shared.record( + "gemini_socket_error", + source: "gemini_live", + stage: "failed", + payload: ["error": msg] + ) + } self.resolveConnect(success: false) self.connectionState = .error(msg) self.isModelSpeaking = false @@ -220,6 +252,22 @@ class GeminiLiveService: ObservableObject { totalDurationMs, payloadBytes ) + Task { + await WorkerTelemetry.shared.record( + "gemini_video_frame_sent", + source: "gemini_live", + stage: "video", + durationMs: totalDurationMs, + metricValue: Double(payloadBytes), + metricUnit: "bytes", + payload: [ + "frames": Int(videoFrameSendCount), + "fps": fps, + "encode_ms": encodeDurationMs, + "payload_bytes": payloadBytes + ] + ) + } videoFrameStatsWindowStart = now videoFrameSendCount = 0 } @@ -250,7 +298,7 @@ class GeminiLiveService: ObservableObject { private func sendSetupMessage() { let setup: [String: Any] = [ "setup": [ - "model": GeminiConfig.model, + "model": setupModel, "generationConfig": [ "responseModalities": ["AUDIO"], "thinkingConfig": [ @@ -364,6 +412,14 @@ class GeminiLiveService: ObservableObject { // Setup complete if json["setupComplete"] != nil { connectionState = .ready + Task { + await WorkerTelemetry.shared.record( + "gemini_setup_complete", + source: "gemini_live", + stage: "ready", + payload: ["model": setupModel] + ) + } resolveConnect(success: true) return } @@ -415,6 +471,16 @@ class GeminiLiveService: ObservableObject { if let speechEnd = lastUserSpeechEnd, !responseLatencyLogged { let latency = Date().timeIntervalSince(speechEnd) NSLog("[Latency] %.0fms (user speech end -> first audio)", latency * 1000) + Task { + await WorkerTelemetry.shared.record( + "gemini_first_audio_latency", + source: "gemini_live", + stage: "first_audio", + durationMs: latency * 1000, + metricValue: latency * 1000, + metricUnit: "ms" + ) + } responseLatencyLogged = true } } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift index 8e92b463..dd0fe6b9 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift @@ -2,141 +2,93 @@ import Foundation import UIKit final class GeminiLiveSpotter { - private let session: URLSession = { - let config = URLSessionConfiguration.default - config.timeoutIntervalForRequest = 8 - return URLSession(configuration: config) - }() + private weak var api: WorkerAdminAPI? struct SpotterRequestItem: Hashable { let id: String let name: String let aiPrompt: String let expectedObjects: [String] + let validation: String + let critical: Bool } - func detectVisibleItemIDs( - image: UIImage, - items: [SpotterRequestItem] - ) async throws -> [String] { - guard !items.isEmpty else { return [] } - - guard GeminiConfig.isConfigured else { - return [] - } - - guard let jpegData = image.jpegData(compressionQuality: 0.55) else { - return [] - } - - let base64 = jpegData.base64EncodedString() - let itemHints = items.map { - [ - "id": $0.id, - "name": $0.name, - "ai_prompt": $0.aiPrompt, - "expected_objects": $0.expectedObjects - ] as [String: Any] - } - let prompt = """ - You are the live SOP brain for an operations execution app. - Each candidate item has an AI prompt describing what visual completion looks like. - Expected objects run in parallel with the prompt, but the prompt is the main rule. - - Candidate steps: \(itemHints) - - Look at the image and decide which candidate steps are clearly complete right now. - Only mark a step complete if the image strongly satisfies that step's ai_prompt. - If evidence is ambiguous, do not include it. - - Reply ONLY with a valid JSON array of the item IDs you clearly see as complete. - Example: [\"wallet\", \"thermos\"] - """ - - let payload: [String: Any] = [ - "contents": [ - [ - "parts": [ - ["text": prompt], - [ - "inline_data": [ - "mime_type": "image/jpeg", - "data": base64 - ] - ] - ] - ] - ], - "generationConfig": [ - "temperature": 0.1, - "maxOutputTokens": 64, - "responseMimeType": "application/json" - ] - ] - - let model = "models/gemini-2.5-flash-lite" - let endpoint = "https://generativelanguage.googleapis.com/v1beta/\(model):generateContent?key=\(GeminiConfig.apiKey)" - guard let url = URL(string: endpoint) else { return [] } - - let data = try JSONSerialization.data(withJSONObject: payload) - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = data - - let (responseData, response) = try await session.data(for: request) - guard let http = response as? HTTPURLResponse, - (200...299).contains(http.statusCode) else { - return [] - } - - return parseVisibleIDs(from: responseData, allowedIDs: Set(items.map(\.id))) + struct SpotterMatch: Equatable { + let id: String + let matched: Bool + let confidence: Double + let reason: String + let evidenceTimestamp: String + let autoComplete: Bool } - private func parseVisibleIDs(from data: Data, allowedIDs: Set) -> [String] { - guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let candidates = json["candidates"] as? [[String: Any]], - let first = candidates.first, - let content = first["content"] as? [String: Any], - let parts = content["parts"] as? [[String: Any]], - let text = parts.compactMap({ $0["text"] as? String }).joined(separator: "\n").nonEmpty else { - return [] - } - - // Try direct JSON array first. - if let ids = parseJSONArrayIDs(from: text) { - return ids.filter { allowedIDs.contains($0) } - } - - // If model wrapped in markdown fences. - let cleaned = text - .replacingOccurrences(of: "```json", with: "") - .replacingOccurrences(of: "```", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - - if let ids = parseJSONArrayIDs(from: cleaned) { - return ids.filter { allowedIDs.contains($0) } - } - - return [] + func configure(api: WorkerAdminAPI?) { + self.api = api } - private func parseJSONArrayIDs(from raw: String) -> [String]? { - guard let rawData = raw.data(using: .utf8), - let array = try? JSONSerialization.jsonObject(with: rawData) as? [String] else { - return nil + func detectVisibleItemMatches( + image: UIImage, + items: [SpotterRequestItem], + sessionID: String? + ) async throws -> [SpotterMatch] { + guard !items.isEmpty else { return [] } + guard let api, let sessionID, !sessionID.isEmpty else { return [] } + guard let imagePayload = Self.encodedSpotterImage(image) else { return [] } + + let capturedAt = ISO8601DateFormatter().string(from: Date()) + + var matches: [SpotterMatch] = [] + for item in items { + let response = try await api.requestGeminiSpotter( + GeminiSpotterRequest( + sessionID: sessionID, + stepID: item.id, + stepTitle: item.name, + aiPrompt: item.aiPrompt, + expectedObjects: item.expectedObjects, + imageBase64: imagePayload.base64, + imageMimeType: imagePayload.mimeType, + capturedAt: capturedAt, + critical: item.critical, + allowAIComplete: item.validation.lowercased() == "visual" + ) + ) + + matches.append( + SpotterMatch( + id: item.id, + matched: response.matched, + confidence: response.confidence, + reason: response.reason, + evidenceTimestamp: response.evidenceTimestamp, + autoComplete: response.autoComplete + ) + ) } + return matches + } - return array.map { - $0 - .lowercased() - .trimmingCharacters(in: .whitespacesAndNewlines) - } + private static func encodedSpotterImage(_ image: UIImage) -> (base64: String, mimeType: String)? { + let resized = image.resizedForSpotter(maxDimension: 768) + guard let jpegData = resized.jpegData(compressionQuality: 0.45) else { return nil } + return (jpegData.base64EncodedString(), "image/jpeg") } } -private extension String { - var nonEmpty: String? { - isEmpty ? nil : self +private extension UIImage { + func resizedForSpotter(maxDimension: CGFloat) -> UIImage { + let width = size.width + let height = size.height + guard width > 0, height > 0 else { return self } + + let longest = max(width, height) + guard longest > maxDimension else { return self } + + let scale = maxDimension / longest + let targetSize = CGSize(width: width * scale, height: height * scale) + let renderer = UIGraphicsImageRenderer(size: targetSize) + return renderer.image { _ in + self.draw(in: CGRect(origin: .zero, size: targetSize)) + } } } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift index 77884a33..265a906d 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift @@ -27,9 +27,17 @@ class GeminiSessionViewModel: ObservableObject { private var isFinalizingSession: Bool = false private var pendingSystemInstruction: String? private var currentSessionInstruction: String? + private weak var workerAdminAPI: WorkerAdminAPI? + private var adminExecutionSessionID: String? + private var currentLiveCredential: GeminiLiveCredential? var streamingMode: StreamingMode = .glasses + func configureWorkerAdminAPI(_ api: WorkerAdminAPI?, sessionID: String? = nil) { + workerAdminAPI = api + adminExecutionSessionID = sessionID + } + func startSession(systemInstruction: String? = nil) async { pendingSystemInstruction = normalizedSystemInstruction(systemInstruction) guard !isGeminiActive else { @@ -40,10 +48,11 @@ class GeminiSessionViewModel: ObservableObject { userTranscript = "" aiTranscript = "" - guard GeminiConfig.isConfigured else { - errorMessage = "Gemini API key not configured. Open Settings and add your key from https://aistudio.google.com/apikey" + guard let credential = await resolveLiveCredential() else { + errorMessage = "Gemini Live token unavailable. Confirm the admin URL, worker bearer token, and server Gemini key." return } + currentLiveCredential = credential isGeminiActive = true configureRealtimeCallbacks() @@ -61,7 +70,10 @@ class GeminiSessionViewModel: ObservableObject { } let resolvedInstruction = resolvedSystemInstruction() - let setupOk = await geminiService.connect(systemInstruction: resolvedInstruction) + let setupOk = await geminiService.connect( + systemInstruction: resolvedInstruction, + credential: credential + ) if !setupOk { let message: String @@ -409,7 +421,16 @@ class GeminiSessionViewModel: ObservableObject { return } - let setupOk = await geminiService.connect(systemInstruction: systemInstruction) + guard let credential = await resolveLiveCredential() else { + resetToIdle(receiptMessage: "Gemini Live token unavailable. Confirm the admin URL, worker bearer token, and server Gemini key.") + return + } + currentLiveCredential = credential + + let setupOk = await geminiService.connect( + systemInstruction: systemInstruction, + credential: credential + ) if !setupOk { let message: String if case .error(let err) = geminiService.connectionState { @@ -432,6 +453,49 @@ class GeminiSessionViewModel: ObservableObject { currentSessionInstruction = systemInstruction } + private func resolveLiveCredential() async -> GeminiLiveCredential? { + if let workerAdminAPI, GeminiConfig.isAdminConfigured { + do { + let token = try await workerAdminAPI.requestGeminiLiveToken( + model: GeminiConfig.model, + sessionID: adminExecutionSessionID + ) + await WorkerTelemetry.shared.record( + "gemini_live_token_received", + source: "gemini_live", + stage: "ready", + payload: [ + "model": token.model, + "expires_at": token.expiresAt + ] + ) + return token.credential + } catch { + await WorkerTelemetry.shared.record( + "gemini_live_token_failed", + source: "gemini_live", + stage: "fallback", + payload: [ + "error": error.localizedDescription, + "api_key_fallback_available": GeminiConfig.isConfigured + ] + ) + } + } + + if let fallback = GeminiLiveCredential.apiKey() { + await WorkerTelemetry.shared.record( + "gemini_live_token_fallback", + source: "gemini_live", + stage: "api_key", + payload: ["model": fallback.model] + ) + return fallback + } + + return nil + } + private func normalizedReceiptMessage(_ receiptMessage: String?) -> String? { let trimmed = receiptMessage?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" guard !trimmed.isEmpty else { return nil } diff --git a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift index f95fd216..6dc05d21 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift @@ -54,6 +54,30 @@ private extension KeyedDecodingContainer { } return nil } + + func decodeLossyString(forKeys keys: [K]) throws -> String? { + for key in keys { + if let stringValue = try? decodeIfPresent(String.self, forKey: key) { + let trimmed = stringValue.trimmingCharacters(in: .whitespacesAndNewlines) + if !trimmed.isEmpty { + return trimmed + } + } + if let intValue = try? decodeIfPresent(Int.self, forKey: key) { + return String(intValue) + } + if let doubleValue = try? decodeIfPresent(Double.self, forKey: key) { + if doubleValue.rounded(.towardZero) == doubleValue { + return String(Int(doubleValue)) + } + return String(doubleValue) + } + if let boolValue = try? decodeIfPresent(Bool.self, forKey: key) { + return boolValue ? "true" : "false" + } + } + return nil + } } private func canonicalLucasSopTitle(for sopID: String) -> String? { @@ -389,7 +413,9 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { case label case step case item + case index case description + case instruction case duration case validation case critical @@ -437,14 +463,14 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { } let container = try decoder.container(keyedBy: CodingKeys.self) - let name = try container.decodeIfPresent(String.self, forKey: .name) - let fallbackTitle = try container.decodeIfPresent(String.self, forKey: .title) - let label = try container.decodeIfPresent(String.self, forKey: .label) - let step = try container.decodeIfPresent(String.self, forKey: .step) - let item = try container.decodeIfPresent(String.self, forKey: .item) + let name = try container.decodeLossyString(forKeys: [.name]) + let fallbackTitle = try container.decodeLossyString(forKeys: [.title]) + let label = try container.decodeLossyString(forKeys: [.label]) + let step = try container.decodeLossyString(forKeys: [.step]) + let item = try container.decodeLossyString(forKeys: [.item]) let resolvedTitle = name ?? fallbackTitle ?? label ?? step ?? item ?? "Untitled Step" let resolvedID = - try container.decodeIfPresent(String.self, forKey: .id) + try container.decodeLossyString(forKeys: [.id]) ?? resolvedTitle.lowercased().replacingOccurrences(of: "[^a-z0-9]+", with: "_", options: .regularExpression) let expectedObjects = try container.decodeIfPresent([String].self, forKey: .expectedObjects) @@ -452,19 +478,17 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { ?? [] self.init( id: resolvedID, - order: try container.decodeIfPresent(Int.self, forKey: .order) ?? 0, + order: try container.decodeLossyInt(forKeys: [.order, .index]) ?? 0, title: resolvedTitle, - description: try container.decodeIfPresent(String.self, forKey: .description) ?? "", - duration: try container.decodeIfPresent(String.self, forKey: .duration) ?? "30s", - validation: try container.decodeIfPresent(String.self, forKey: .validation) ?? "visual", - critical: try container.decodeIfPresent(Bool.self, forKey: .critical) ?? false, + description: try container.decodeLossyString(forKeys: [.description, .instruction]) ?? "", + duration: try container.decodeLossyString(forKeys: [.duration]) ?? "30s", + validation: try container.decodeLossyString(forKeys: [.validation]) ?? "visual", + critical: try container.decodeLossyBool(forKeys: [.critical]) ?? false, aiPrompt: - try container.decodeIfPresent(String.self, forKey: .aiPrompt) - ?? container.decodeIfPresent(String.self, forKey: .aiPromptCamel), + try container.decodeLossyString(forKeys: [.aiPrompt, .aiPromptCamel]), expectedObjects: expectedObjects.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }, allowManualComplete: - try container.decodeIfPresent(Bool.self, forKey: .allowManualComplete) - ?? container.decodeIfPresent(Bool.self, forKey: .allowManualCompleteCamel) + try container.decodeLossyBool(forKeys: [.allowManualComplete, .allowManualCompleteCamel]) ?? true ) } @@ -572,7 +596,7 @@ struct WorkerQueueItem: Identifiable, Decodable, Equatable { startsAt = try container.decodeFirstString(forKeys: [.startsAt, .startsAtCamel, .scheduledFor]) endsAt = try container.decodeFirstString(forKeys: [.endsAt, .endsAtCamel, .completedAtCamel]) - if let direct = try container.decodeIfPresent([String].self, forKey: .steps) { + if let direct = try? container.decodeIfPresent([String].self, forKey: .steps) { steps = direct.enumerated().map { index, title in WorkerQueueStep( id: "\(decodedSopID)-\(index + 1)", @@ -815,14 +839,23 @@ struct BackendExecutionSession: Identifiable, Decodable, Equatable { } struct BackendExecutionEvent: Identifiable, Decodable, Equatable { - let id: Int + let id: String let sessionID: String? let eventType: String? private enum CodingKeys: String, CodingKey { case id case sessionID = "session_id" + case sessionIDCamel = "sessionId" case eventType = "event_type" + case eventTypeCamel = "eventType" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decodeLossyString(forKeys: [.id]) ?? UUID().uuidString + sessionID = try container.decodeFirstString(forKeys: [.sessionID, .sessionIDCamel]) + eventType = try container.decodeFirstString(forKeys: [.eventType, .eventTypeCamel]) } } @@ -878,6 +911,385 @@ struct BackendMediaUploadTarget: Decodable, Equatable { } } +struct WorkerTelemetryEvent: @unchecked Sendable { + let name: String + let source: String + let stage: String + let occurredAt: String + let durationMs: Double? + let sequence: Int? + let metricValue: Double? + let metricUnit: String? + let payload: [String: Any] + + init( + name: String, + source: String, + stage: String = "point", + occurredAt: Date = Date(), + durationMs: Double? = nil, + sequence: Int? = nil, + metricValue: Double? = nil, + metricUnit: String? = nil, + payload: [String: Any] = [:] + ) { + self.name = name + self.source = source + self.stage = stage + self.occurredAt = Self.formatter.string(from: occurredAt) + self.durationMs = durationMs + self.sequence = sequence + self.metricValue = metricValue + self.metricUnit = metricUnit + self.payload = WorkerTelemetryPayloadSanitizer.sanitizedPayload(payload) + } + + var wirePayload: [String: Any] { + var payload: [String: Any] = [ + "name": name, + "source": source, + "stage": stage, + "occurredAt": occurredAt + ] + if let durationMs { payload["durationMs"] = durationMs } + if let sequence { payload["sequence"] = sequence } + if let metricValue { payload["metricValue"] = metricValue } + if let metricUnit { payload["metricUnit"] = metricUnit } + if !self.payload.isEmpty { payload["payload"] = self.payload } + return payload + } + + private static var formatter: ISO8601DateFormatter { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formatter + } +} + +struct WorkerTelemetryBatch: @unchecked Sendable { + let sessionID: String + let deviceID: String? + let workerID: String? + let platform: String + let appBuild: String? + let events: [WorkerTelemetryEvent] + + var payload: [String: Any] { + var payload: [String: Any] = [ + "sessionId": sessionID, + "platform": platform, + "events": events.map(\.wirePayload) + ] + if let deviceID { payload["deviceId"] = deviceID } + if let workerID { payload["workerId"] = workerID } + if let appBuild { payload["appBuild"] = appBuild } + return payload + } +} + +enum WorkerTelemetryPayloadSanitizer { + private static let maxStringLength = 512 + private static let maxArrayLength = 25 + private static let maxObjectKeys = 60 + private static let maxPayloadBytes = 8 * 1024 + private static let maxDepth = 4 + + static func sanitizedPayload(_ payload: [String: Any]) -> [String: Any] { + let sanitized = sanitizeDictionary(payload, depth: 0) + guard jsonByteCount(sanitized) > maxPayloadBytes else { return sanitized } + + var trimmed: [String: Any] = ["_truncated": true] + for (key, value) in sanitized { + trimmed[key] = value + if jsonByteCount(trimmed) > maxPayloadBytes { + trimmed.removeValue(forKey: key) + break + } + } + return trimmed + } + + private static func sanitizeDictionary(_ payload: [String: Any], depth: Int) -> [String: Any] { + guard depth <= maxDepth else { return ["_truncated": true] } + var sanitized: [String: Any] = [:] + let entries = Array(payload.prefix(maxObjectKeys)) + for (key, value) in entries { + sanitized[key] = sanitizeValue(value, key: key, depth: depth + 1) + } + if payload.count > entries.count { + sanitized["_truncated"] = true + } + return sanitized + } + + private static func sanitizeValue(_ value: Any, key: String, depth: Int) -> Any { + if value is NSNull { + return NSNull() + } + if let number = value as? NSNumber { + return number + } + if let string = value as? String { + return sanitizeString(string, key: key) + } + if let array = value as? [Any] { + var sanitized = array.prefix(maxArrayLength).enumerated().map { index, item in + sanitizeValue(item, key: "\(key).\(index)", depth: depth + 1) + } + if array.count > sanitized.count { + sanitized.append("[truncated]") + } + return sanitized + } + if let dictionary = value as? [String: Any] { + return sanitizeDictionary(dictionary, depth: depth + 1) + } + return String(describing: value).prefixString(maxStringLength) + } + + private static func sanitizeString(_ value: String, key: String) -> String { + let lowercasedKey = key.lowercased() + if lowercasedKey.contains("authorization") + || lowercasedKey.contains("bearer") + || lowercasedKey.contains("token") + || lowercasedKey.contains("secret") + || lowercasedKey.contains("apikey") + || lowercasedKey.contains("api_key") + || lowercasedKey.contains("password") { + return "[redacted]" + } + if lowercasedKey.contains("signedurl") + || lowercasedKey.contains("signed_url") + || lowercasedKey.contains("uploadurl") + || lowercasedKey.contains("upload_url") { + return "[redacted-url]" + } + if isRawPayloadKey(lowercasedKey) || looksLikeRawPayload(value) { + return "[redacted-raw-payload]" + } + return value.prefixString(maxStringLength) + } + + private static func isRawPayloadKey(_ key: String) -> Bool { + key.contains("base64") + || key.contains("image_data") + || key.contains("imagedata") + || key.contains("audio_data") + || key.contains("audiodata") + || key.contains("video_data") + || key.contains("videodata") + || key.contains("jpeg_data") + || key.contains("jpegdata") + || key.contains("raw_transcript") + || key == "transcript" + } + + private static func looksLikeRawPayload(_ value: String) -> Bool { + if value.hasPrefix("data:image/") || value.hasPrefix("data:audio/") { + return true + } + guard value.count >= 512, value.count % 4 == 0 else { return false } + return value.range(of: #"^[A-Za-z0-9+/]+={0,2}$"#, options: .regularExpression) != nil + } + + private static func jsonByteCount(_ payload: [String: Any]) -> Int { + guard JSONSerialization.isValidJSONObject(payload), + let data = try? JSONSerialization.data(withJSONObject: payload) + else { + return Int.max + } + return data.count + } +} + +actor WorkerTelemetry { + static let shared = WorkerTelemetry() + + typealias Sleeper = @Sendable (UInt64) async -> Void + + private weak var api: WorkerAdminAPI? + private var sessionID: String? + private var deviceID: String? + private var workerID: String? + private var platform: String + private var appBuild: String? + private var sequence: Int = 0 + private var queue: [WorkerTelemetryEvent] = [] + private var flushTask: Task? + private var isFlushing = false + + private let flushIntervalNanoseconds: UInt64 + private let maxBatchSize: Int + private let maxQueueSize: Int + private let sleeper: Sleeper + + init( + api: WorkerAdminAPI? = nil, + sessionID: String? = nil, + deviceID: String? = nil, + workerID: String? = nil, + platform: String = "ios", + appBuild: String? = WorkerTelemetry.defaultAppBuild, + flushIntervalNanoseconds: UInt64 = 5_000_000_000, + maxBatchSize: Int = 20, + maxQueueSize: Int = 500, + sleeper: @escaping Sleeper = { nanoseconds in + guard nanoseconds > 0 else { return } + try? await Task.sleep(nanoseconds: nanoseconds) + } + ) { + self.api = api + self.sessionID = sessionID + self.deviceID = deviceID + self.workerID = workerID + self.platform = platform + self.appBuild = appBuild + self.flushIntervalNanoseconds = flushIntervalNanoseconds + self.maxBatchSize = max(1, maxBatchSize) + self.maxQueueSize = max(1, maxQueueSize) + self.sleeper = sleeper + } + + func configure( + api: WorkerAdminAPI, + sessionID: String, + deviceID: String? = GeminiConfig.deviceID, + workerID: String? = nil, + platform: String = "ios", + appBuild: String? = WorkerTelemetry.defaultAppBuild + ) { + let cleanedSessionID = sessionID.trimmingCharacters(in: .whitespacesAndNewlines) + if self.sessionID != cleanedSessionID { + queue.removeAll() + sequence = 0 + } + self.api = api + self.sessionID = cleanedSessionID + self.deviceID = trimmed(deviceID) + self.workerID = trimmed(workerID) + self.platform = platform + self.appBuild = trimmed(appBuild) + } + + func record( + _ name: String, + source: String, + stage: String = "point", + sessionID explicitSessionID: String? = nil, + durationMs: Double? = nil, + metricValue: Double? = nil, + metricUnit: String? = nil, + payload: [String: Any] = [:] + ) { + guard let resolvedSessionID = trimmed(explicitSessionID) ?? sessionID, + !resolvedSessionID.isEmpty + else { + return + } + + if sessionID == nil { + sessionID = resolvedSessionID + } + + sequence += 1 + queue.append( + WorkerTelemetryEvent( + name: name, + source: source, + stage: stage, + durationMs: durationMs, + sequence: sequence, + metricValue: metricValue, + metricUnit: metricUnit, + payload: payload + ) + ) + if queue.count > maxQueueSize { + queue.removeFirst(queue.count - maxQueueSize) + } + + if queue.count >= maxBatchSize { + Task { await self.flush() } + } else { + scheduleFlush() + } + } + + func flush() async { + guard !isFlushing else { return } + guard let api, let sessionID, !queue.isEmpty else { return } + + let count = min(maxBatchSize, queue.count) + let events = Array(queue.prefix(count)) + queue.removeFirst(count) + isFlushing = true + + do { + try await api.sendWorkerTelemetryBatch( + WorkerTelemetryBatch( + sessionID: sessionID, + deviceID: deviceID, + workerID: workerID, + platform: platform, + appBuild: appBuild, + events: events + ) + ) + } catch { + queue = Array((events + queue).suffix(maxQueueSize)) + NSLog("[telemetry] flush failed: %@", error.localizedDescription) + } + + isFlushing = false + if !queue.isEmpty { + scheduleFlush() + } + } + + func flushAndStop() async { + flushTask?.cancel() + flushTask = nil + await flush() + } + + private func scheduleFlush() { + guard flushIntervalNanoseconds > 0, flushTask == nil else { return } + flushTask = Task { [flushIntervalNanoseconds, sleeper] in + await sleeper(flushIntervalNanoseconds) + await self.flushAfterDelay() + } + } + + private func flushAfterDelay() async { + flushTask = nil + await flush() + } + + private func trimmed(_ value: String?) -> String? { + guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines), + !value.isEmpty + else { + return nil + } + return value + } + + private static var defaultAppBuild: String? { + let info = Bundle.main.infoDictionary + let version = info?["CFBundleShortVersionString"] as? String + let build = info?["CFBundleVersion"] as? String + return [version, build].compactMap { $0 }.joined(separator: " ") + } +} + +private extension String { + func prefixString(_ maxLength: Int) -> String { + guard count > maxLength else { return self } + let index = self.index(startIndex, offsetBy: maxLength) + return "\(self[.. GeminiLiveTokenResponse + func requestGeminiSpotter(_ request: GeminiSpotterRequest) async throws -> GeminiSpotterResponse } struct WorkerLiveHeartbeatRequest: Equatable { @@ -1005,6 +1423,60 @@ struct WorkerMediaFinalizeRequest: Equatable { } } +struct GeminiLiveTokenResponse: Decodable, Equatable { + let token: String + let expiresAt: String + let newSessionExpiresAt: String + let model: String + let websocketBaseURL: String + let queryParameterName: String + + var credential: GeminiLiveCredential { + GeminiLiveCredential( + token: token, + queryParameterName: queryParameterName.isEmpty ? "access_token" : queryParameterName, + websocketBaseURL: websocketBaseURL.isEmpty ? GeminiConfig.ephemeralTokenWebsocketBaseURL : websocketBaseURL, + model: model.isEmpty ? GeminiConfig.model : model + ) + } +} + +struct GeminiSpotterRequest: Equatable { + let sessionID: String + let stepID: String + let stepTitle: String + let aiPrompt: String + let expectedObjects: [String] + let imageBase64: String + let imageMimeType: String + let capturedAt: String + let critical: Bool + let allowAIComplete: Bool + + var payload: [String: Any] { + [ + "sessionId": sessionID, + "stepId": stepID, + "stepTitle": stepTitle, + "aiPrompt": aiPrompt, + "expectedObjects": expectedObjects, + "imageBase64": imageBase64, + "imageMimeType": imageMimeType, + "capturedAt": capturedAt, + "critical": critical, + "allowAIComplete": allowAIComplete + ] + } +} + +struct GeminiSpotterResponse: Decodable, Equatable { + let matched: Bool + let confidence: Double + let reason: String + let evidenceTimestamp: String + let autoComplete: Bool +} + struct BackendMemoryLink: Identifiable, Decodable, Equatable { let id: String } @@ -1400,6 +1872,57 @@ final class OpsAPIClient: WorkerAdminAPI { ) } + func sendWorkerTelemetryBatch(_ batch: WorkerTelemetryBatch) async throws { + _ = try await performWorkerRequest( + path: "/api/worker/telemetry", + method: "POST", + payload: batch.payload + ) + } + + func requestGeminiLiveToken( + model: String, + sessionID: String? = nil + ) async throws -> GeminiLiveTokenResponse { + var payload: [String: Any] = [ + "model": model, + "responseModalities": ["AUDIO"] + ] + if let sessionID, !sessionID.isEmpty { + payload["sessionId"] = sessionID + } + + let data = try await performWorkerRequest( + path: "/api/worker/gemini/live-token", + method: "POST", + payload: payload + ) + + do { + return try decoder.decode(GeminiLiveTokenResponse.self, from: data) + } catch { + let body = String(data: data, encoding: .utf8) ?? "" + NSLog("[admin-ingest] Failed decoding /api/worker/gemini/live-token -> %@", body) + throw error + } + } + + func requestGeminiSpotter(_ request: GeminiSpotterRequest) async throws -> GeminiSpotterResponse { + let data = try await performWorkerRequest( + path: "/api/worker/gemini/spotter", + method: "POST", + payload: request.payload + ) + + do { + return try decoder.decode(GeminiSpotterResponse.self, from: data) + } catch { + let body = String(data: data, encoding: .utf8) ?? "" + NSLog("[admin-ingest] Failed decoding /api/worker/gemini/spotter -> %@", body) + throw error + } + } + func uploadBinary( to target: WorkerMediaUploadTarget, data: Data, diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift index acf5a52b..ed406c43 100644 --- a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift +++ b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift @@ -22,6 +22,17 @@ class ToolCallRouter { NSLog("[ToolCall] Received: %@ (id: %@) args: %@", callName, callId, String(describing: call.args)) + Task { + await WorkerTelemetry.shared.record( + "tool_call_received", + source: "gemini_live", + stage: "tool", + payload: [ + "tool_name": callName, + "arg_keys": Array(call.args.keys).sorted() + ] + ) + } // Circuit breaker: stop sending tool calls after repeated failures if consecutiveFailures >= maxConsecutiveFailures { @@ -31,6 +42,17 @@ class ToolCallRouter { "Tool execution is temporarily unavailable after \(consecutiveFailures) consecutive failures. " + "Please tell the user you cannot complete this action right now and suggest they check their Video AI Analyst gateway connection." ) + Task { + await WorkerTelemetry.shared.record( + "tool_call_rejected", + source: "gemini_live", + stage: "failed", + payload: [ + "tool_name": callName, + "consecutive_failures": consecutiveFailures + ] + ) + } let response = buildToolResponse(callId: callId, name: callName, result: errorResult) sendResponse(response) return @@ -38,10 +60,19 @@ class ToolCallRouter { let task = Task { @MainActor in let taskDesc = call.args["task"] as? String ?? String(describing: call.args) + let startedAt = Date() let result = await bridge.delegateTask(task: taskDesc, toolName: callName) guard !Task.isCancelled else { NSLog("[ToolCall] Task %@ was cancelled, skipping response", callId) + Task { + await WorkerTelemetry.shared.record( + "tool_call_cancelled", + source: "gemini_live", + stage: "cancelled", + payload: ["tool_name": callName] + ) + } return } @@ -54,6 +85,25 @@ class ToolCallRouter { NSLog("[ToolCall] Result for %@ (id: %@): %@", callName, callId, String(describing: result)) + let resultStage: String + switch result { + case .success: + resultStage = "success" + case .failure: + resultStage = "failed" + } + Task { + await WorkerTelemetry.shared.record( + "tool_call_result", + source: "gemini_live", + stage: resultStage, + durationMs: Date().timeIntervalSince(startedAt) * 1000, + payload: [ + "tool_name": callName, + "consecutive_failures": self.consecutiveFailures + ] + ) + } let response = self.buildToolResponse(callId: callId, name: callName, result: result) sendResponse(response) @@ -71,6 +121,13 @@ class ToolCallRouter { NSLog("[ToolCall] Cancelling in-flight call: %@", id) task.cancel() inFlightTasks.removeValue(forKey: id) + Task { + await WorkerTelemetry.shared.record( + "tool_call_cancellation_requested", + source: "gemini_live", + stage: "cancelled" + ) + } } } bridge.lastToolCallStatus = .cancelled(ids.first ?? "unknown") diff --git a/samples/CameraAccess/CameraAccess/Secrets.swift.example b/samples/CameraAccess/CameraAccess/Secrets.swift.example index 5e80369b..457381e1 100644 --- a/samples/CameraAccess/CameraAccess/Secrets.swift.example +++ b/samples/CameraAccess/CameraAccess/Secrets.swift.example @@ -11,7 +11,7 @@ enum Secrets { static let workerLoginCode = "EMBC-0001" static let workerEmail = "" - // REQUIRED: Get your key at https://aistudio.google.com/apikey + // DEV-ONLY fallback. Production Gemini Live uses /api/worker/gemini/live-token. static let geminiAPIKey = "YOUR_GEMINI_API_KEY" // OPTIONAL: Private OpenClaw gateway config (for agentic tool-calling / memory) @@ -23,7 +23,7 @@ enum Secrets { static let openClawGatewayToken = "YOUR_OPENCLAW_GATEWAY_TOKEN" // Operations API URL for worker bootstrap, sessions, events, and media registration. - static let opsBaseURL = "https://ops.embarcaderolabs.cloud" + static let opsBaseURL = "https://admin.embarcaderolabs.cloud" // Admin API URL for worker live ingest (/api/worker/*). static let adminBaseURL = "https://admin.embarcaderolabs.cloud" diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift index 53f32ceb..4284c993 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift @@ -4,6 +4,12 @@ import Security final class SettingsManager { static let shared = SettingsManager() + private enum RuntimeURL { + static let adminBaseURL = "https://admin.embarcaderolabs.cloud" + static let opsBaseURL = "https://admin.embarcaderolabs.cloud" + static let signalBaseURL = "https://signal.embarcaderolabs.cloud" + } + private let defaults = UserDefaults.standard private let keychain = KeychainStore( service: Bundle.main.bundleIdentifier ?? "com.embarcaderolabs.visionclaw" @@ -71,9 +77,9 @@ final class SettingsManager { get { if let stored = defaults.string(forKey: Key.opsBaseURL.rawValue), Self.isUsableRuntimeURL(stored) { - return stored + return migratedRuntimeURL(stored, for: .opsBaseURL) } - return Secrets.opsBaseURL + return migratedRuntimeURL(Secrets.opsBaseURL, for: .opsBaseURL) } set { defaults.set(newValue, forKey: Key.opsBaseURL.rawValue) } } @@ -82,9 +88,9 @@ final class SettingsManager { get { if let stored = defaults.string(forKey: Key.adminBaseURL.rawValue), Self.isUsableRuntimeURL(stored) { - return stored + return migratedRuntimeURL(stored, for: .adminBaseURL) } - return Secrets.adminBaseURL + return migratedRuntimeURL(Secrets.adminBaseURL, for: .adminBaseURL) } set { defaults.set(newValue, forKey: Key.adminBaseURL.rawValue) } } @@ -93,13 +99,13 @@ final class SettingsManager { get { if let stored = defaults.string(forKey: Key.signalBaseURL.rawValue), Self.isUsableRuntimeURL(stored) { - return stored + return migratedRuntimeURL(stored, for: .signalBaseURL) } if let legacy = defaults.string(forKey: Key.webrtcSignalingURL.rawValue), Self.isUsableRuntimeURL(legacy) { - return Self.normalizeSignalBaseURL(legacy) + return migratedRuntimeURL(Self.normalizeSignalBaseURL(legacy), for: .signalBaseURL) } - return Secrets.signalBaseURL + return migratedRuntimeURL(Secrets.signalBaseURL, for: .signalBaseURL) } set { defaults.set(newValue, forKey: Key.signalBaseURL.rawValue) } } @@ -220,6 +226,38 @@ final class SettingsManager { } } + private func migratedRuntimeURL(_ raw: String, for key: Key) -> String { + let migrated = Self.migrateRuntimeURL(raw, for: key) + if migrated != raw { + defaults.set(migrated, forKey: key.rawValue) + if key == .signalBaseURL { + defaults.set(migrated, forKey: Key.webrtcSignalingURL.rawValue) + } + } + return migrated + } + + private static func migrateRuntimeURL(_ raw: String, for key: Key) -> String { + let normalized = normalizeSignalBaseURL(raw) + let lowercased = normalized.lowercased() + + if lowercased.contains("embarcadero-admin-705096377819.us-central1.run.app") { + if key == .signalBaseURL { + return RuntimeURL.signalBaseURL + } + if key == .opsBaseURL { + return RuntimeURL.opsBaseURL + } + return RuntimeURL.adminBaseURL + } + + if lowercased.contains("embarcadero-signal-705096377819.us-central1.run.app") { + return RuntimeURL.signalBaseURL + } + + return normalized + } + private static func normalizeSignalBaseURL(_ raw: String) -> String { let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return trimmed } diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift index d3045171..eb2dcab4 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift @@ -54,7 +54,7 @@ struct SettingsView: View { Text("Ops Base URL") .font(.caption) .foregroundColor(.secondary) - TextField("https://ops.embarcaderolabs.cloud", text: $opsBaseURL) + TextField("https://admin.embarcaderolabs.cloud", text: $opsBaseURL) .autocapitalization(.none) .disableAutocorrection(true) .keyboardType(.URL) diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index df5babf0..681c6313 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -571,7 +571,11 @@ private enum WorkerLiveLogger { byteSize: Int? = nil, retryCount: Int? = nil, uploadState: String? = nil, - error: String? = nil + error: String? = nil, + durationMs: Double? = nil, + metricValue: Double? = nil, + metricUnit: String? = nil, + telemetry: WorkerTelemetry? = nil ) { let payload: [String: Any] = [ "event": event, @@ -584,7 +588,8 @@ private enum WorkerLiveLogger { "byteSize": byteSize ?? NSNull(), "retryCount": retryCount ?? NSNull(), "uploadState": uploadState ?? NSNull(), - "error": error ?? NSNull() + "error": error ?? NSNull(), + "durationMs": durationMs ?? NSNull() ] guard JSONSerialization.isValidJSONObject(payload), @@ -596,6 +601,49 @@ private enum WorkerLiveLogger { } NSLog("[worker-live] %@", encoded) + + guard let telemetry, let sessionID else { return } + Task { + await telemetry.record( + event, + source: telemetrySource(for: event), + stage: telemetryStage(for: event, uploadState: uploadState), + sessionID: sessionID, + durationMs: durationMs, + metricValue: metricValue, + metricUnit: metricUnit, + payload: payload + ) + } + } + + private static func telemetrySource(for event: String) -> String { + if event.contains("upload") || event.contains("finalize") || event == "retry_scheduled" { + return "media_upload" + } + if event.contains("heartbeat") { + return "ios_app" + } + return "ios_app" + } + + private static func telemetryStage(for event: String, uploadState: String?) -> String { + if event.contains("failure") || uploadState == "failed" { + return "failed" + } + if event.contains("success") || uploadState == "uploaded" { + return "uploaded" + } + if event.contains("target") { + return "target" + } + if event.contains("heartbeat") { + return "heartbeat" + } + if event == "retry_scheduled" { + return "retry" + } + return "point" } } @@ -618,6 +666,7 @@ actor WorkerAdminLiveSessionCoordinator { typealias FileLoader = @Sendable (URL) async -> Data? private let api: WorkerAdminAPI + private let telemetry: WorkerTelemetry? private let heartbeatIntervalNanoseconds: UInt64 private let sleeper: Sleeper private let fileLoader: FileLoader @@ -636,6 +685,7 @@ actor WorkerAdminLiveSessionCoordinator { api: WorkerAdminAPI, sessionID: String? = nil, heartbeatIntervalNanoseconds: UInt64 = 7_000_000_000, + telemetry: WorkerTelemetry? = nil, sleeper: @escaping Sleeper = { nanoseconds in guard nanoseconds > 0 else { return } try? await Task.sleep(nanoseconds: nanoseconds) @@ -647,6 +697,7 @@ actor WorkerAdminLiveSessionCoordinator { } ) { self.api = api + self.telemetry = telemetry self.sessionID = sessionID self.heartbeatIntervalNanoseconds = heartbeatIntervalNanoseconds self.sleeper = sleeper @@ -662,6 +713,22 @@ actor WorkerAdminLiveSessionCoordinator { self.sessionID = sessionID self.currentStepIndex = currentStepIndex self.helpRequested = helpRequested + await telemetry?.configure( + api: api, + sessionID: sessionID, + deviceID: GeminiConfig.deviceID + ) + await telemetry?.record( + "session_start", + source: "ios_app", + stage: "started", + sessionID: sessionID, + payload: [ + "current_step_index": currentStepIndex, + "help_requested": helpRequested, + "room_code_present": Self.trimmed(roomCode) != nil + ] + ) if let roomCode = Self.trimmed(roomCode) { self.roomCode = roomCode } @@ -703,6 +770,20 @@ actor WorkerAdminLiveSessionCoordinator { func enqueueFrameUpload(data: Data) async { queuedFrameData = data + if let sessionID { + await telemetry?.record( + "frame_enqueued", + source: "media_upload", + stage: "queued", + sessionID: sessionID, + metricValue: Double(data.count), + metricUnit: "bytes", + payload: [ + "bytes": data.count, + "latest_frame_only": true + ] + ) + } guard frameUploadTask == nil else { return } frameUploadTask = Task { await self.drainQueuedFrames() @@ -755,6 +836,18 @@ actor WorkerAdminLiveSessionCoordinator { let result = await uploadVideoRecording(from: videoFileURL, source: videoSource) await sendHeartbeat() await onBeforeMarkEnded() + await telemetry?.record( + "session_end_requested", + source: "ios_app", + stage: result.succeeded ? "uploaded" : "failed", + sessionID: sessionID, + payload: [ + "video_upload_state": result.uploadState, + "video_bytes": result.byteSize, + "error": result.errorMessage ?? NSNull() + ] + ) + await telemetry?.flushAndStop() heartbeatTask?.cancel() heartbeatTask = nil @@ -767,6 +860,15 @@ actor WorkerAdminLiveSessionCoordinator { frameUploadTask = nil heartbeatTask?.cancel() heartbeatTask = nil + if let sessionID { + await telemetry?.record( + "session_stop", + source: "ios_app", + stage: "stopped", + sessionID: sessionID + ) + await telemetry?.flushAndStop() + } } private func sendHeartbeat() async { @@ -788,10 +890,12 @@ actor WorkerAdminLiveSessionCoordinator { roomCode: roomCode, bucket: lastFrameBucket, path: lastFramePath, - uploadState: "active" + uploadState: "active", + telemetry: telemetry ) do { + let heartbeatStartedAt = CACurrentMediaTime() try await retry( sessionID: sessionID, roomCode: roomCode, @@ -809,7 +913,9 @@ actor WorkerAdminLiveSessionCoordinator { roomCode: roomCode, bucket: lastFrameBucket, path: lastFramePath, - uploadState: "active" + uploadState: "active", + durationMs: (CACurrentMediaTime() - heartbeatStartedAt) * 1000, + telemetry: telemetry ) } catch { WorkerLiveLogger.log( @@ -819,7 +925,8 @@ actor WorkerAdminLiveSessionCoordinator { bucket: lastFrameBucket, path: lastFramePath, uploadState: "active", - error: error.localizedDescription + error: error.localizedDescription, + telemetry: telemetry ) } } @@ -878,8 +985,10 @@ actor WorkerAdminLiveSessionCoordinator { } let logPrefix = assetType == "frame" ? "frame" : "video" + let assetUploadStartedAt = CACurrentMediaTime() do { + let targetStartedAt = CACurrentMediaTime() let target = try await retry( sessionID: sessionID, roomCode: roomCode, @@ -906,7 +1015,9 @@ actor WorkerAdminLiveSessionCoordinator { bucket: target.bucket, path: target.path, byteSize: byteSize, - uploadState: "pending" + uploadState: "pending", + durationMs: (CACurrentMediaTime() - targetStartedAt) * 1000, + telemetry: telemetry ) guard let data, !data.isEmpty else { @@ -920,7 +1031,9 @@ actor WorkerAdminLiveSessionCoordinator { path: target.path, byteSize: byteSize, uploadState: "failed", - error: missingDataError + error: missingDataError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry ) return await finalizeFailure( logPrefix: logPrefix, @@ -933,6 +1046,7 @@ actor WorkerAdminLiveSessionCoordinator { } do { + let binaryUploadStartedAt = CACurrentMediaTime() try await retry( sessionID: sessionID, roomCode: roomCode, @@ -955,10 +1069,15 @@ actor WorkerAdminLiveSessionCoordinator { bucket: target.bucket, path: target.path, byteSize: byteSize, - uploadState: "pending" + uploadState: "pending", + durationMs: (CACurrentMediaTime() - binaryUploadStartedAt) * 1000, + metricValue: Double(byteSize), + metricUnit: "bytes", + telemetry: telemetry ) do { + let finalizeStartedAt = CACurrentMediaTime() try await retry( sessionID: sessionID, roomCode: roomCode, @@ -991,7 +1110,11 @@ actor WorkerAdminLiveSessionCoordinator { bucket: target.bucket, path: target.path, byteSize: byteSize, - uploadState: "uploaded" + uploadState: "uploaded", + durationMs: (CACurrentMediaTime() - finalizeStartedAt) * 1000, + metricValue: Double(byteSize), + metricUnit: "bytes", + telemetry: telemetry ) return WorkerMediaUploadResult( @@ -1015,7 +1138,9 @@ actor WorkerAdminLiveSessionCoordinator { path: target.path, byteSize: byteSize, uploadState: "uploaded", - error: finalizeError + error: finalizeError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry ) return await finalizeFailure( logPrefix: logPrefix, @@ -1038,7 +1163,9 @@ actor WorkerAdminLiveSessionCoordinator { path: target.path, byteSize: byteSize, uploadState: "failed", - error: uploadError + error: uploadError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry ) return await finalizeFailure( logPrefix: logPrefix, @@ -1057,7 +1184,9 @@ actor WorkerAdminLiveSessionCoordinator { assetType: assetType, byteSize: byteSize, uploadState: "failed", - error: error.localizedDescription + error: error.localizedDescription, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry ) return WorkerMediaUploadResult( @@ -1114,7 +1243,8 @@ actor WorkerAdminLiveSessionCoordinator { path: target.path, byteSize: byteSize, uploadState: "failed", - error: errorMessage + error: errorMessage, + telemetry: telemetry ) } catch { WorkerLiveLogger.log( @@ -1127,7 +1257,8 @@ actor WorkerAdminLiveSessionCoordinator { path: target.path, byteSize: byteSize, uploadState: "failed", - error: error.localizedDescription + error: error.localizedDescription, + telemetry: telemetry ) } @@ -1176,7 +1307,8 @@ actor WorkerAdminLiveSessionCoordinator { byteSize: byteSize, retryCount: retryCount, uploadState: uploadState, - error: error.localizedDescription + error: error.localizedDescription, + telemetry: telemetry ) await sleeper(backoffSchedule[attempt]) @@ -1924,6 +2056,10 @@ class StreamSessionViewModel: ObservableObject { func toggleGeminiAssistant() async { geminiAssistant.streamingMode = streamingMode + geminiAssistant.configureWorkerAdminAPI( + opsAPIClient, + sessionID: activeExecutionSession?.id + ) if geminiAssistant.isGeminiActive { geminiAssistant.stopSession() return @@ -2435,7 +2571,11 @@ class StreamSessionViewModel: ObservableObject { let sessionId = await createOrFallbackSessionID(for: sop) await workerAdminSync?.stop() - workerAdminSync = WorkerAdminLiveSessionCoordinator(api: opsAPIClient) + workerAdminSync = WorkerAdminLiveSessionCoordinator( + api: opsAPIClient, + telemetry: WorkerTelemetry.shared + ) + geminiLiveSpotter.configure(api: opsAPIClient) currentSopSessionId = sessionId activeCaptureSOP = sop isSopAuditRunning = true @@ -3050,7 +3190,8 @@ class StreamSessionViewModel: ObservableObject { let recoverySync = WorkerAdminLiveSessionCoordinator( api: opsAPIClient, sessionID: pendingRecording.sessionID, - heartbeatIntervalNanoseconds: 0 + heartbeatIntervalNanoseconds: 0, + telemetry: WorkerTelemetry.shared ) let result = await recoverySync.uploadVideoRecording( @@ -3232,6 +3373,10 @@ class StreamSessionViewModel: ObservableObject { } private func setChecklistItemCheckedBySpotterID(_ itemID: String) { + setChecklistItemCheckedBySpotterID(itemID, evidence: nil) + } + + private func setChecklistItemCheckedBySpotterID(_ itemID: String, evidence: [String: Any]?) { guard let index = checklistItems.firstIndex(where: { $0.itemID == itemID }) else { return } guard !checklistItems[index].isChecked else { return } @@ -3245,7 +3390,8 @@ class StreamSessionViewModel: ObservableObject { await handleChecklistMutation( item: item, stepIndex: index, - eventType: "step_complete" + eventType: "step_complete", + evidence: evidence ) } @@ -3334,25 +3480,63 @@ class StreamSessionViewModel: ObservableObject { Task { [weak self] in guard let self else { return } - let matchedIDs: [String] + let matches: [GeminiLiveSpotter.SpotterMatch] let requestStartedAt = CACurrentMediaTime() do { - matchedIDs = try await self.geminiLiveSpotter.detectVisibleItemIDs(image: image, items: pendingItems) + matches = try await self.geminiLiveSpotter.detectVisibleItemMatches( + image: image, + items: pendingItems, + sessionID: self.currentSopSessionId + ) } catch { - matchedIDs = [] + matches = [] + await WorkerTelemetry.shared.record( + "gemini_spotter_failed", + source: "gemini_spotter", + stage: "failed", + sessionID: self.currentSopSessionId, + payload: [ + "error": error.localizedDescription, + "target_count": pendingItems.count + ] + ) } let durationMs = (CACurrentMediaTime() - requestStartedAt) * 1000 + let autoCompleteMatches = matches.filter(\.autoComplete) NSLog( - "[Spotter] Active-step review targets=%@ matched=%@ duration=%.1fms", + "[Spotter] Active-step review targets=%@ matched=%@ autoComplete=%@ duration=%.1fms", pendingItems.map(\.id).joined(separator: ","), - matchedIDs.joined(separator: ","), + matches.filter(\.matched).map(\.id).joined(separator: ","), + autoCompleteMatches.map(\.id).joined(separator: ","), durationMs ) + if let firstMatch = matches.first { + await WorkerTelemetry.shared.record( + "gemini_spotter_result", + source: "gemini_spotter", + stage: firstMatch.autoComplete ? "auto_complete" : firstMatch.matched ? "matched" : "not_matched", + sessionID: self.currentSopSessionId, + durationMs: durationMs, + metricValue: firstMatch.confidence, + metricUnit: "confidence", + payload: [ + "step_id": firstMatch.id, + "matched": firstMatch.matched, + "auto_complete": firstMatch.autoComplete, + "reason": firstMatch.reason + ] + ) + } await MainActor.run { - matchedIDs.forEach { - self.captureProofImageIfNeeded(for: $0, from: image) - self.setChecklistItemCheckedBySpotterID($0) + autoCompleteMatches.forEach { + self.captureProofImageIfNeeded(for: $0.id, from: image) + self.setChecklistItemCheckedBySpotterID($0.id, evidence: [ + "ai_confidence": $0.confidence, + "ai_reason": $0.reason, + "evidence_timestamp": $0.evidenceTimestamp, + "auto_complete": $0.autoComplete + ]) } self.isSpotterInferenceInFlight = false } @@ -3569,18 +3753,23 @@ class StreamSessionViewModel: ObservableObject { private func handleChecklistMutation( item: ChecklistItemState, stepIndex: Int, - eventType: String + eventType: String, + evidence: [String: Any]? = nil ) async { let nextIndex = nextIncompleteStepIndex() await workerAdminSync?.updateCurrentStepIndex(nextIndex, sendImmediateHeartbeat: true) + var eventPayload: [String: Any] = [ + "step_index": stepIndex, + "step_name": item.name, + "source": item.completionSource.rawValue, + "checked": item.isChecked + ] + if let evidence { + eventPayload["evidence"] = evidence + } await postExecutionEvent( type: eventType, - payload: [ - "step_index": stepIndex, - "step_name": item.name, - "source": item.completionSource.rawValue, - "checked": item.isChecked - ] + payload: eventPayload ) await patchActiveExecutionSession( ExecutionSessionPatch( @@ -3605,7 +3794,9 @@ class StreamSessionViewModel: ObservableObject { id: currentStep.itemID, name: currentStep.name, aiPrompt: currentStep.aiPrompt, - expectedObjects: currentStep.expectedObjects + expectedObjects: currentStep.expectedObjects, + validation: currentStep.validation, + critical: currentStep.critical ) ] } diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift index 91f19c47..85fdab31 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift @@ -48,6 +48,20 @@ final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { fps, waitDurationMs ) + Task { + await WorkerTelemetry.shared.record( + "webrtc_realtime_forwarder", + source: "webrtc", + stage: "forwarder", + durationMs: waitDurationMs, + metricValue: fps, + metricUnit: "fps", + payload: [ + "fps": fps, + "wait_ms": waitDurationMs + ] + ) + } pixelBufferStatsWindowStart = now pixelBufferForwardCount = 0 } @@ -104,6 +118,14 @@ class WebRTCSessionViewModel: ObservableObject { incomingRemoteVideoEnabled = wantsIncomingRemoteVideo isUsingPhoneFallbackProfile = false stablePhoneSenderWindows = 0 + Task { + await WorkerTelemetry.shared.record( + "webrtc_session_start", + source: "webrtc", + stage: "connecting", + payload: ["capture_mode": captureMode == .iPhone ? "iphone" : "glasses"] + ) + } // Fetch TURN credentials for NAT traversal across networks let iceServers = await WebRTCConfig.fetchIceServers() @@ -132,6 +154,13 @@ class WebRTCSessionViewModel: ObservableObject { wantsIncomingRemoteVideo = true isUsingPhoneFallbackProfile = false stablePhoneSenderWindows = 0 + Task { + await WorkerTelemetry.shared.record( + "webrtc_session_stop", + source: "webrtc", + stage: "disconnected" + ) + } } func toggleMute() { @@ -193,6 +222,13 @@ class WebRTCSessionViewModel: ObservableObject { signaling.onConnected = { [weak self] in Task { @MainActor in + Task { + await WorkerTelemetry.shared.record( + "webrtc_signaling_connected", + source: "webrtc", + stage: "signaling" + ) + } if let code = rejoinCode { NSLog("[WebRTC] Reconnected, rejoining room: %@", code) self?.signalingClient?.rejoinRoom(code: code) @@ -211,6 +247,14 @@ class WebRTCSessionViewModel: ObservableObject { signaling.onDisconnected = { [weak self] reason in Task { @MainActor in guard let self, self.isActive else { return } + Task { + await WorkerTelemetry.shared.record( + "webrtc_signaling_disconnected", + source: "webrtc", + stage: self.savedRoomCode != nil ? "backgrounded" : "failed", + payload: ["reason": reason ?? NSNull()] + ) + } // Don't fully stop -- mark as backgrounded so we can reconnect if self.savedRoomCode != nil { self.connectionState = .backgrounded @@ -302,15 +346,38 @@ class WebRTCSessionViewModel: ObservableObject { savedRoomCode = code connectionState = .waitingForPeer NSLog("[WebRTC] Room created: %@", code) + Task { + await WorkerTelemetry.shared.record( + "webrtc_room_created", + source: "webrtc", + stage: "room", + payload: ["room_code_present": true] + ) + } case .roomRejoined(let code): roomCode = code savedRoomCode = code connectionState = .waitingForPeer NSLog("[WebRTC] Room rejoined: %@", code) + Task { + await WorkerTelemetry.shared.record( + "webrtc_room_rejoined", + source: "webrtc", + stage: "room", + payload: ["room_code_present": true] + ) + } case .peerJoined: NSLog("[WebRTC] Peer joined, creating offer") + Task { + await WorkerTelemetry.shared.record( + "webrtc_peer_joined", + source: "webrtc", + stage: "peer" + ) + } webRTCClient?.createOffer { [weak self] sdp in self?.signalingClient?.send(sdp: sdp) } @@ -332,8 +399,23 @@ class WebRTCSessionViewModel: ObservableObject { case .peerLeft: NSLog("[WebRTC] Peer left") connectionState = .waitingForPeer + Task { + await WorkerTelemetry.shared.record( + "webrtc_peer_left", + source: "webrtc", + stage: "peer" + ) + } case .error(let msg): + Task { + await WorkerTelemetry.shared.record( + "webrtc_signaling_error", + source: "webrtc", + stage: "failed", + payload: ["error": msg] + ) + } // If rejoin fails (room expired), fall back to creating a new room if savedRoomCode != nil && msg == "Room not found" { NSLog("[WebRTC] Rejoin failed (room expired), creating new room") @@ -355,10 +437,31 @@ class WebRTCSessionViewModel: ObservableObject { case .connected, .completed: connectionState = .connected NSLog("[WebRTC] Peer connected") + Task { + await WorkerTelemetry.shared.record( + "webrtc_ice_connected", + source: "webrtc", + stage: "connected" + ) + } case .disconnected: connectionState = .waitingForPeer + Task { + await WorkerTelemetry.shared.record( + "webrtc_ice_disconnected", + source: "webrtc", + stage: "disconnected" + ) + } case .failed: connectionState = .error("Connection failed") + Task { + await WorkerTelemetry.shared.record( + "webrtc_ice_failed", + source: "webrtc", + stage: "failed" + ) + } case .closed: connectionState = .disconnected default: @@ -386,6 +489,21 @@ class WebRTCSessionViewModel: ObservableObject { guard currentCaptureMode == .iPhone else { return } let enqueueMs = stats.lastEnqueueDurationMs ?? 0 + Task { + await WorkerTelemetry.shared.record( + "webrtc_sender_stats", + source: "webrtc", + stage: isUsingPhoneFallbackProfile ? "fallback" : "sender", + metricValue: stats.windowFramesPerSecond, + metricUnit: "fps", + payload: [ + "sender_fps": stats.windowFramesPerSecond, + "dropped_frames": stats.windowDroppedFrames, + "enqueue_ms": enqueueMs, + "fallback_profile": isUsingPhoneFallbackProfile + ] + ) + } let isUnderPressure = enqueueMs > 20 || stats.windowDroppedFrames >= 3 || stats.windowFramesPerSecond < 14 @@ -395,6 +513,18 @@ class WebRTCSessionViewModel: ObservableObject { guard !isUsingPhoneFallbackProfile else { return } isUsingPhoneFallbackProfile = true webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneFallbackProfile) + Task { + await WorkerTelemetry.shared.record( + "webrtc_profile_downgrade", + source: "webrtc", + stage: "fallback", + payload: [ + "sender_fps": stats.windowFramesPerSecond, + "dropped_frames": stats.windowDroppedFrames, + "enqueue_ms": enqueueMs + ] + ) + } NSLog( "[WebRTC] Phone sender downgraded to fallback profile (fps=%.1f dropped=%lld enqueue=%@ms)", stats.windowFramesPerSecond, @@ -411,6 +541,14 @@ class WebRTCSessionViewModel: ObservableObject { stablePhoneSenderWindows = 0 isUsingPhoneFallbackProfile = false webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneProfile) + Task { + await WorkerTelemetry.shared.record( + "webrtc_profile_restore", + source: "webrtc", + stage: "sender", + payload: ["stable_windows": stablePhoneSenderWindows] + ) + } NSLog("[WebRTC] Phone sender restored to default support profile") } } diff --git a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift index be7278f4..dbf27e20 100644 --- a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift +++ b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift @@ -36,6 +36,15 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { self?.configureSession() self?.captureSession.startRunning() self?.isRunning = true + let sessionRunning = self?.captureSession.isRunning == true + Task { + await WorkerTelemetry.shared.record( + "iphone_camera_start", + source: "ios_app", + stage: "camera", + payload: ["session_running": sessionRunning] + ) + } NSLog("[iPhoneCamera] captureSession.startRunning() complete (isRunning=%@, sessionRunning=%@)", self?.isRunning == true ? "true" : "false", self?.captureSession.isRunning == true ? "true" : "false") @@ -74,6 +83,18 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { self.currentRecordingURL = fileURL self.movieOutput.startRecording(to: fileURL, recordingDelegate: self) + Task { + await WorkerTelemetry.shared.record( + "iphone_recording_start", + source: "ios_app", + stage: "recording", + sessionID: sessionID, + payload: [ + "file_extension": fileURL.pathExtension, + "disk_free_checked": true + ] + ) + } NSLog("[iPhoneCamera] Started recording SOP video: %@", fileURL.path) } } @@ -113,6 +134,13 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { NSLog("[iPhoneCamera] stop() requested") self?.captureSession.stopRunning() self?.isRunning = false + Task { + await WorkerTelemetry.shared.record( + "iphone_camera_stop", + source: "ios_app", + stage: "camera" + ) + } NSLog("[iPhoneCamera] captureSession.stopRunning() complete") } } @@ -247,8 +275,34 @@ extension IPhoneCameraManager: AVCaptureFileOutputRecordingDelegate { if let attributes = try? FileManager.default.attributesOfItem(atPath: outputFileURL.path), let fileSize = attributes[.size] as? NSNumber { NSLog("[iPhoneCamera] Recorded file size: %@ bytes", fileSize) + let stage = error == nil ? "recorded" : "failed" + let errorMessage = error?.localizedDescription + let byteCount = fileSize.intValue + let metricValue = fileSize.doubleValue + Task { + await WorkerTelemetry.shared.record( + "iphone_recording_finish", + source: "ios_app", + stage: stage, + metricValue: metricValue, + metricUnit: "bytes", + payload: [ + "bytes": byteCount, + "error": errorMessage ?? NSNull() + ] + ) + } } else { NSLog("[iPhoneCamera] Could not read recorded file size at %@", outputFileURL.path) + let errorMessage = error?.localizedDescription + Task { + await WorkerTelemetry.shared.record( + "iphone_recording_finish", + source: "ios_app", + stage: "unknown_size", + payload: ["error": errorMessage ?? NSNull()] + ) + } } let completion = recordingCompletion @@ -292,6 +346,20 @@ extension IPhoneCameraManager { analysisFPS, movieOutput.isRecording ? "true" : "false" ) + Task { + await WorkerTelemetry.shared.record( + "iphone_camera_fps", + source: "ios_app", + stage: "camera", + metricValue: previewFPS, + metricUnit: "fps", + payload: [ + "preview_fps": previewFPS, + "analysis_fps": analysisFPS, + "recording": movieOutput.isRecording + ] + ) + } statsWindowStart = now sampleFrameCount = 0 diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index 692bc3b5..2360071e 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -160,6 +160,95 @@ class ViewModelIntegrationTests: XCTestCase { } #endif +final class BackendBootstrapDecodingTests: XCTestCase { + func testBootstrapPayloadAcceptsCloudSQLNumericStringsAndNumericStepDurations() throws { + let json = """ + { + "worker": { + "id": "11111111-1111-1111-1111-111111111111", + "login_code": "EMBC-0001", + "display_name": "Lucas Pereira", + "role": "Kitchen Staff", + "active": true + }, + "device": { + "id": "22222222-2222-4222-8222-222222222222", + "worker_id": "11111111-1111-1111-1111-111111111111", + "platform": "ios", + "device_label": "iPhone" + }, + "queue": [ + { + "shift_assignment_id": "33333333-3333-4333-8333-333333333333", + "worker_id": "11111111-1111-1111-1111-111111111111", + "package_id": "44444444-4444-4444-8444-444444444444", + "package_title": "Meal Prep", + "package_version": "2", + "package_run_id": "55555555-5555-4555-8555-555555555555", + "sop_id": "66666666-6666-4666-8666-666666666666", + "sop_title": "Burger Assembly", + "sop_version": "1", + "sort_order": "1", + "required": "true", + "active": true, + "source_type": "package", + "steps": [ + { + "id": "step-1", + "title": "Prepare the bun", + "duration": 15, + "validation": "visual", + "allowManualComplete": false + }, + { + "index": 1, + "title": "Record temperature log", + "instruction": "Read the temperature logger display.", + "requires_photo": false + } + ] + } + ], + "assigned_packages": [], + "worker_session_token": "worker-token", + "worker_session_expires_at": "2026-05-31T14:43:20.929Z" + } + """ + + let payload = try JSONDecoder().decode(BootstrapPayload.self, from: Data(json.utf8)) + + XCTAssertEqual(payload.worker.loginCode, "EMBC-0001") + XCTAssertEqual(payload.queue.first?.sortOrder, 1) + XCTAssertEqual(payload.queue.first?.packageVersion, 2) + XCTAssertEqual(payload.queue.first?.steps.first?.duration, "15") + XCTAssertEqual(payload.queue.first?.steps.last?.description, "Read the temperature logger display.") + XCTAssertEqual(payload.workerSessionToken, "worker-token") + } + + func testExecutionEventDecodesCloudSQLUuidResponse() throws { + let json = """ + { + "id": "bb81da90-9e5e-491e-ba02-0c91730b35b9", + "session_id": "0089c81d-e79f-407f-a235-a1b84a535c9c", + "event_type": "step_complete", + "payload": { + "source": "vision", + "checked": true, + "step_index": 0 + }, + "created_at": "2026-05-31T02:57:53.933Z", + "workspace_id": "00000000-0000-4000-8000-000000000001" + } + """ + + let event = try JSONDecoder().decode(BackendExecutionEvent.self, from: Data(json.utf8)) + + XCTAssertEqual(event.id, "bb81da90-9e5e-491e-ba02-0c91730b35b9") + XCTAssertEqual(event.sessionID, "0089c81d-e79f-407f-a235-a1b84a535c9c") + XCTAssertEqual(event.eventType, "step_complete") + } +} + private final class RequestCaptureURLProtocol: URLProtocol { static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))? @@ -204,6 +293,9 @@ private struct WorkerAdminAPISnapshot { let uploadTargetRequests: [WorkerUploadTargetRequestCapture] let uploadCalls: [(assetID: String, byteSize: Int, contentType: String)] let finalizeRequests: [WorkerMediaFinalizeRequest] + let telemetryBatches: [WorkerTelemetryBatch] + let liveTokenRequests: [(model: String, sessionID: String?)] + let spotterRequests: [GeminiSpotterRequest] } private func requestBodyData(from request: URLRequest) -> Data? { @@ -241,13 +333,21 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { var uploadTargetErrors: [Error] = [] var uploadErrors: [Error] = [] var finalizeErrors: [Error] = [] + var telemetryErrors: [Error] = [] + var liveTokenErrors: [Error] = [] + var spotterErrors: [Error] = [] var uploadTargetResponses: [WorkerMediaUploadTarget] = [] + var liveTokenResponses: [GeminiLiveTokenResponse] = [] + var spotterResponses: [GeminiSpotterResponse] = [] var onFinalizeAttempt: ((WorkerMediaFinalizeRequest) -> Void)? private var recordedHeartbeats: [WorkerLiveHeartbeatRequest] = [] private var recordedUploadTargetRequests: [WorkerUploadTargetRequestCapture] = [] private var recordedUploadCalls: [(assetID: String, byteSize: Int, contentType: String)] = [] private var recordedFinalizeRequests: [WorkerMediaFinalizeRequest] = [] + private var recordedTelemetryBatches: [WorkerTelemetryBatch] = [] + private var recordedLiveTokenRequests: [(model: String, sessionID: String?)] = [] + private var recordedSpotterRequests: [GeminiSpotterRequest] = [] func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws { let queuedError = lock.withLock { () -> Error? in @@ -330,6 +430,65 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { } } + func sendWorkerTelemetryBatch(_ batch: WorkerTelemetryBatch) async throws { + let queuedError = lock.withLock { () -> Error? in + recordedTelemetryBatches.append(batch) + return telemetryErrors.isEmpty ? nil : telemetryErrors.removeFirst() + } + + if let queuedError { + throw queuedError + } + } + + func requestGeminiLiveToken( + model: String, + sessionID: String? + ) async throws -> GeminiLiveTokenResponse { + let (queuedError, response) = lock.withLock { () -> (Error?, GeminiLiveTokenResponse) in + recordedLiveTokenRequests.append((model: model, sessionID: sessionID)) + let queuedError = liveTokenErrors.isEmpty ? nil : liveTokenErrors.removeFirst() + let response = liveTokenResponses.isEmpty + ? GeminiLiveTokenResponse( + token: "ephemeral-token", + expiresAt: "2026-05-30T19:00:00.000Z", + newSessionExpiresAt: "2026-05-30T18:31:00.000Z", + model: model, + websocketBaseURL: GeminiConfig.ephemeralTokenWebsocketBaseURL, + queryParameterName: "access_token" + ) + : liveTokenResponses.removeFirst() + return (queuedError, response) + } + + if let queuedError { + throw queuedError + } + return response + } + + func requestGeminiSpotter(_ request: GeminiSpotterRequest) async throws -> GeminiSpotterResponse { + let (queuedError, response) = lock.withLock { () -> (Error?, GeminiSpotterResponse) in + recordedSpotterRequests.append(request) + let queuedError = spotterErrors.isEmpty ? nil : spotterErrors.removeFirst() + let response = spotterResponses.isEmpty + ? GeminiSpotterResponse( + matched: true, + confidence: 0.93, + reason: "Clear visual evidence.", + evidenceTimestamp: request.capturedAt, + autoComplete: true + ) + : spotterResponses.removeFirst() + return (queuedError, response) + } + + if let queuedError { + throw queuedError + } + return response + } + func snapshot() -> WorkerAdminAPISnapshot { lock.lock() defer { lock.unlock() } @@ -337,7 +496,10 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { heartbeats: recordedHeartbeats, uploadTargetRequests: recordedUploadTargetRequests, uploadCalls: recordedUploadCalls, - finalizeRequests: recordedFinalizeRequests + finalizeRequests: recordedFinalizeRequests, + telemetryBatches: recordedTelemetryBatches, + liveTokenRequests: recordedLiveTokenRequests, + spotterRequests: recordedSpotterRequests ) } } @@ -599,6 +761,81 @@ final class WorkerAdminLiveSessionCoordinatorTests: XCTestCase { XCTAssertTrue(result.succeeded) XCTAssertEqual(snapshot.uploadTargetRequests.last?.source, "phone-recording") } + + func testTelemetryBatchFlushesSanitizedPayload() async throws { + let api = WorkerAdminAPIMock() + let telemetry = WorkerTelemetry( + api: api, + sessionID: "11111111-1111-1111-1111-111111111111", + deviceID: "iphone-test", + appBuild: "test-build", + flushIntervalNanoseconds: 0, + maxBatchSize: 20 + ) + + await telemetry.record( + "video_upload_success", + source: "media_upload", + stage: "uploaded", + durationMs: 42, + metricValue: 1024, + metricUnit: "bytes", + payload: [ + "token": "secret-token", + "uploadUrl": "https://signed.example/upload?token=secret", + "image_data": "data:image/jpeg;base64,abcd", + "asset_type": "video" + ] + ) + await telemetry.flush() + + let snapshot = api.snapshot() + let batch = try XCTUnwrap(snapshot.telemetryBatches.first) + XCTAssertEqual(batch.sessionID, "11111111-1111-1111-1111-111111111111") + XCTAssertEqual(batch.deviceID, "iphone-test") + XCTAssertEqual(batch.appBuild, "test-build") + XCTAssertEqual(batch.events.first?.name, "video_upload_success") + XCTAssertEqual(batch.events.first?.payload["token"] as? String, "[redacted]") + XCTAssertEqual(batch.events.first?.payload["uploadUrl"] as? String, "[redacted-url]") + XCTAssertEqual(batch.events.first?.payload["image_data"] as? String, "[redacted-raw-payload]") + XCTAssertEqual(batch.events.first?.payload["asset_type"] as? String, "video") + } + + func testTelemetryFailureDoesNotBlockCoordinatorUpload() async throws { + let api = WorkerAdminAPIMock() + api.telemetryErrors = [URLError(.timedOut)] + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-telemetry", + bucket: "execution-videos", + path: "sessions/session-telemetry/recording.mp4", + uploadURL: "https://upload.example/video-telemetry" + ) + ] + let telemetry = WorkerTelemetry( + api: api, + sessionID: "22222222-2222-2222-2222-222222222222", + flushIntervalNanoseconds: 0, + maxBatchSize: 100 + ) + let fileURL = try makeTempFile(data: Data([0x41, 0x42, 0x43]), suffix: "telemetry") + defer { try? FileManager.default.removeItem(at: fileURL) } + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "22222222-2222-2222-2222-222222222222", + heartbeatIntervalNanoseconds: 0, + telemetry: telemetry, + sleeper: { _ in } + ) + + let result = await coordinator.uploadVideoRecording(from: fileURL) + await telemetry.flush() + + let snapshot = api.snapshot() + XCTAssertTrue(result.succeeded) + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "uploaded") + XCTAssertEqual(snapshot.telemetryBatches.count, 1) + } } @MainActor @@ -732,6 +969,16 @@ final class OpsAPIClientRoutingTests: XCTestCase { #"{"assetId":"video-asset-1","bucket":"execution-videos","path":"sessions/session-1/recording.mp4","uploadUrl":"https://upload.example/video-1"}"# .utf8 ) + case "/api/worker/gemini/live-token": + body = Data( + #"{"token":"ephemeral-token","expiresAt":"2026-05-30T19:00:00.000Z","newSessionExpiresAt":"2026-05-30T18:31:00.000Z","model":"gemini-live-2.5-flash-native-audio","websocketBaseURL":"wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained","queryParameterName":"access_token"}"# + .utf8 + ) + case "/api/worker/gemini/spotter": + body = Data( + #"{"matched":true,"confidence":0.93,"reason":"Clear visual evidence.","evidenceTimestamp":"2026-05-30T18:31:00.000Z","autoComplete":true}"# + .utf8 + ) default: body = Data("{}".utf8) } @@ -773,15 +1020,46 @@ final class OpsAPIClientRoutingTests: XCTestCase { source: "phone-recording" ) + _ = try await client.requestGeminiLiveToken( + model: "models/gemini-live-2.5-flash-native-audio", + sessionID: "11111111-1111-1111-1111-111111111111" + ) + + _ = try await client.requestGeminiSpotter( + GeminiSpotterRequest( + sessionID: "11111111-1111-1111-1111-111111111111", + stepID: "step-1", + stepTitle: "Check seal", + aiPrompt: "Confirm the seal is visible.", + expectedObjects: ["seal"], + imageBase64: "ZmFrZQ==", + imageMimeType: "image/jpeg", + capturedAt: "2026-05-30T18:31:00.000Z", + critical: false, + allowAIComplete: true + ) + ) + let requests = lock.withLock { capturedRequests } - XCTAssertEqual(requests.map { $0.url?.host }, ["ops.example.test", "admin.example.test", "admin.example.test"]) - XCTAssertEqual(requests.map { $0.url?.path }, ["/health", "/api/worker/live/heartbeat", "/api/worker/media/upload-target"]) + XCTAssertEqual(requests.map { $0.url?.host }, ["ops.example.test", "admin.example.test", "admin.example.test", "admin.example.test", "admin.example.test"]) + XCTAssertEqual(requests.map { $0.url?.path }, ["/health", "/api/worker/live/heartbeat", "/api/worker/media/upload-target", "/api/worker/gemini/live-token", "/api/worker/gemini/spotter"]) XCTAssertNil(requests.first?.value(forHTTPHeaderField: "Authorization")) XCTAssertEqual(requests.dropFirst().first?.value(forHTTPHeaderField: "Authorization"), "Bearer worker-bearer-token") let uploadPayload = try XCTUnwrap( - JSONSerialization.jsonObject(with: try XCTUnwrap(requests.last.flatMap(requestBodyData(from:)))) as? [String: Any] + JSONSerialization.jsonObject(with: try XCTUnwrap(requestBodyData(from: requests[2]))) as? [String: Any] ) XCTAssertEqual(uploadPayload["source"] as? String, "phone-recording") + let tokenPayload = try XCTUnwrap( + JSONSerialization.jsonObject(with: try XCTUnwrap(requestBodyData(from: requests[3]))) as? [String: Any] + ) + XCTAssertEqual(tokenPayload["model"] as? String, "models/gemini-live-2.5-flash-native-audio") + XCTAssertEqual(tokenPayload["sessionId"] as? String, "11111111-1111-1111-1111-111111111111") + let spotterPayload = try XCTUnwrap( + JSONSerialization.jsonObject(with: try XCTUnwrap(requests.last.flatMap(requestBodyData(from:)))) as? [String: Any] + ) + XCTAssertEqual(spotterPayload["stepId"] as? String, "step-1") + XCTAssertEqual(spotterPayload["allowAIComplete"] as? Bool, true) + XCTAssertEqual(spotterPayload["imageBase64"] as? String, "ZmFrZQ==") } func testWorkerRouteErrorsMentionAdminIngest() async throws { @@ -827,4 +1105,67 @@ final class OpsAPIClientRoutingTests: XCTestCase { XCTAssertTrue(error.localizedDescription.contains("/api/worker/media/upload-target")) } } + + func testTelemetryRouteUsesAdminBaseURL() async throws { + let settings = SettingsManager.shared + let originalAdminBaseURL = settings.adminBaseURL + let originalBearerToken = settings.openClawBearerToken + + settings.adminBaseURL = "http://admin.example.test:3001" + settings.openClawBearerToken = "worker-bearer-token" + defer { + settings.adminBaseURL = originalAdminBaseURL + settings.openClawBearerToken = originalBearerToken + } + + let lock = NSLock() + var capturedRequests: [URLRequest] = [] + RequestCaptureURLProtocol.handler = { request in + lock.withLock { + capturedRequests.append(request) + } + + let response = HTTPURLResponse( + url: try XCTUnwrap(request.url), + statusCode: 202, + httpVersion: nil, + headerFields: ["Content-Type": "application/json"] + ) + return (try XCTUnwrap(response), Data(#"{"accepted":1,"inserted":1}"#.utf8)) + } + + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [RequestCaptureURLProtocol.self] + let client = OpsAPIClient(session: URLSession(configuration: configuration)) + + try await client.sendWorkerTelemetryBatch( + WorkerTelemetryBatch( + sessionID: "11111111-1111-1111-1111-111111111111", + deviceID: "iphone-test", + workerID: nil, + platform: "ios", + appBuild: "test-build", + events: [ + WorkerTelemetryEvent( + name: "heartbeat_result", + source: "ios_app", + stage: "heartbeat", + payload: ["status": "active"] + ) + ] + ) + ) + + let request = try XCTUnwrap(lock.withLock { capturedRequests.first }) + XCTAssertEqual(request.url?.host, "admin.example.test") + XCTAssertEqual(request.url?.path, "/api/worker/telemetry") + XCTAssertEqual(request.value(forHTTPHeaderField: "Authorization"), "Bearer worker-bearer-token") + let payload = try XCTUnwrap( + JSONSerialization.jsonObject(with: try XCTUnwrap(requestBodyData(from: request))) as? [String: Any] + ) + XCTAssertEqual(payload["sessionId"] as? String, "11111111-1111-1111-1111-111111111111") + XCTAssertEqual(payload["deviceId"] as? String, "iphone-test") + XCTAssertEqual(payload["platform"] as? String, "ios") + XCTAssertEqual((payload["events"] as? [[String: Any]])?.first?["name"] as? String, "heartbeat_result") + } } From aac20027f6afd049e6c88b482d39f9017774fa38 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 31 May 2026 10:52:38 -0500 Subject: [PATCH 05/11] Make VisionClaw assignment driven --- .../ViewModels/StreamSessionViewModel.swift | 83 ++++ .../CameraAccess/Views/DesignSystem.swift | 11 + .../CameraAccess/Views/HomeView.swift | 449 ++++++++++-------- .../CameraAccess/Views/MainAppView.swift | 15 +- .../Views/StreamSessionView.swift | 4 +- .../CameraAccessTests/CameraAccessTests.swift | 50 ++ 6 files changed, 399 insertions(+), 213 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 681c6313..1c95e715 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -1623,6 +1623,34 @@ class StreamSessionViewModel: ObservableObject { } } + var currentAssignmentSubtitle: String { + guard let sop = currentAssignedSOP else { return "No assignment loaded" } + let package = sop.packageTitle ?? activePackageTitle + let stepCount = sop.steps.count + return "\(package) · \(stepCount) STEP\(stepCount == 1 ? "" : "S")" + } + + var assignmentQueueSummary: String { + let count = pendingTaskSOPs.count + if count == 0 { + return "Queue clear" + } + if count == 1 { + return "1 assignment ready" + } + return "\(count) assignments ready" + } + + var cameraReadinessLabel: String { + hasActiveDevice ? "Meta camera ready" : "iPhone camera ready" + } + + var cameraReadinessDetail: String { + hasActiveDevice + ? "Glasses will be used for the next execution." + : "Using iPhone until glasses are available." + } + // Photo capture properties @Published var capturedPhoto: UIImage? @Published var showPhotoPreview: Bool = false @@ -1647,6 +1675,7 @@ class StreamSessionViewModel: ObservableObject { private var successToastTask: Task? private var hasLoadedWorkerContext = false private var hasEnteredWorkerHome = false + private var autoPresentedAssignmentKeys: Set = [] private var isUsingLocalSessionFallback = false private var roomCodeCancellable: AnyCancellable? private var connectionStateCancellable: AnyCancellable? @@ -1747,6 +1776,7 @@ class StreamSessionViewModel: ObservableObject { deviceMonitorTask = Task { @MainActor in for await device in deviceSelector.activeDeviceStream() { self.hasActiveDevice = device != nil + self.reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: true) } } @@ -2110,6 +2140,23 @@ class StreamSessionViewModel: ObservableObject { } } + func startCurrentAssignmentFromHome() { + reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) + guard let sop = currentAssignedSOP else { + if operationsSyncError == nil { + setCriticalOperationsSyncIssue( + phase: "assignment", + message: "No active SOP assignment is available for this worker." + ) + } + return + } + + selectedSOP = sop + activeCaptureSOP = sop + shouldDismissCapture = false + } + func presentCapture(for sop: SOPTemplate) { selectedSOP = sop activeCaptureSOP = sop @@ -2120,16 +2167,19 @@ class StreamSessionViewModel: ObservableObject { if !hasLoadedWorkerContext { await loadWorkerContextIfNeeded() } + reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) guard !hasEnteredWorkerHome else { return } hasEnteredWorkerHome = true await resetDemoShiftForHomeIfNeeded(reloadAssignments: false) + autoPresentCurrentAssignmentIfNeeded() } func handleWorkerAppBecameActive() async { guard hasEnteredWorkerHome else { return } guard !isSopAuditRunning, activeCaptureSOP == nil else { return } await resetDemoShiftForHomeIfNeeded(reloadAssignments: true) + autoPresentCurrentAssignmentIfNeeded() } func restoreActiveCaptureIfNeeded() { @@ -2161,6 +2211,15 @@ class StreamSessionViewModel: ObservableObject { await refreshWorkerContext() } + private func autoPresentCurrentAssignmentIfNeeded() { + guard activeCaptureSOP == nil, !isSopAuditRunning else { return } + guard let sop = currentAssignedSOP else { return } + let key = pendingTaskKey(for: sop) + guard !autoPresentedAssignmentKeys.contains(key) else { return } + autoPresentedAssignmentKeys.insert(key) + startCurrentAssignmentFromHome() + } + func refreshWorkerContext() async { guard GeminiConfig.isOpsConfigured else { setCriticalOperationsSyncIssue( @@ -2206,6 +2265,7 @@ class StreamSessionViewModel: ObservableObject { if selectedSOP == nil || !pendingTaskSOPs.contains(selectedSOP!) { selectedSOP = pendingTaskSOPs.first } + reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) hasLoadedWorkerContext = true if availableSOPs.isEmpty { @@ -3544,6 +3604,7 @@ class StreamSessionViewModel: ObservableObject { } private func startPreferredCamera() async { + reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) switch preferredCaptureMode { case .iPhone: await handleStartIPhone() @@ -3556,6 +3617,28 @@ class StreamSessionViewModel: ObservableObject { } } + private func reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: Bool) { + let nextMode: StreamingMode = hasActiveDevice ? .glasses : .iPhone + guard preferredCaptureMode != nextMode else { return } + + preferredCaptureMode = nextMode + + if hasActiveDevice { + if isSopAuditRunning && streamingMode == .iPhone { + sopAuditStatusMessage = "Meta camera connected. The next assignment will use glasses." + } else { + sopAuditStatusMessage = "Meta camera ready." + } + } else if streamingMode == .glasses { + sopAuditStatusMessage = "Meta camera disconnected." + } + + guard allowTransportSwitch, isStreaming, !isSopAuditRunning else { return } + Task { @MainActor [weak self] in + await self?.switchToPreferredCaptureModeIfNeeded() + } + } + private func stopCurrentCameraTransportOnly() async { switch streamingMode { case .iPhone: diff --git a/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift b/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift index d7adba1e..f62ea750 100644 --- a/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift +++ b/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift @@ -29,6 +29,17 @@ extension Color { enum DesignSystem { enum colors { + static let adminBackground = Color(hex: "#F9FAF7") + static let adminSurface = Color(hex: "#FFFFFF") + static let adminInk = Color(hex: "#2C2C2C") + static let adminMuted = Color(hex: "#646464") + static let adminSubtle = Color(hex: "#B4B8B4") + static let adminStroke = Color(hex: "#DEE2DE") + static let brandOrange = Color(hex: "#DD6B2E") + static let brandOrangeDeep = Color(hex: "#B85222") + static let successGreen = Color(hex: "#1F9D6F") + static let adminWarning = Color(hex: "#D8A838") + static let deepNavy = Color(hex: "#080D18") static let surface = Color(hex: "#111827") static let surfaceRaised = Color(hex: "#1F2937") diff --git a/samples/CameraAccess/CameraAccess/Views/HomeView.swift b/samples/CameraAccess/CameraAccess/Views/HomeView.swift index 72ab6fbc..8e96775c 100644 --- a/samples/CameraAccess/CameraAccess/Views/HomeView.swift +++ b/samples/CameraAccess/CameraAccess/Views/HomeView.swift @@ -1,3 +1,4 @@ +import MWDATCore import SwiftUI private enum WorkerHomeSheet: String, Identifiable { @@ -10,26 +11,30 @@ private enum WorkerHomeSheet: String, Identifiable { struct HomeView: View { @Environment(\.scenePhase) private var scenePhase @ObservedObject var viewModel: StreamSessionViewModel + @ObservedObject var wearablesViewModel: WearablesViewModel @State private var activeSheet: WorkerHomeSheet? var body: some View { - VStack(alignment: .leading, spacing: 14) { - header - actionControls - captureModeControls - pendingTasksSection + ScrollView { + VStack(alignment: .leading, spacing: 14) { + header + statusStrip + assignmentPanel + cameraPanel + notices + } + .padding(16) } - .padding(14) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) + .background(DesignSystem.colors.adminBackground.ignoresSafeArea()) .toolbar(.hidden, for: .navigationBar) .sheet(item: $activeSheet) { sheet in switch sheet { case .history: NavigationStack { HistoryView(viewModel: viewModel) - .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) - .navigationTitle("HISTORY") + .background(DesignSystem.colors.adminBackground.ignoresSafeArea()) + .navigationTitle("History") .navigationBarTitleDisplayMode(.inline) } case .settings: @@ -53,244 +58,294 @@ struct HomeView: View { } private var header: some View { - HStack(alignment: .top, spacing: 12) { + HStack(alignment: .center, spacing: 12) { VStack(alignment: .leading, spacing: 6) { - Text(viewModel.workerDisplayName.uppercased()) - .font(DesignSystem.fonts.mono(size: 24, weight: .semibold)) - .foregroundColor(DesignSystem.colors.white) - - Text("\(viewModel.workerRoleText) · \(viewModel.pendingShiftLabel)") + Text("EMBARCADERO") .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) - .foregroundColor(DesignSystem.colors.blueGrey) - - Text(viewModel.pendingTaskHeaderSummary) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background(DesignSystem.colors.brandOrange) + .clipShape(RoundedRectangle(cornerRadius: 8)) + + Text(viewModel.workerDisplayName) + .font(DesignSystem.fonts.body(size: 28, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + .lineLimit(1) + .minimumScaleFactor(0.72) + + Text(viewModel.workerRoleText) .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.vibrantTeal) + .foregroundColor(DesignSystem.colors.adminMuted) } - Spacer() + Spacer(minLength: 12) - Button { - activeSheet = .settings - } label: { - Image(systemName: "slider.horizontal.3") - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(DesignSystem.colors.vibrantTeal) - .frame(width: 42, height: 42) - .background(DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) - } - .buttonStyle(.plain) - } - } + HStack(spacing: 8) { + iconButton(systemName: "arrow.clockwise") { + Task { await viewModel.refreshWorkerContext() } + } + .disabled(viewModel.isSyncingOperations) + .opacity(viewModel.isSyncingOperations ? 0.55 : 1) - private var actionControls: some View { - HStack(spacing: 0) { - actionButton(title: "SYNC TASKS") { - Task { - await viewModel.refreshWorkerContext() + iconButton(systemName: "clock.arrow.circlepath") { + activeSheet = .history } - } - actionButton(title: "HISTORY") { - activeSheet = .history + iconButton(systemName: "slider.horizontal.3") { + activeSheet = .settings + } } } - .frame(height: 44) - .background(DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) } - private var captureModeControls: some View { - HStack(spacing: 0) { - modeButton(title: "IPHONE CAMERA", mode: .iPhone, enabled: true) - modeButton(title: "META CAMERA", mode: .glasses, enabled: viewModel.hasActiveDevice) + private var statusStrip: some View { + HStack(spacing: 8) { + statusPill(title: viewModel.pendingShiftLabel, color: DesignSystem.colors.adminMuted) + statusPill(title: viewModel.assignmentQueueSummary, color: DesignSystem.colors.brandOrange) } - .frame(height: 44) - .background(DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) } - private var pendingTasksSection: some View { - VStack(alignment: .leading, spacing: 0) { - Text("PENDING TASKS") - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.blueGrey) - .padding(.bottom, 8) - - if viewModel.isSyncingOperations { - syncBanner - .padding(.bottom, 10) - } - - if let message = viewModel.operationsSyncError, - !message.isEmpty { - noticeBanner(title: "OPS NOTICE", body: message) - .padding(.bottom, viewModel.pendingTaskSOPs.isEmpty ? 0 : 10) + private var assignmentPanel: some View { + VStack(alignment: .leading, spacing: 14) { + HStack(spacing: 8) { + Text("CURRENT ASSIGNMENT") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.brandOrange) + Spacer() + Text(viewModel.currentPackageProgressText) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminMuted) + .lineLimit(1) + .minimumScaleFactor(0.75) } - if let warning = viewModel.operationsSyncWarning, - !warning.isEmpty { - statusBanner( - title: "SYNC WARNING", - body: warning, - accent: DesignSystem.colors.warningAmber - ) - .padding(.bottom, viewModel.pendingTaskSOPs.isEmpty ? 0 : 10) + if let sop = viewModel.currentAssignedSOP { + VStack(alignment: .leading, spacing: 10) { + Text(sop.name) + .font(DesignSystem.fonts.body(size: 30, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + .lineLimit(3) + .minimumScaleFactor(0.72) + + Text(viewModel.currentAssignmentSubtitle) + .font(DesignSystem.fonts.body(size: 15, weight: .medium)) + .foregroundColor(DesignSystem.colors.adminMuted) + .lineLimit(2) + + HStack(spacing: 8) { + assignmentMetric("\(sop.steps.count)", "steps") + assignmentMetric(sop.validationSummary, "check") + } + } + } else { + VStack(alignment: .leading, spacing: 8) { + Text("No active SOP") + .font(DesignSystem.fonts.body(size: 28, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + + Text("Sync assignments or set the worker login in Settings.") + .font(DesignSystem.fonts.body(size: 15)) + .foregroundColor(DesignSystem.colors.adminMuted) + } } - if viewModel.pendingTaskSOPs.isEmpty { - emptyStateCard( - title: "NO PENDING TASKS", - body: "All assigned SOPs for this demo shift are complete. Reopen the app to reset the shift list." - ) - } else { - VStack(spacing: 0) { - ForEach(viewModel.pendingTaskSOPs) { sop in - Button { - viewModel.presentCapture(for: sop) - } label: { - PendingTaskRow( - sop: sop, - packageTitle: sop.packageTitle, - isTopTask: sop.id == viewModel.pendingTaskSOPs.first?.id - ) - } - .buttonStyle(.plain) - - if sop.id != viewModel.pendingTaskSOPs.last?.id { - Divider() - .overlay(DesignSystem.colors.white.opacity(0.08)) - } - } + Button { + viewModel.startCurrentAssignmentFromHome() + } label: { + HStack(spacing: 10) { + Image(systemName: "camera.fill") + .font(.system(size: 16, weight: .semibold)) + Text(viewModel.currentAssignedSOP == nil ? "NO ASSIGNMENT" : "START CAMERA") + .font(DesignSystem.fonts.mono(size: 14, weight: .semibold)) } - .background(DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white.opacity(0.16), lineWidth: 1)) + .foregroundColor(DesignSystem.colors.white) + .frame(maxWidth: .infinity) + .frame(height: 54) + .background(viewModel.currentAssignedSOP == nil ? DesignSystem.colors.adminSubtle : DesignSystem.colors.adminInk) + .clipShape(RoundedRectangle(cornerRadius: 8)) } + .buttonStyle(.plain) + .disabled(viewModel.currentAssignedSOP == nil || viewModel.isSyncingOperations) } + .padding(16) + .background(DesignSystem.colors.adminSurface) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .shadow(color: .black.opacity(0.05), radius: 18, x: 0, y: 10) } - private var syncBanner: some View { - HStack(spacing: 10) { - ProgressView() - .tint(DesignSystem.colors.vibrantTeal) + private var cameraPanel: some View { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 10) { + Image(systemName: viewModel.hasActiveDevice ? "eyeglasses" : "iphone") + .font(.system(size: 18, weight: .semibold)) + .foregroundColor(viewModel.hasActiveDevice ? DesignSystem.colors.successGreen : DesignSystem.colors.brandOrange) + .frame(width: 34, height: 34) + .background(DesignSystem.colors.adminBackground) + .clipShape(RoundedRectangle(cornerRadius: 8)) + + VStack(alignment: .leading, spacing: 3) { + Text(viewModel.cameraReadinessLabel) + .font(DesignSystem.fonts.body(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + Text(viewModel.cameraReadinessDetail) + .font(DesignSystem.fonts.body(size: 13)) + .foregroundColor(DesignSystem.colors.adminMuted) + } - Text("Syncing pending tasks…") - .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) - .foregroundColor(DesignSystem.colors.white) - } - .padding(12) - .frame(maxWidth: .infinity, alignment: .leading) - .background(DesignSystem.colors.deepNavy.opacity(0.75)) - .overlay(Rectangle().stroke(DesignSystem.colors.vibrantTeal.opacity(0.5), lineWidth: 1)) - } + Spacer() - private func noticeBanner(title: String, body: String) -> some View { - statusBanner( - title: title, - body: body, - accent: DesignSystem.colors.vibrantTeal + Text("AUTO") + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.brandOrange) + .padding(.horizontal, 8) + .padding(.vertical, 5) + .background(DesignSystem.colors.brandOrange.opacity(0.12)) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + if !viewModel.hasActiveDevice { + Button { + wearablesViewModel.connectGlasses() + } label: { + HStack(spacing: 8) { + Image(systemName: "eyeglasses") + Text(wearablesViewModel.registrationState == .registering ? "CONNECTING GLASSES" : "CONNECT GLASSES") + } + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + .frame(maxWidth: .infinity) + .frame(height: 42) + .background(DesignSystem.colors.adminBackground) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) + ) + } + .buttonStyle(.plain) + .disabled(wearablesViewModel.registrationState == .registering) + } + } + .padding(14) + .background(DesignSystem.colors.adminSurface) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } - private func statusBanner(title: String, body: String, accent: Color) -> some View { - VStack(alignment: .leading, spacing: 8) { - Text(title) - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(accent) - - Text(body) - .font(DesignSystem.fonts.body(size: 14)) - .foregroundColor(DesignSystem.colors.blueGrey) + @ViewBuilder + private var notices: some View { + if viewModel.isSyncingOperations { + noticeRow( + icon: "arrow.triangle.2.circlepath", + title: "Syncing assignments", + body: "Loading the current worker queue.", + color: DesignSystem.colors.brandOrange + ) } - .padding(12) - .frame(maxWidth: .infinity, alignment: .leading) - .background(DesignSystem.colors.deepNavy.opacity(0.75)) - .overlay(Rectangle().stroke(accent.opacity(0.32), lineWidth: 1)) - } - private func emptyStateCard(title: String, body: String) -> some View { - VStack(alignment: .leading, spacing: 8) { - Text(title) - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.vibrantTeal) + if let warning = viewModel.operationsSyncWarning, + !warning.isEmpty { + noticeRow( + icon: "exclamationmark.triangle.fill", + title: "Sync warning", + body: warning, + color: DesignSystem.colors.adminWarning + ) + } - Text(body) - .font(DesignSystem.fonts.body(size: 14)) - .foregroundColor(DesignSystem.colors.blueGrey) + if let error = viewModel.operationsSyncError, + !error.isEmpty { + noticeRow( + icon: "exclamationmark.octagon.fill", + title: "Assignment issue", + body: error, + color: DesignSystem.colors.dangerRed + ) } - .padding(16) - .frame(maxWidth: .infinity, alignment: .leading) - .background(DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white.opacity(0.16), lineWidth: 1)) } - private func actionButton(title: String, action: @escaping () -> Void) -> some View { + private func iconButton(systemName: String, action: @escaping () -> Void) -> some View { Button(action: action) { - Text(title) - .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) - .foregroundColor(DesignSystem.colors.deepNavy) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(DesignSystem.colors.vibrantTeal) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 0.5)) + Image(systemName: systemName) + .font(.system(size: 15, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + .frame(width: 40, height: 40) + .background(DesignSystem.colors.adminSurface) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } .buttonStyle(.plain) } - private func modeButton(title: String, mode: StreamingMode, enabled: Bool) -> some View { - let selected = viewModel.preferredCaptureMode == mode - return Button { - viewModel.selectCaptureMode(mode) - } label: { - Text(title) - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(selected ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(selected ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 0.5)) - } - .disabled(!enabled) - .opacity(enabled ? 1.0 : 0.45) - .buttonStyle(.plain) + private func statusPill(title: String, color: Color) -> some View { + Text(title.uppercased()) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(color) + .lineLimit(1) + .minimumScaleFactor(0.7) + .padding(.horizontal, 10) + .padding(.vertical, 7) + .background(DesignSystem.colors.adminSurface) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } -} -private struct PendingTaskRow: View { - let sop: SOPTemplate - let packageTitle: String? - let isTopTask: Bool + private func assignmentMetric(_ value: String, _ label: String) -> some View { + VStack(alignment: .leading, spacing: 2) { + Text(value.uppercased()) + .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminInk) + .lineLimit(1) + .minimumScaleFactor(0.7) - private var taskSubtitle: String { - let duration = "\(Int(max(sop.estimatedDuration, 15)))S" - let prefix = "\(sop.items.count) ITEMS · EST. \(duration)" - if let packageTitle, !packageTitle.isEmpty { - return "\(prefix) · \(packageTitle.uppercased())" + Text(label.uppercased()) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminMuted) } - return prefix + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(DesignSystem.colors.adminBackground) + .clipShape(RoundedRectangle(cornerRadius: 8)) } - var body: some View { - HStack(alignment: .center, spacing: 12) { - VStack(alignment: .leading, spacing: 6) { - Text(sop.name) - .font(DesignSystem.fonts.mono(size: 22, weight: .semibold)) - .foregroundColor(DesignSystem.colors.white) - .multilineTextAlignment(.leading) + private func noticeRow(icon: String, title: String, body: String, color: Color) -> some View { + HStack(alignment: .top, spacing: 10) { + Image(systemName: icon) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(color) + .frame(width: 24, height: 24) - Text(taskSubtitle) + VStack(alignment: .leading, spacing: 4) { + Text(title.uppercased()) .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) - .foregroundColor(isTopTask ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.blueGrey) - } - - Spacer() + .foregroundColor(color) - Image(systemName: "chevron.right") - .font(.system(size: 14, weight: .semibold)) - .foregroundColor(DesignSystem.colors.blueGrey) + Text(body) + .font(DesignSystem.fonts.body(size: 13)) + .foregroundColor(DesignSystem.colors.adminMuted) + .fixedSize(horizontal: false, vertical: true) + } + Spacer(minLength: 0) } - .padding(.horizontal, 14) - .padding(.vertical, 18) - .frame(maxWidth: .infinity, alignment: .leading) - .background(isTopTask ? DesignSystem.colors.deepNavy.opacity(0.92) : DesignSystem.colors.deepNavy) + .padding(12) + .background(DesignSystem.colors.adminSurface) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(color.opacity(0.35), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } } diff --git a/samples/CameraAccess/CameraAccess/Views/MainAppView.swift b/samples/CameraAccess/CameraAccess/Views/MainAppView.swift index b83cbc26..265e0895 100644 --- a/samples/CameraAccess/CameraAccess/Views/MainAppView.swift +++ b/samples/CameraAccess/CameraAccess/Views/MainAppView.swift @@ -21,25 +21,12 @@ struct MainAppView: View { let wearables: WearablesInterface @ObservedObject private var viewModel: WearablesViewModel - private var canUseMockDevices: Bool { - #if DEBUG - return true - #else - return false - #endif - } - init(wearables: WearablesInterface, viewModel: WearablesViewModel) { self.wearables = wearables self.viewModel = viewModel } var body: some View { - if viewModel.registrationState == .registered || (canUseMockDevices && viewModel.hasMockDevice) || viewModel.skipToIPhoneMode { - StreamSessionView(wearables: wearables, wearablesVM: viewModel) - } else { - // User not registered - show registration/onboarding flow - HomeScreenView(viewModel: viewModel) - } + StreamSessionView(wearables: wearables, wearablesVM: viewModel) } } diff --git a/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift b/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift index 47c960b7..bd9f03aa 100644 --- a/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift +++ b/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift @@ -28,8 +28,8 @@ struct StreamSessionView: View { var body: some View { NavigationStack { - HomeView(viewModel: viewModel) - .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) + HomeView(viewModel: viewModel, wearablesViewModel: wearablesViewModel) + .background(DesignSystem.colors.adminBackground.ignoresSafeArea()) } .overlay(alignment: .bottom) { if viewModel.showShipSuccessToast { diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index 2360071e..37c5b9b1 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -930,6 +930,56 @@ final class GeminiInstructionSyncTests: XCTestCase { } } +@MainActor +final class AssignmentDrivenFlowTests: XCTestCase { + override func setUp() async throws { + try await super.setUp() + try? Wearables.configure() + } + + func testHomeStartsFirstAssignedSopWithoutPicker() async throws { + let viewModel = StreamSessionViewModel(wearables: Wearables.shared) + let secondAssignment = SOPTemplate( + name: "Second assigned SOP", + items: ["Inspect secondary station"], + packageTitle: "Line A", + sortOrder: 2 + ) + let firstAssignment = SOPTemplate( + name: "First assigned SOP", + items: ["Inspect primary station"], + packageTitle: "Line A", + sortOrder: 1 + ) + + viewModel.availableSOPs = [secondAssignment, firstAssignment] + viewModel.startCurrentAssignmentFromHome() + + XCTAssertEqual(viewModel.currentAssignedSOP?.name, "First assigned SOP") + XCTAssertEqual(viewModel.selectedSOP?.name, "First assigned SOP") + XCTAssertEqual(viewModel.activeCaptureSOP?.name, "First assigned SOP") + XCTAssertEqual(viewModel.preferredCaptureMode, .iPhone) + } + + func testHomePrefersGlassesForNextAssignmentWhenAvailable() async throws { + let viewModel = StreamSessionViewModel(wearables: Wearables.shared) + viewModel.hasActiveDevice = true + viewModel.availableSOPs = [ + SOPTemplate( + name: "Assigned SOP", + items: ["Inspect station"], + packageTitle: "Line A", + sortOrder: 1 + ) + ] + + viewModel.startCurrentAssignmentFromHome() + + XCTAssertEqual(viewModel.activeCaptureSOP?.name, "Assigned SOP") + XCTAssertEqual(viewModel.preferredCaptureMode, .glasses) + } +} + final class OpsAPIClientRoutingTests: XCTestCase { override func tearDown() { RequestCaptureURLProtocol.handler = nil From 9f929c545a860a284642f0393bc0711acdec077c Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 31 May 2026 14:40:46 -0500 Subject: [PATCH 06/11] Stabilize assignment-guided SOP spotter --- .../Gemini/GeminiLiveSpotter.swift | 10 + .../CameraAccess/Gemini/SopRelayClient.swift | 43 +++ .../ViewModels/StreamSessionViewModel.swift | 251 +++++++++++++++++- .../CameraAccessTests/CameraAccessTests.swift | 59 +++- 4 files changed, 350 insertions(+), 13 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift index dd0fe6b9..7954dfcc 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift @@ -9,6 +9,10 @@ final class GeminiLiveSpotter { let name: String let aiPrompt: String let expectedObjects: [String] + let preconditions: [String] + let postconditions: [String] + let skipRisk: String + let evidenceRequired: Bool let validation: String let critical: Bool } @@ -19,6 +23,7 @@ final class GeminiLiveSpotter { let confidence: Double let reason: String let evidenceTimestamp: String + let threshold: Double let autoComplete: Bool } @@ -46,6 +51,10 @@ final class GeminiLiveSpotter { stepTitle: item.name, aiPrompt: item.aiPrompt, expectedObjects: item.expectedObjects, + preconditions: item.preconditions, + postconditions: item.postconditions, + skipRisk: item.skipRisk, + evidenceRequired: item.evidenceRequired, imageBase64: imagePayload.base64, imageMimeType: imagePayload.mimeType, capturedAt: capturedAt, @@ -61,6 +70,7 @@ final class GeminiLiveSpotter { confidence: response.confidence, reason: response.reason, evidenceTimestamp: response.evidenceTimestamp, + threshold: response.threshold ?? (item.critical ? 0.94 : ((item.evidenceRequired || item.skipRisk == "high") ? 0.9 : 0.88)), autoComplete: response.autoComplete ) ) diff --git a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift index 6dc05d21..67885263 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift @@ -403,6 +403,10 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { let critical: Bool let aiPrompt: String let expectedObjects: [String] + let preconditions: [String] + let postconditions: [String] + let skipRisk: String + let evidenceRequired: Bool let allowManualComplete: Bool private enum CodingKeys: String, CodingKey { @@ -423,6 +427,12 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { case aiPromptCamel = "aiPrompt" case expectedObjects = "expected_objects" case expectedObjectsCamel = "expectedObjects" + case preconditions + case postconditions + case skipRisk = "skip_risk" + case skipRiskCamel = "skipRisk" + case evidenceRequired = "evidence_required" + case evidenceRequiredCamel = "evidenceRequired" case allowManualComplete = "allow_manual_complete" case allowManualCompleteCamel = "allowManualComplete" } @@ -437,6 +447,10 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { critical: Bool = false, aiPrompt: String? = nil, expectedObjects: [String] = [], + preconditions: [String] = [], + postconditions: [String] = [], + skipRisk: String = "medium", + evidenceRequired: Bool = true, allowManualComplete: Bool = true ) { self.id = id @@ -448,6 +462,10 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { self.critical = critical self.aiPrompt = aiPrompt ?? "Look at the image and confirm whether \"\(title)\" has been completed." self.expectedObjects = expectedObjects + self.preconditions = preconditions + self.postconditions = postconditions + self.skipRisk = skipRisk + self.evidenceRequired = evidenceRequired self.allowManualComplete = allowManualComplete } @@ -476,6 +494,11 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { try container.decodeIfPresent([String].self, forKey: .expectedObjects) ?? container.decodeIfPresent([String].self, forKey: .expectedObjectsCamel) ?? [] + let preconditions = (try container.decodeIfPresent([String].self, forKey: .preconditions)) ?? [] + let postconditions = (try container.decodeIfPresent([String].self, forKey: .postconditions)) ?? [] + let skipRisk = + try container.decodeLossyString(forKeys: [.skipRisk, .skipRiskCamel]) + ?? ((try container.decodeLossyBool(forKeys: [.critical]) ?? false) ? "high" : "medium") self.init( id: resolvedID, order: try container.decodeLossyInt(forKeys: [.order, .index]) ?? 0, @@ -487,6 +510,12 @@ struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { aiPrompt: try container.decodeLossyString(forKeys: [.aiPrompt, .aiPromptCamel]), expectedObjects: expectedObjects.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }, + preconditions: preconditions.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }, + postconditions: postconditions.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }, + skipRisk: ["low", "medium", "high"].contains(skipRisk) ? skipRisk : "medium", + evidenceRequired: + try container.decodeLossyBool(forKeys: [.evidenceRequired, .evidenceRequiredCamel]) + ?? true, allowManualComplete: try container.decodeLossyBool(forKeys: [.allowManualComplete, .allowManualCompleteCamel]) ?? true @@ -616,6 +645,10 @@ struct WorkerQueueItem: Identifiable, Decodable, Equatable { critical: step.critical, aiPrompt: step.aiPrompt, expectedObjects: step.expectedObjects, + preconditions: step.preconditions, + postconditions: step.postconditions, + skipRisk: step.skipRisk, + evidenceRequired: step.evidenceRequired, allowManualComplete: step.allowManualComplete ) } @@ -1447,6 +1480,10 @@ struct GeminiSpotterRequest: Equatable { let stepTitle: String let aiPrompt: String let expectedObjects: [String] + let preconditions: [String] + let postconditions: [String] + let skipRisk: String + let evidenceRequired: Bool let imageBase64: String let imageMimeType: String let capturedAt: String @@ -1460,6 +1497,10 @@ struct GeminiSpotterRequest: Equatable { "stepTitle": stepTitle, "aiPrompt": aiPrompt, "expectedObjects": expectedObjects, + "preconditions": preconditions, + "postconditions": postconditions, + "skipRisk": skipRisk, + "evidenceRequired": evidenceRequired, "imageBase64": imageBase64, "imageMimeType": imageMimeType, "capturedAt": capturedAt, @@ -1474,6 +1515,8 @@ struct GeminiSpotterResponse: Decodable, Equatable { let confidence: Double let reason: String let evidenceTimestamp: String + let threshold: Double? + let model: String? let autoComplete: Bool } diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 1c95e715..6af765e4 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -49,6 +49,29 @@ enum SopTerminationStatus: String, Codable { case userEnded = "user_ended" } +enum GuidancePolicy: String { + case silent + case confirm + case nextInstruction = "next_instruction" + case warning + case helpPrompt = "help_prompt" + + var instruction: String { + switch self { + case .silent: + return "Stay silent unless the worker asks for help, the step changes, or a safety/compliance issue appears." + case .confirm: + return "Briefly confirm the observed evidence, then stay quiet." + case .nextInstruction: + return "Give the next step instruction once, then stay quiet while the worker acts." + case .warning: + return "Warn the worker only about the specific skipped, out-of-order, or unsafe condition." + case .helpPrompt: + return "Ask one short clarifying question or offer help because the evidence is unclear." + } + } +} + enum DossierPipelineStatusKind { case info case active @@ -143,6 +166,10 @@ struct SOPStepTemplate: Identifiable, Hashable { let critical: Bool let aiPrompt: String let expectedObjects: [String] + let preconditions: [String] + let postconditions: [String] + let skipRisk: String + let evidenceRequired: Bool let allowManualComplete: Bool init( @@ -155,6 +182,10 @@ struct SOPStepTemplate: Identifiable, Hashable { critical: Bool = false, aiPrompt: String, expectedObjects: [String] = [], + preconditions: [String] = [], + postconditions: [String] = [], + skipRisk: String = "medium", + evidenceRequired: Bool = true, allowManualComplete: Bool = true ) { self.id = id @@ -166,6 +197,10 @@ struct SOPStepTemplate: Identifiable, Hashable { self.critical = critical self.aiPrompt = aiPrompt self.expectedObjects = expectedObjects + self.preconditions = preconditions + self.postconditions = postconditions + self.skipRisk = skipRisk + self.evidenceRequired = evidenceRequired self.allowManualComplete = allowManualComplete } } @@ -515,10 +550,33 @@ struct ChecklistItemState: Identifiable, Codable, Hashable { let critical: Bool let aiPrompt: String let expectedObjects: [String] + let preconditions: [String] + let postconditions: [String] + let skipRisk: String + let evidenceRequired: Bool let allowManualComplete: Bool var isChecked: Bool var completionSource: ChecklistCompletionSource + private enum CodingKeys: String, CodingKey { + case id + case itemID + case name + case description + case duration + case validation + case critical + case aiPrompt + case expectedObjects + case preconditions + case postconditions + case skipRisk + case evidenceRequired + case allowManualComplete + case isChecked + case completionSource + } + init( id: UUID = UUID(), itemID: String? = nil, @@ -529,6 +587,10 @@ struct ChecklistItemState: Identifiable, Codable, Hashable { critical: Bool = false, aiPrompt: String? = nil, expectedObjects: [String] = [], + preconditions: [String] = [], + postconditions: [String] = [], + skipRisk: String = "medium", + evidenceRequired: Bool = true, allowManualComplete: Bool = true, isChecked: Bool = false, completionSource: ChecklistCompletionSource = .pending @@ -542,11 +604,38 @@ struct ChecklistItemState: Identifiable, Codable, Hashable { self.critical = critical self.aiPrompt = aiPrompt ?? "Look at the image and confirm whether \"\(name)\" has been completed." self.expectedObjects = expectedObjects + self.preconditions = preconditions + self.postconditions = postconditions + self.skipRisk = skipRisk + self.evidenceRequired = evidenceRequired self.allowManualComplete = allowManualComplete self.isChecked = isChecked self.completionSource = completionSource } + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let name = try container.decode(String.self, forKey: .name) + self.init( + id: try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID(), + itemID: try container.decodeIfPresent(String.self, forKey: .itemID), + name: name, + description: try container.decodeIfPresent(String.self, forKey: .description) ?? "", + duration: try container.decodeIfPresent(String.self, forKey: .duration) ?? "30s", + validation: try container.decodeIfPresent(String.self, forKey: .validation) ?? "visual", + critical: try container.decodeIfPresent(Bool.self, forKey: .critical) ?? false, + aiPrompt: try container.decodeIfPresent(String.self, forKey: .aiPrompt), + expectedObjects: try container.decodeIfPresent([String].self, forKey: .expectedObjects) ?? [], + preconditions: try container.decodeIfPresent([String].self, forKey: .preconditions) ?? [], + postconditions: try container.decodeIfPresent([String].self, forKey: .postconditions) ?? [], + skipRisk: try container.decodeIfPresent(String.self, forKey: .skipRisk) ?? "medium", + evidenceRequired: try container.decodeIfPresent(Bool.self, forKey: .evidenceRequired) ?? true, + allowManualComplete: try container.decodeIfPresent(Bool.self, forKey: .allowManualComplete) ?? true, + isChecked: try container.decodeIfPresent(Bool.self, forKey: .isChecked) ?? false, + completionSource: try container.decodeIfPresent(ChecklistCompletionSource.self, forKey: .completionSource) ?? .pending + ) + } + static func normalizedItemID(from name: String) -> String { let lowered = name.lowercased() let filtered = lowered.map { ch in @@ -559,6 +648,63 @@ struct ChecklistItemState: Identifiable, Codable, Hashable { } } +struct SpotterEvidenceDecision: Equatable { + let shouldAutoComplete: Bool + let sampleCount: Int + let positiveCount: Int + let averagePositiveConfidence: Double + let threshold: Double +} + +struct SpotterEvidenceWindow { + private struct Sample { + let matched: Bool + let confidence: Double + } + + var maxSamples = 5 + var minPositiveSamples = 3 + private var samplesByStepID: [String: [Sample]] = [:] + + mutating func record( + stepID: String, + matched: Bool, + autoComplete: Bool, + confidence: Double, + threshold: Double + ) -> SpotterEvidenceDecision { + let positive = matched && autoComplete && confidence >= threshold + var samples = samplesByStepID[stepID] ?? [] + samples.append(Sample(matched: positive, confidence: confidence)) + if samples.count > maxSamples { + samples = Array(samples.suffix(maxSamples)) + } + samplesByStepID[stepID] = samples + + let positives = samples.filter(\.matched) + let average = positives.isEmpty + ? 0 + : positives.reduce(0) { $0 + $1.confidence } / Double(positives.count) + let shouldAutoComplete = positives.count >= minPositiveSamples && average >= threshold + + return SpotterEvidenceDecision( + shouldAutoComplete: shouldAutoComplete, + sampleCount: samples.count, + positiveCount: positives.count, + averagePositiveConfidence: average, + threshold: threshold + ) + } + + mutating func reset(stepID: String) { + samplesByStepID[stepID] = nil + } + + mutating func resetAll() { + samplesByStepID.removeAll() + } +} + private enum WorkerLiveLogger { static func log( _ event: String, @@ -1515,6 +1661,8 @@ class StreamSessionViewModel: ObservableObject { @Published var isClosingPackage: Bool = false @Published var activeCaptureSOP: SOPTemplate? @Published var geminiInstructionSyncStatus: String = "" + @Published private(set) var guidancePolicy: GuidancePolicy = .nextInstruction + @Published private(set) var guidancePolicyReason: String = "Start the assigned SOP." @Published var iPhonePreviewSession: AVCaptureSession? @Published var availableSOPs: [SOPTemplate] = [] @@ -1669,6 +1817,7 @@ class StreamSessionViewModel: ObservableObject { qos: .userInitiated ) private var proofImagesByTargetID: [String: Data] = [:] + private var spotterEvidenceWindow = SpotterEvidenceWindow() private var lastSpotterInferenceTime: Date = .distantPast private var isSpotterInferenceInFlight = false private var isFinalizingAndShipping = false @@ -2337,6 +2486,10 @@ class StreamSessionViewModel: ObservableObject { critical: step.critical, aiPrompt: step.aiPrompt, expectedObjects: step.expectedObjects, + preconditions: step.preconditions, + postconditions: step.postconditions, + skipRisk: step.skipRisk, + evidenceRequired: step.evidenceRequired, allowManualComplete: step.allowManualComplete ) } @@ -3152,6 +3305,11 @@ class StreamSessionViewModel: ObservableObject { } } + private func updateGuidancePolicy(_ policy: GuidancePolicy, reason: String) { + guidancePolicy = policy + guidancePolicyReason = reason + } + private func formatStreamingError(_ error: StreamSessionError) -> String { switch error { case .internalError: @@ -3188,9 +3346,15 @@ class StreamSessionViewModel: ObservableObject { critical: step.critical, aiPrompt: step.aiPrompt, expectedObjects: step.expectedObjects, + preconditions: step.preconditions, + postconditions: step.postconditions, + skipRisk: step.skipRisk, + evidenceRequired: step.evidenceRequired, allowManualComplete: step.allowManualComplete ) } + spotterEvidenceWindow.resetAll() + updateGuidancePolicy(.nextInstruction, reason: "A new SOP assignment started.") Task { @MainActor [weak self] in await self?.syncGeminiSessionInstruction(for: sop) @@ -3419,6 +3583,8 @@ class StreamSessionViewModel: ObservableObject { checklistItems[index].completionSource = source let item = checklistItems[index] + spotterEvidenceWindow.reset(stepID: item.itemID) + updateGuidancePolicy(.nextInstruction, reason: "Step completed by \(source.rawValue).") Task { await handleChecklistMutation( item: item, @@ -3443,6 +3609,8 @@ class StreamSessionViewModel: ObservableObject { checklistItems[index].isChecked = true checklistItems[index].completionSource = .vision dossierSpotterHitCount += 1 + spotterEvidenceWindow.reset(stepID: itemID) + updateGuidancePolicy(.nextInstruction, reason: "Step completed after stable visual evidence.") updateDossierPipelineStatus("Live spotter hit #\(dossierSpotterHitCount)", kind: .active) let item = checklistItems[index] @@ -3562,19 +3730,45 @@ class StreamSessionViewModel: ObservableObject { ) } let durationMs = (CACurrentMediaTime() - requestStartedAt) * 1000 - let autoCompleteMatches = matches.filter(\.autoComplete) + let stableMatches = await MainActor.run { + matches.compactMap { match -> (GeminiLiveSpotter.SpotterMatch, SpotterEvidenceDecision)? in + guard let target = pendingItems.first(where: { $0.id == match.id }) else { return nil } + let decision = self.spotterEvidenceWindow.record( + stepID: match.id, + matched: match.matched, + autoComplete: match.autoComplete, + confidence: match.confidence, + threshold: match.threshold + ) + if match.matched && !decision.shouldAutoComplete { + self.updateGuidancePolicy( + .confirm, + reason: "Visual evidence is accumulating for \(target.name)." + ) + } else if !match.matched && decision.sampleCount >= self.spotterEvidenceWindow.maxSamples && target.skipRisk == "high" { + self.updateGuidancePolicy( + .helpPrompt, + reason: "High-risk step evidence is still unclear for \(target.name)." + ) + } + return (match, decision) + } + } + let autoCompleteMatches = stableMatches.filter { $0.1.shouldAutoComplete } NSLog( - "[Spotter] Active-step review targets=%@ matched=%@ autoComplete=%@ duration=%.1fms", + "[Spotter] Active-step review targets=%@ matched=%@ stableAutoComplete=%@ duration=%.1fms", pendingItems.map(\.id).joined(separator: ","), matches.filter(\.matched).map(\.id).joined(separator: ","), - autoCompleteMatches.map(\.id).joined(separator: ","), + autoCompleteMatches.map { $0.0.id }.joined(separator: ","), durationMs ) - if let firstMatch = matches.first { + if let first = stableMatches.first { + let firstMatch = first.0 + let firstDecision = first.1 await WorkerTelemetry.shared.record( "gemini_spotter_result", source: "gemini_spotter", - stage: firstMatch.autoComplete ? "auto_complete" : firstMatch.matched ? "matched" : "not_matched", + stage: firstDecision.shouldAutoComplete ? "auto_complete" : firstMatch.matched ? "stabilizing" : "not_matched", sessionID: self.currentSopSessionId, durationMs: durationMs, metricValue: firstMatch.confidence, @@ -3583,19 +3777,29 @@ class StreamSessionViewModel: ObservableObject { "step_id": firstMatch.id, "matched": firstMatch.matched, "auto_complete": firstMatch.autoComplete, + "stable_auto_complete": firstDecision.shouldAutoComplete, + "window_samples": firstDecision.sampleCount, + "window_positive_samples": firstDecision.positiveCount, + "window_average_confidence": firstDecision.averagePositiveConfidence, + "threshold": firstDecision.threshold, "reason": firstMatch.reason ] ) } await MainActor.run { - autoCompleteMatches.forEach { - self.captureProofImageIfNeeded(for: $0.id, from: image) - self.setChecklistItemCheckedBySpotterID($0.id, evidence: [ - "ai_confidence": $0.confidence, - "ai_reason": $0.reason, - "evidence_timestamp": $0.evidenceTimestamp, - "auto_complete": $0.autoComplete + autoCompleteMatches.forEach { match, decision in + self.captureProofImageIfNeeded(for: match.id, from: image) + self.setChecklistItemCheckedBySpotterID(match.id, evidence: [ + "ai_confidence": match.confidence, + "ai_reason": match.reason, + "evidence_timestamp": match.evidenceTimestamp, + "auto_complete": match.autoComplete, + "stable_auto_complete": decision.shouldAutoComplete, + "window_samples": decision.sampleCount, + "window_positive_samples": decision.positiveCount, + "window_average_confidence": decision.averagePositiveConfidence, + "threshold": decision.threshold ]) } self.isSpotterInferenceInFlight = false @@ -3878,6 +4082,10 @@ class StreamSessionViewModel: ObservableObject { name: currentStep.name, aiPrompt: currentStep.aiPrompt, expectedObjects: currentStep.expectedObjects, + preconditions: currentStep.preconditions, + postconditions: currentStep.postconditions, + skipRisk: currentStep.skipRisk, + evidenceRequired: currentStep.evidenceRequired, validation: currentStep.validation, critical: currentStep.critical ) @@ -3927,6 +4135,14 @@ class StreamSessionViewModel: ObservableObject { .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } .joined(separator: ", ") + let preconditions = step.preconditions + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .joined(separator: "; ") + let postconditions = step.postconditions + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .joined(separator: "; ") let directNextAction: String if hasRemainingSteps { @@ -3957,6 +4173,17 @@ class StreamSessionViewModel: ObservableObject { if !expectedObjects.isEmpty { lines.append("Expected objects to look for: \(expectedObjects)") } + if !preconditions.isEmpty { + lines.append("Preconditions before this step: \(preconditions)") + } + if !postconditions.isEmpty { + lines.append("Postconditions that prove completion: \(postconditions)") + } + lines.append("Skip risk: \(step.skipRisk)") + lines.append("Evidence required: \(step.evidenceRequired ? "yes" : "no")") + lines.append("Guidance policy: \(guidancePolicy.rawValue)") + lines.append("Guidance policy reason: \(guidancePolicyReason)") + lines.append("Guidance policy instruction: \(guidancePolicy.instruction)") if let nextCritical = nextCriticalStepTitleAfterActive(in: sop, nextIndex: nextIndex) { lines.append("Next critical checkpoint after this: \(nextCritical)") diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index 37c5b9b1..1f333387 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -477,6 +477,8 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { confidence: 0.93, reason: "Clear visual evidence.", evidenceTimestamp: request.capturedAt, + threshold: 0.88, + model: "gemini-3.5-flash", autoComplete: true ) : spotterResponses.removeFirst() @@ -980,6 +982,57 @@ final class AssignmentDrivenFlowTests: XCTestCase { } } +final class SpotterEvidenceWindowTests: XCTestCase { + func testRequiresMultiplePositiveSamplesBeforeAutoComplete() { + var window = SpotterEvidenceWindow() + + let first = window.record( + stepID: "seal-check", + matched: true, + autoComplete: true, + confidence: 0.92, + threshold: 0.88 + ) + let second = window.record( + stepID: "seal-check", + matched: true, + autoComplete: true, + confidence: 0.91, + threshold: 0.88 + ) + let third = window.record( + stepID: "seal-check", + matched: true, + autoComplete: true, + confidence: 0.9, + threshold: 0.88 + ) + + XCTAssertFalse(first.shouldAutoComplete) + XCTAssertFalse(second.shouldAutoComplete) + XCTAssertTrue(third.shouldAutoComplete) + XCTAssertEqual(third.positiveCount, 3) + } + + func testNegativeSamplesPreventStableAutoComplete() { + var window = SpotterEvidenceWindow() + + _ = window.record(stepID: "seal-check", matched: true, autoComplete: true, confidence: 0.92, threshold: 0.88) + _ = window.record(stepID: "seal-check", matched: false, autoComplete: false, confidence: 0.2, threshold: 0.88) + let decision = window.record( + stepID: "seal-check", + matched: true, + autoComplete: true, + confidence: 0.91, + threshold: 0.88 + ) + + XCTAssertFalse(decision.shouldAutoComplete) + XCTAssertEqual(decision.sampleCount, 3) + XCTAssertEqual(decision.positiveCount, 2) + } +} + final class OpsAPIClientRoutingTests: XCTestCase { override func tearDown() { RequestCaptureURLProtocol.handler = nil @@ -1026,7 +1079,7 @@ final class OpsAPIClientRoutingTests: XCTestCase { ) case "/api/worker/gemini/spotter": body = Data( - #"{"matched":true,"confidence":0.93,"reason":"Clear visual evidence.","evidenceTimestamp":"2026-05-30T18:31:00.000Z","autoComplete":true}"# + #"{"matched":true,"confidence":0.93,"reason":"Clear visual evidence.","evidenceTimestamp":"2026-05-30T18:31:00.000Z","threshold":0.88,"model":"gemini-3.5-flash","autoComplete":true}"# .utf8 ) default: @@ -1082,6 +1135,10 @@ final class OpsAPIClientRoutingTests: XCTestCase { stepTitle: "Check seal", aiPrompt: "Confirm the seal is visible.", expectedObjects: ["seal"], + preconditions: ["Package is in view"], + postconditions: ["Seal is visible"], + skipRisk: "medium", + evidenceRequired: true, imageBase64: "ZmFrZQ==", imageMimeType: "image/jpeg", capturedAt: "2026-05-30T18:31:00.000Z", From 88e051241efdb07fab4f0b61c64428e86ca8c9ce Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 11 Jun 2026 13:16:07 -0500 Subject: [PATCH 07/11] Stabilize iOS audio handoff and recording --- .../CameraAccess.xcodeproj/project.pbxproj | 17 +- .../CameraAccess/Gemini/AudioManager.swift | 525 +++- .../CameraAccess/Gemini/GeminiConfig.swift | 35 +- .../Gemini/GeminiLiveService.swift | 88 +- .../Gemini/GeminiLiveSpotter.swift | 20 +- .../Gemini/GeminiSessionViewModel.swift | 562 ++--- .../CameraAccess/Gemini/SopRelayClient.swift | 215 +- .../OpenClaw/OpenClawBridge.swift | 139 -- .../OpenClaw/OpenClawEventClient.swift | 191 -- .../OpenClaw/ToolCallModels.swift | 142 -- .../OpenClaw/ToolCallRouter.swift | 165 -- .../CameraAccess/Secrets.swift.example | 15 +- .../Settings/SettingsManager.swift | 127 +- .../CameraAccess/Settings/SettingsView.swift | 226 +- .../ViewModels/StreamSessionViewModel.swift | 2224 +++++++++++++++-- .../CameraAccess/Views/CaptureView.swift | 663 +++-- .../Views/Components/GeminiOverlayView.swift | 76 - .../CameraAccess/Views/HomeView.swift | 304 ++- .../Views/IPhoneCameraPreviewSurface.swift | 42 + .../CameraAccess/WebRTC/WebRTCClient.swift | 135 +- .../CameraAccess/WebRTC/WebRTCConfig.swift | 14 +- .../WebRTC/WebRTCSessionViewModel.swift | 188 +- .../iPhone/IPhoneCameraManager.swift | 57 +- .../CameraAccessTests/CameraAccessTests.swift | 57 +- 24 files changed, 4123 insertions(+), 2104 deletions(-) delete mode 100644 samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift delete mode 100644 samples/CameraAccess/CameraAccess/OpenClaw/OpenClawEventClient.swift delete mode 100644 samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift delete mode 100644 samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift create mode 100644 samples/CameraAccess/CameraAccess/Views/IPhoneCameraPreviewSurface.swift diff --git a/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj b/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj index 3d465458..76c71be4 100644 --- a/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj +++ b/samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ B10000012F50000100AA0001 /* DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000100AA0002 /* DesignSystem.swift */; }; B10000012F50000200AA0001 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000200AA0002 /* HomeView.swift */; }; B10000012F50000300AA0001 /* CaptureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000300AA0002 /* CaptureView.swift */; }; + B10000012F50004000AA0001 /* IPhoneCameraPreviewSurface.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50004000AA0002 /* IPhoneCameraPreviewSurface.swift */; }; B10000012F50000400AA0001 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10000012F50000400AA0002 /* HistoryView.swift */; }; 9DD895962F405E0E0090B9B9 /* RTCVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD895952F405E0E0090B9B9 /* RTCVideoView.swift */; }; 9DD895972F405E0E0090B9B9 /* PiPVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD895942F405E0E0090B9B9 /* PiPVideoView.swift */; }; @@ -117,6 +118,7 @@ B10000012F50000100AA0002 /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = ""; }; B10000012F50000200AA0002 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; B10000012F50000300AA0002 /* CaptureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureView.swift; sourceTree = ""; }; + B10000012F50004000AA0002 /* IPhoneCameraPreviewSurface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPhoneCameraPreviewSurface.swift; sourceTree = ""; }; B10000012F50000400AA0002 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; 9DD895942F405E0E0090B9B9 /* PiPVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPVideoView.swift; sourceTree = ""; }; 9DD895952F405E0E0090B9B9 /* RTCVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTCVideoView.swift; sourceTree = ""; }; @@ -138,7 +140,6 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 9D3C69602F367CF700E641A5 /* iPhone */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = iPhone; sourceTree = ""; }; - 9D85EB992F35EC46006C44D1 /* OpenClaw */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = OpenClaw; path = CameraAccess/OpenClaw; sourceTree = SOURCE_ROOT; }; E699CC962E8150670052C240 /* CameraAccessTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CameraAccessTests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -203,6 +204,7 @@ B10000012F50000100AA0002 /* DesignSystem.swift */, B10000012F50000200AA0002 /* HomeView.swift */, B10000012F50000300AA0002 /* CaptureView.swift */, + B10000012F50004000AA0002 /* IPhoneCameraPreviewSurface.swift */, B10000012F50000400AA0002 /* HistoryView.swift */, 8FFD605E2E84A2F70035E446 /* DebugMenuView.swift */, 8FD96B722E6F0A9800F56AB1 /* HomeScreenView.swift */, @@ -231,7 +233,6 @@ 8FD96B782E6F0A9800F56AB1 /* CameraAccess.entitlements */, 8FD96B792E6F0A9800F56AB1 /* CameraAccessApp.swift */, 8FD96B7B2E6F0A9800F56AB1 /* Info.plist */, - 9D85EB992F35EC46006C44D1 /* OpenClaw */, ); path = CameraAccess; sourceTree = ""; @@ -323,7 +324,6 @@ ); fileSystemSynchronizedGroups = ( 9D3C69602F367CF700E641A5 /* iPhone */, - 9D85EB992F35EC46006C44D1 /* OpenClaw */, ); name = CameraAccess; productName = CameraAccess; @@ -415,11 +415,12 @@ AAAAAAAAAAAAAAAAAAAAAA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; - files = ( - B10000012F50000100AA0001 /* DesignSystem.swift in Sources */, - B10000012F50000200AA0001 /* HomeView.swift in Sources */, - B10000012F50000300AA0001 /* CaptureView.swift in Sources */, - B10000012F50000400AA0001 /* HistoryView.swift in Sources */, + files = ( + B10000012F50000100AA0001 /* DesignSystem.swift in Sources */, + B10000012F50000200AA0001 /* HomeView.swift in Sources */, + B10000012F50000300AA0001 /* CaptureView.swift in Sources */, + B10000012F50004000AA0001 /* IPhoneCameraPreviewSurface.swift in Sources */, + B10000012F50000400AA0001 /* HistoryView.swift in Sources */, 8FD96B7F2E6F0A9800F56AB1 /* CameraAccessApp.swift in Sources */, 8FD96B812E6F0A9800F56AB1 /* HomeScreenView.swift in Sources */, 8F2D23802E856711002D0588 /* DebugMenuViewModel.swift in Sources */, diff --git a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift index 184f848e..aa901b4d 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift @@ -2,14 +2,344 @@ import AVFoundation import Foundation import UIKit -class AudioManager { +enum WorkerAudioRouteOwner: String, Sendable { + case aiGuide = "ai_guide" + case backOfficeWebRTC = "back_office_webrtc" + case holdToTalk = "hold_to_talk" + case viewer = "viewer" +} + +struct WorkerAudioRouteLease: Equatable, Sendable { + let owner: WorkerAudioRouteOwner + let token: UUID + let generation: UInt64 + + var payload: [String: Any] { + [ + "owner": owner.rawValue, + "token": token.uuidString, + "generation": generation + ] + } +} + +struct WorkerAudioRouteSnapshot { + let lease: WorkerAudioRouteLease + let owner: WorkerAudioRouteOwner + let mode: StreamingMode + let reason: String + let category: String + let audioMode: String + let inputs: [String] + let outputs: [String] + let preferredInput: String? + let usesHandsFreeRoute: Bool + let fallbackMessage: String? + let sampleRate: Double + let ioBufferDuration: Double + + var payload: [String: Any] { + [ + "owner": owner.rawValue, + "token": lease.token.uuidString, + "generation": lease.generation, + "mode": mode == .iPhone ? "iphone" : "glasses", + "reason": reason, + "category": category, + "audio_mode": audioMode, + "inputs": inputs, + "outputs": outputs, + "preferred_input": preferredInput ?? NSNull(), + "uses_hands_free_route": usesHandsFreeRoute, + "fallback_message": fallbackMessage ?? NSNull(), + "sample_rate": sampleRate, + "io_buffer_duration": ioBufferDuration + ] + } +} + +final class WorkerAudioRouteCoordinator: @unchecked Sendable { + static let shared = WorkerAudioRouteCoordinator() + + private let stateQueue = DispatchQueue(label: "worker.audio.route.state") + private let deactivationQueue = DispatchQueue( + label: "worker.audio.route.deactivation", + qos: .userInitiated + ) + private var activeLease: WorkerAudioRouteLease? + private var routeGeneration: UInt64 = 0 + + @discardableResult + func acquire( + owner: WorkerAudioRouteOwner, + mode: StreamingMode, + reason: String, + forceSpeaker: Bool = false, + preferredSampleRate: Double? = nil, + preferredIOBufferDuration: TimeInterval = 0.02 + ) throws -> WorkerAudioRouteSnapshot { + let lease = stateQueue.sync { () -> WorkerAudioRouteLease in + routeGeneration &+= 1 + let lease = WorkerAudioRouteLease( + owner: owner, + token: UUID(), + generation: routeGeneration + ) + activeLease = lease + return lease + } + + let session = AVAudioSession.sharedInstance() + do { + var options: AVAudioSession.CategoryOptions = [.allowBluetoothHFP] + let audioMode: AVAudioSession.Mode + + switch mode { + case .iPhone: + audioMode = .voiceChat + options.formUnion([.defaultToSpeaker, .duckOthers]) + case .glasses: + audioMode = forceSpeaker ? .voiceChat : .videoChat + if forceSpeaker { + options.formUnion([.defaultToSpeaker]) + } + } + + try session.setCategory(.playAndRecord, mode: audioMode, options: options) + if let preferredSampleRate { + try session.setPreferredSampleRate(preferredSampleRate) + } + try session.setPreferredIOBufferDuration(preferredIOBufferDuration) + try session.setActive(true, options: .notifyOthersOnDeactivation) + + var preferredInputName: String? + if mode == .glasses, !forceSpeaker, let input = preferredBluetoothHandsFreeInput(session) { + try session.setPreferredInput(input) + preferredInputName = "\(input.portType.rawValue):\(input.portName)" + try session.setActive(true, options: .notifyOthersOnDeactivation) + } + + let routeBeforeOverride = session.currentRoute + let hasHandsFree = hasBluetoothHandsFreeRoute(routeBeforeOverride) + let fallbackMessage: String? + + if mode == .iPhone || forceSpeaker { + try session.overrideOutputAudioPort(.speaker) + fallbackMessage = mode == .glasses + ? "Glasses audio route unavailable. Using phone speaker." + : nil + } else if hasHandsFree { + try session.overrideOutputAudioPort(.none) + fallbackMessage = nil + } else { + try session.overrideOutputAudioPort(.speaker) + fallbackMessage = "Meta audio route unavailable. Using phone audio until Bluetooth HFP connects." + } + + let snapshot = WorkerAudioRouteSnapshot( + lease: lease, + owner: owner, + mode: mode, + reason: reason, + category: session.category.rawValue, + audioMode: session.mode.rawValue, + inputs: describePorts(session.currentRoute.inputs), + outputs: describePorts(session.currentRoute.outputs), + preferredInput: preferredInputName, + usesHandsFreeRoute: hasBluetoothHandsFreeRoute(session.currentRoute), + fallbackMessage: fallbackMessage, + sampleRate: session.sampleRate, + ioBufferDuration: session.ioBufferDuration + ) + log(snapshot) + Task { + await WorkerTelemetry.shared.record( + "audio_route_acquired", + source: "ios_audio", + stage: owner.rawValue, + payload: snapshot.payload + ) + } + return snapshot + } catch { + stateQueue.sync { + if activeLease?.token == lease.token { + activeLease = nil + } + } + throw error + } + } + + func release( + lease: WorkerAudioRouteLease, + afterAudioGraphStops: @escaping () async -> Void = {} + ) async { + let didRelease = stateQueue.sync { () -> Bool in + guard activeLease?.token == lease.token else { return false } + activeLease = nil + return true + } + + guard didRelease else { + let currentLease = stateQueue.sync { activeLease } + print("[Audio] Stale release ignored; newer session active") + var payload: [String: Any] = [ + "release_owner": lease.owner.rawValue, + "release_token": lease.token.uuidString, + "release_generation": lease.generation + ] + if let currentLease { + payload["current_owner"] = currentLease.owner.rawValue + payload["current_token"] = currentLease.token.uuidString + payload["current_generation"] = currentLease.generation + } else { + payload["current_owner"] = NSNull() + payload["current_token"] = NSNull() + payload["current_generation"] = NSNull() + } + await WorkerTelemetry.shared.record( + "audio_route_stale_release_ignored", + source: "ios_audio", + stage: lease.owner.rawValue, + payload: payload + ) + return + } + + NSLog("[WorkerAudio] released owner=%@ token=%@", lease.owner.rawValue, lease.token.uuidString) + await WorkerTelemetry.shared.record( + "audio_route_released", + source: "ios_audio", + stage: lease.owner.rawValue, + payload: lease.payload + ) + + await afterAudioGraphStops() + await Task.yield() + + let currentState = stateQueue.sync { () -> (lease: WorkerAudioRouteLease?, generation: UInt64) in + (activeLease, routeGeneration) + } + let currentLease = currentState.lease + let currentGeneration = currentState.generation + let shouldDeactivate = currentLease == nil && currentGeneration == lease.generation + + guard shouldDeactivate else { + var payload: [String: Any] = [ + "owner": lease.owner.rawValue, + "token": lease.token.uuidString, + "release_generation": lease.generation, + "current_generation": currentGeneration + ] + payload["current_owner"] = currentLease?.owner.rawValue ?? NSNull() + payload["current_token"] = currentLease?.token.uuidString ?? NSNull() + NSLog( + "[WorkerAudio] skip deactivate owner=%@ token=%@ generation=%llu currentOwner=%@ currentGeneration=%llu", + lease.owner.rawValue, + lease.token.uuidString, + lease.generation, + currentLease?.owner.rawValue ?? "none", + currentGeneration + ) + await WorkerTelemetry.shared.record( + "audio_route_deactivation_skipped", + source: "ios_audio", + stage: lease.owner.rawValue, + payload: payload + ) + return + } + + let result = await deactivateSharedAudioSession() + switch result { + case .success: + NSLog("[WorkerAudio] deactivated owner=%@ token=%@", lease.owner.rawValue, lease.token.uuidString) + await WorkerTelemetry.shared.record( + "audio_route_deactivated", + source: "ios_audio", + stage: lease.owner.rawValue, + payload: lease.payload + ) + case .failure(let error): + NSLog("[WorkerAudio] deactivate failed owner=%@ token=%@ error=%@", + lease.owner.rawValue, lease.token.uuidString, error.localizedDescription) + await WorkerTelemetry.shared.record( + "audio_route_deactivate_failed", + source: "ios_audio", + stage: lease.owner.rawValue, + payload: [ + "owner": lease.owner.rawValue, + "token": lease.token.uuidString, + "generation": lease.generation, + "error": error.localizedDescription + ] + ) + } + } + + private func deactivateSharedAudioSession() async -> Result { + await withCheckedContinuation { (continuation: CheckedContinuation, Never>) in + deactivationQueue.async { + do { + try AVAudioSession.sharedInstance().setActive( + false, + options: .notifyOthersOnDeactivation + ) + continuation.resume(returning: .success(())) + } catch { + continuation.resume(returning: .failure(error)) + } + } + } + } + + private func preferredBluetoothHandsFreeInput(_ session: AVAudioSession) -> AVAudioSessionPortDescription? { + session.availableInputs?.first { + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE + } + } + + private func hasBluetoothHandsFreeRoute(_ route: AVAudioSessionRouteDescription) -> Bool { + route.inputs.contains { + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE + } || route.outputs.contains { + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE + } + } + + private func describePorts(_ ports: [AVAudioSessionPortDescription]) -> [String] { + ports.map { "\($0.portType.rawValue):\($0.portName)" } + } + + private func log(_ snapshot: WorkerAudioRouteSnapshot) { + NSLog( + "[WorkerAudio] owner=%@ mode=%@ reason=%@ inputs=%@ outputs=%@ fallback=%@", + snapshot.owner.rawValue, + snapshot.mode == .iPhone ? "iphone" : "glasses", + snapshot.reason, + snapshot.inputs.joined(separator: ","), + snapshot.outputs.joined(separator: ","), + snapshot.fallbackMessage ?? "none" + ) + } +} + +final class AudioManager: @unchecked Sendable { var onAudioCaptured: ((Data) -> Void)? private let audioEngine = AVAudioEngine() + private let audioLifecycleQueue = DispatchQueue( + label: "gemini.audio.lifecycle", + qos: .userInitiated + ) private let playerNode = AVAudioPlayerNode() private var isCapturing = false + private var isInputTapInstalled = false + private var isPlayerNodeAttached = false private var wasCapturingBeforeInterruption = false private var useIPhoneMode = false + private var audioRouteLease: WorkerAudioRouteLease? private let outputFormat: AVAudioFormat @@ -35,33 +365,22 @@ class AudioManager { func setupAudioSession(useIPhoneMode: Bool = false) throws { self.useIPhoneMode = useIPhoneMode - let session = AVAudioSession.sharedInstance() - // voiceChat: aggressive echo cancellation (mic + speaker co-located on phone) - // videoChat: mild AEC (mic on glasses, speaker on glasses) - // When Speaker Output is ON, speaker is on phone so always use voiceChat AEC let forceSpeaker = SettingsManager.shared.speakerOutputEnabled - if useIPhoneMode || forceSpeaker { - try session.setCategory( - .playAndRecord, - mode: .voiceChat, - options: [.defaultToSpeaker, .allowBluetoothHFP, .mixWithOthers] - ) - } else { - try session.setCategory( - .playAndRecord, - mode: .videoChat, - options: [.allowBluetoothHFP, .mixWithOthers, .defaultToSpeaker] - ) - } - try session.setPreferredSampleRate(GeminiConfig.inputAudioSampleRate) - try session.setPreferredIOBufferDuration(0.064) - try session.setActive(true) - if SettingsManager.shared.speakerOutputEnabled { - try session.overrideOutputAudioPort(.speaker) - NSLog("[Audio] Speaker output override: ON (iPhone speaker)") + let captureMode: StreamingMode = useIPhoneMode ? .iPhone : .glasses + let snapshot = try WorkerAudioRouteCoordinator.shared.acquire( + owner: .aiGuide, + mode: captureMode, + reason: "gemini_live", + forceSpeaker: forceSpeaker, + preferredSampleRate: GeminiConfig.inputAudioSampleRate, + preferredIOBufferDuration: 0.064 + ) + audioRouteLease = snapshot.lease + if let fallback = snapshot.fallbackMessage { + NSLog("[Audio] %@", fallback) } - NSLog("[Audio] Session mode: %@", useIPhoneMode ? "voiceChat (iPhone)" : "videoChat (glasses)") + removeObservers() setupInterruptionHandling() setupAppLifecycleObservers() } @@ -69,13 +388,23 @@ class AudioManager { func startCapture() throws { guard !isCapturing else { return } - audioEngine.attach(playerNode) + if isInputTapInstalled { + audioEngine.inputNode.removeTap(onBus: 0) + isInputTapInstalled = false + } + + if !isPlayerNodeAttached { + audioEngine.attach(playerNode) + isPlayerNodeAttached = true + } + let playerFormat = AVAudioFormat( commonFormat: .pcmFormatFloat32, sampleRate: GeminiConfig.outputAudioSampleRate, channels: GeminiConfig.audioChannels, interleaved: false )! + audioEngine.disconnectNodeOutput(playerNode) audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: playerFormat) let inputNode = audioEngine.inputNode @@ -111,6 +440,10 @@ class AudioManager { guard let self else { return } tapCount += 1 + let rmsValue = self.computeRMS(buffer) + if tapCount % 15 == 0 { + print("[Audio Monitor] App Mic Level: \(rmsValue)") + } let pcmData: Data if let converter { @@ -143,10 +476,16 @@ class AudioManager { } } } + isInputTapInstalled = true - try audioEngine.start() - playerNode.play() - isCapturing = true + do { + try audioEngine.start() + playerNode.play() + isCapturing = true + } catch { + tearDownEngineGraph(flushPendingAudio: false) + throw error + } } func playAudio(data: Data) { @@ -180,26 +519,43 @@ class AudioManager { } func stopPlayback() { + guard isPlayerNodeAttached else { return } playerNode.stop() - playerNode.play() + if isCapturing { + playerNode.play() + } } - func stopCapture() { - guard isCapturing else { return } - audioEngine.inputNode.removeTap(onBus: 0) - playerNode.stop() - audioEngine.stop() - audioEngine.detach(playerNode) - isCapturing = false - // Flush any remaining accumulated audio - sendQueue.async { - if !self.accumulatedData.isEmpty { - let chunk = self.accumulatedData - self.accumulatedData = Data() - self.onAudioCaptured?(chunk) + func stopCapture() async { + // AVAudioEngine graph teardown runs on a serial lifecycle queue so callers + // can await the barrier without blocking the MainActor on audio hardware. + await withCheckedContinuation { (continuation: CheckedContinuation) in + audioLifecycleQueue.async { [weak self] in + guard let self else { + continuation.resume() + return + } + + guard self.isCapturing || self.isInputTapInstalled || self.isPlayerNodeAttached else { + self.removeObservers() + continuation.resume() + return + } + + self.tearDownEngineGraph(flushPendingAudio: true) { + self.removeObservers() + continuation.resume() + } + } + } + + let lease = audioRouteLease + audioRouteLease = nil + if let lease { + await WorkerAudioRouteCoordinator.shared.release(lease: lease) { [weak self] in + await self?.waitForAudioGraphClean() } } - removeObservers() } // MARK: - Audio Interruption & Route Change Handling @@ -298,6 +654,23 @@ class AudioManager { } } + private func preferredBluetoothHFPInput(_ session: AVAudioSession) -> AVAudioSessionPortDescription? { + session.availableInputs?.first { + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE + } + } + + private func hasBluetoothHandsFreeRoute(_ route: AVAudioSessionRouteDescription) -> Bool { + route.inputs.contains { + $0.portType == .bluetoothHFP || + $0.portType == .bluetoothLE + } || + route.outputs.contains { + $0.portType == .bluetoothHFP || + $0.portType == .bluetoothLE + } + } + private func resumeAudioAfterInterruption() { NSLog("[Audio] Resuming audio after interruption") let audioSession = AVAudioSession.sharedInstance() @@ -315,11 +688,7 @@ class AudioManager { NSLog("[Audio] Attempting audio reset") let wasCapturing = isCapturing - if audioEngine.isRunning { - audioEngine.stop() - } - audioEngine.inputNode.removeTap(onBus: 0) - isCapturing = false + tearDownEngineGraph(flushPendingAudio: false) if wasCapturing { do { @@ -332,6 +701,64 @@ class AudioManager { } } + private func tearDownEngineGraph(flushPendingAudio: Bool, completion: (() -> Void)? = nil) { + if isInputTapInstalled { + audioEngine.inputNode.removeTap(onBus: 0) + isInputTapInstalled = false + } + if audioEngine.isRunning { + audioEngine.stop() + } + if playerNode.isPlaying { + playerNode.stop() + } + if isPlayerNodeAttached { + audioEngine.disconnectNodeOutput(playerNode) + audioEngine.detach(playerNode) + isPlayerNodeAttached = false + } + isCapturing = false + + sendQueue.async { + defer { completion?() } + guard !self.accumulatedData.isEmpty else { return } + let chunk = self.accumulatedData + self.accumulatedData = Data() + if flushPendingAudio { + self.onAudioCaptured?(chunk) + } + } + } + + private func waitForAudioGraphClean() async { + await withCheckedContinuation { (continuation: CheckedContinuation) in + audioLifecycleQueue.async { [weak self] in + guard let self else { + continuation.resume() + return + } + + if self.isInputTapInstalled { + self.audioEngine.inputNode.removeTap(onBus: 0) + self.isInputTapInstalled = false + } + if self.audioEngine.isRunning { + self.audioEngine.stop() + } + if self.playerNode.isPlaying { + self.playerNode.stop() + } + if self.isPlayerNodeAttached { + self.audioEngine.disconnectNodeOutput(self.playerNode) + self.audioEngine.detach(self.playerNode) + self.isPlayerNodeAttached = false + } + self.isCapturing = false + continuation.resume() + } + } + } + private func removeObservers() { if let observer = interruptionObserver { NotificationCenter.default.removeObserver(observer) diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift index c319a6e6..db15f596 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift @@ -5,23 +5,10 @@ struct GeminiLiveCredential: Equatable { let queryParameterName: String let websocketBaseURL: String let model: String - - static func apiKey(_ apiKey: String = GeminiConfig.apiKey) -> GeminiLiveCredential? { - let trimmed = apiKey.trimmingCharacters(in: .whitespacesAndNewlines) - guard trimmed != "YOUR_GEMINI_API_KEY", !trimmed.isEmpty else { return nil } - return GeminiLiveCredential( - token: trimmed, - queryParameterName: "key", - websocketBaseURL: GeminiConfig.websocketBaseURL, - model: GeminiConfig.model - ) - } } enum GeminiConfig { - static let websocketBaseURL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent" static let ephemeralTokenWebsocketBaseURL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained" - // Use a model that exists for this API key and supports native audio interactions. static let model = "models/gemini-live-2.5-flash-native-audio" static let inputAudioSampleRate: Double = 16000 @@ -29,11 +16,9 @@ enum GeminiConfig { static let audioChannels: UInt32 = 1 static let audioBitsPerSample: UInt32 = 16 - static let videoFrameInterval: TimeInterval = 1.0 + static let videoFrameInterval: TimeInterval = 3.0 static let videoJPEGQuality: CGFloat = 0.5 - static var systemInstruction: String { SettingsManager.shared.geminiSystemPrompt } - static let defaultSystemInstruction = """ You are a live frontline worker copilot for SOP execution sessions. @@ -52,16 +37,10 @@ enum GeminiConfig { static var deviceID: String { SettingsManager.shared.deviceID } static var workerLoginCode: String { SettingsManager.shared.workerLoginCode } static var workerEmail: String { SettingsManager.shared.workerEmail } - static var apiKey: String { SettingsManager.shared.geminiAPIKey } static var opsBaseURL: String { SettingsManager.shared.opsBaseURL } static var adminBaseURL: String { SettingsManager.shared.adminBaseURL } static var signalBaseURL: String { SettingsManager.shared.signalBaseURL } - static var openClawHost: String { SettingsManager.shared.openClawHost } - static var openClawPort: Int { SettingsManager.shared.openClawPort } - static var openClawTailscaleIP: String { SettingsManager.shared.openClawTailscaleIP } - static var openClawBearerToken: String { SettingsManager.shared.openClawBearerToken } - static var openClawHookToken: String { SettingsManager.shared.openClawHookToken } - static var openClawGatewayToken: String { SettingsManager.shared.openClawGatewayToken } + static var workerAPIBearerToken: String { SettingsManager.shared.workerAPIBearerToken } static func websocketURL(credential: GeminiLiveCredential) -> URL? { guard var components = URLComponents(string: credential.websocketBaseURL) else { return nil } @@ -73,10 +52,6 @@ enum GeminiConfig { return components.url } - static var isConfigured: Bool { - return apiKey != "YOUR_GEMINI_API_KEY" && !apiKey.isEmpty - } - static var isOpsConfigured: Bool { let trimmed = opsBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) return !trimmed.isEmpty && !trimmed.contains("YOUR_") @@ -86,10 +61,4 @@ enum GeminiConfig { let trimmed = adminBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) return !trimmed.isEmpty && !trimmed.contains("YOUR_") } - - static var isOpenClawConfigured: Bool { - return openClawGatewayToken != "YOUR_OPENCLAW_GATEWAY_TOKEN" - && !openClawGatewayToken.isEmpty - && openClawHost != "http://YOUR_MAC_HOSTNAME.local" - } } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift index a6c69472..6ee41683 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift @@ -21,8 +21,6 @@ class GeminiLiveService: ObservableObject { var onDisconnected: ((String?) -> Void)? var onInputTranscription: ((String) -> Void)? var onOutputTranscription: ((String) -> Void)? - var onToolCall: ((GeminiToolCall) -> Void)? - var onToolCallCancellation: ((GeminiToolCallCancellation) -> Void)? var onSocketOpened: (() -> Void)? var onSocketClosed: ((String?) -> Void)? @@ -33,11 +31,12 @@ class GeminiLiveService: ObservableObject { private var webSocketTask: URLSessionWebSocketTask? private var receiveTask: Task? private var connectContinuation: CheckedContinuation? + private var closeWaitContinuation: CheckedContinuation? private let delegate = WebSocketDelegate() private var urlSession: URLSession! private let sendQueue = DispatchQueue(label: "gemini.send", qos: .userInitiated) private var latestVideoFrameBase64: String? - private var setupSystemInstruction: String = GeminiConfig.systemInstruction + private var setupSystemInstruction: String = GeminiConfig.defaultSystemInstruction private var setupModel: String = GeminiConfig.model private var videoFrameSendCount: Int64 = 0 private var videoFrameStatsWindowStart = CACurrentMediaTime() @@ -102,6 +101,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .disconnected self.isModelSpeaking = false + self.resolveCloseWait() self.onSocketClosed?("Connection closed (code \(code.rawValue): \(reasonStr))") self.onDisconnected?("Connection closed (code \(code.rawValue): \(reasonStr))") } @@ -122,6 +122,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .error(msg) self.isModelSpeaking = false + self.resolveCloseWait() self.onSocketClosed?(msg) self.onDisconnected?(msg) } @@ -153,8 +154,43 @@ class GeminiLiveService: ObservableObject { delegate.onOpen = nil delegate.onClose = nil delegate.onError = nil - onToolCall = nil - onToolCallCancellation = nil + onSocketOpened = nil + onSocketClosed = nil + connectionState = .disconnected + isModelSpeaking = false + resolveConnect(success: false) + resolveCloseWait() + } + + func disconnectAndWaitForClose(timeout: TimeInterval = 1.0) async { + receiveTask?.cancel() + receiveTask = nil + + guard let task = webSocketTask, connectionState != .disconnected else { + disconnect() + return + } + + resolveCloseWait() + await withCheckedContinuation { (continuation: CheckedContinuation) in + closeWaitContinuation = continuation + let boundedTimeout = DispatchTimeInterval.milliseconds(Int(max(timeout, 0.05) * 1_000)) + DispatchQueue.main.asyncAfter(deadline: .now() + boundedTimeout) { [weak self] in + Task { @MainActor in + guard let self else { return } + if self.closeWaitContinuation != nil { + NSLog("[Gemini] WebSocket close wait timed out") + } + self.resolveCloseWait() + } + } + task.cancel(with: .normalClosure, reason: nil) + } + + webSocketTask = nil + delegate.onOpen = nil + delegate.onClose = nil + delegate.onError = nil onSocketOpened = nil onSocketClosed = nil connectionState = .disconnected @@ -211,14 +247,6 @@ class GeminiLiveService: ObservableObject { } } - func sendToolResponse(_ response: [String: Any]) { - sendQueue.async { [weak self] in - Task { @MainActor [weak self] in - self?.sendJSON(response) - } - } - } - func sendTextMessage(_ text: String) { guard connectionState == .ready else { return } sendQueue.async { [weak self] in @@ -281,17 +309,18 @@ class GeminiLiveService: ObservableObject { } } + private func resolveCloseWait() { + guard let cont = closeWaitContinuation else { return } + closeWaitContinuation = nil + cont.resume() + } + private func resolvedSystemInstruction(_ override: String?) -> String { let candidate = override?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !candidate.isEmpty { return candidate } - let configured = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) - if !configured.isEmpty { - return configured - } - return GeminiConfig.defaultSystemInstruction } @@ -310,11 +339,6 @@ class GeminiLiveService: ObservableObject { ["text": setupSystemInstruction] ] ], - "tools": [ - [ - "functionDeclarations": ToolDeclarations.allDeclarations() - ] - ], "realtimeInputConfig": [ "automaticActivityDetection": [ "disabled": false, @@ -350,6 +374,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .error("WebSocket send failed: \(error.localizedDescription)") self.isModelSpeaking = false + self.resolveCloseWait() self.onSocketClosed?(error.localizedDescription) self.onDisconnected?(error.localizedDescription) } @@ -380,6 +405,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .disconnected self.isModelSpeaking = false + self.resolveCloseWait() self.onDisconnected?(reason) } } @@ -404,6 +430,7 @@ class GeminiLiveService: ObservableObject { connectionState = .error(full) isModelSpeaking = false resolveConnect(success: false) + resolveCloseWait() onSocketClosed?(full) onDisconnected?(full) return @@ -430,25 +457,12 @@ class GeminiLiveService: ObservableObject { let seconds = timeLeft?["seconds"] as? Int ?? 0 connectionState = .disconnected isModelSpeaking = false + resolveCloseWait() onSocketClosed?("Server closing (time left: \(seconds)s)") onDisconnected?("Server closing (time left: \(seconds)s)") return } - // Tool call from model (top-level message, not inside serverContent) - if let toolCall = GeminiToolCall(json: json) { - NSLog("[Gemini] Tool call received: %d function(s)", toolCall.functionCalls.count) - onToolCall?(toolCall) - return - } - - // Tool call cancellation (user interrupted during tool execution) - if let cancellation = GeminiToolCallCancellation(json: json) { - NSLog("[Gemini] Tool call cancellation: %@", cancellation.ids.joined(separator: ", ")) - onToolCallCancellation?(cancellation) - return - } - // Server content if let serverContent = json["serverContent"] as? [String: Any] { if let interrupted = serverContent["interrupted"] as? Bool, interrupted { diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift index 7954dfcc..804b1c94 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift @@ -25,6 +25,12 @@ final class GeminiLiveSpotter { let evidenceTimestamp: String let threshold: Double let autoComplete: Bool + let advancedToStepIndex: Int? + let completedSop: Bool + let evidenceWindowSatisfied: Bool? + let activeDurationSatisfied: Bool? + let stableObservations: Int? + let stableObservationsRequired: Int? } func configure(api: WorkerAdminAPI?) { @@ -34,7 +40,8 @@ final class GeminiLiveSpotter { func detectVisibleItemMatches( image: UIImage, items: [SpotterRequestItem], - sessionID: String? + sessionID: String?, + elapsedActiveMs: Int? = nil ) async throws -> [SpotterMatch] { guard !items.isEmpty else { return [] } guard let api, let sessionID, !sessionID.isEmpty else { return [] } @@ -59,7 +66,8 @@ final class GeminiLiveSpotter { imageMimeType: imagePayload.mimeType, capturedAt: capturedAt, critical: item.critical, - allowAIComplete: item.validation.lowercased() == "visual" + allowAIComplete: item.validation.lowercased() == "visual", + elapsedActiveMs: elapsedActiveMs ) ) @@ -71,7 +79,13 @@ final class GeminiLiveSpotter { reason: response.reason, evidenceTimestamp: response.evidenceTimestamp, threshold: response.threshold ?? (item.critical ? 0.94 : ((item.evidenceRequired || item.skipRisk == "high") ? 0.9 : 0.88)), - autoComplete: response.autoComplete + autoComplete: response.autoComplete, + advancedToStepIndex: response.advancedToStepIndex, + completedSop: response.completedSop ?? false, + evidenceWindowSatisfied: response.evidenceWindowSatisfied, + activeDurationSatisfied: response.activeDurationSatisfied, + stableObservations: response.stableObservations, + stableObservationsRequired: response.stableObservationsRequired ) ) } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift index 265a906d..c65d3038 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift @@ -1,37 +1,39 @@ import Foundation import SwiftUI +import UIKit @MainActor class GeminiSessionViewModel: ObservableObject { @Published var isGeminiActive: Bool = false + @Published var isAudioReady: Bool = false @Published var connectionState: GeminiConnectionState = .disconnected @Published var isModelSpeaking: Bool = false @Published var errorMessage: String? @Published var userTranscript: String = "" @Published var aiTranscript: String = "" - @Published var toolCallStatus: ToolCallStatus = .idle - @Published var openClawConnectionState: OpenClawConnectionState = .notConfigured + + private struct LiveSessionConfig { + let credential: GeminiLiveCredential + let systemInstruction: String + let diagnosticsID: String? + let provider: String? + } private let geminiService = GeminiLiveService() - private let sopRelayClient = SopRelayClient() - private let openClawBridge = OpenClawBridge() - private var toolCallRouter: ToolCallRouter? private let audioManager = AudioManager() - private let eventClient = OpenClawEventClient() private var lastVideoFrameTime: Date = .distantPast private var stateObservation: Task? - private var heartbeatTask: Task? - private var heartbeatTimeoutTask: Task? - private var currentSopSessionId: String? - private var isSopSessionTerminated: Bool = true - private var isFinalizingSession: Bool = false - private var pendingSystemInstruction: String? - private var currentSessionInstruction: String? private weak var workerAdminAPI: WorkerAdminAPI? private var adminExecutionSessionID: String? private var currentLiveCredential: GeminiLiveCredential? + private var currentSessionInstruction: String? + private var lastDiagnosticsID: String? + private var isStoppingSession = false var streamingMode: StreamingMode = .glasses + var onInputCommand: ((String) -> Void)? + var onInputAudioChunk: ((Data) -> Void)? + var onOutputAudioChunk: ((Data) -> Void)? func configureWorkerAdminAPI(_ api: WorkerAdminAPI?, sessionID: String? = nil) { workerAdminAPI = api @@ -39,76 +41,94 @@ class GeminiSessionViewModel: ObservableObject { } func startSession(systemInstruction: String? = nil) async { - pendingSystemInstruction = normalizedSystemInstruction(systemInstruction) guard !isGeminiActive else { await refreshSessionInstruction(systemInstruction) return } + errorMessage = nil userTranscript = "" aiTranscript = "" + isStoppingSession = false - guard let credential = await resolveLiveCredential() else { - errorMessage = "Gemini Live token unavailable. Confirm the admin URL, worker bearer token, and server Gemini key." + guard let liveConfig = await resolveLiveSessionConfig(fallbackInstruction: systemInstruction) else { + errorMessage = "Gemini Live token unavailable. Check Admin AI Settings and the worker backend connection." return } - currentLiveCredential = credential + currentLiveCredential = liveConfig.credential + currentSessionInstruction = liveConfig.systemInstruction + lastDiagnosticsID = liveConfig.diagnosticsID - isGeminiActive = true + isAudioReady = false configureRealtimeCallbacks() - - await openClawBridge.checkConnection() - openClawBridge.resetSession() - toolCallRouter = ToolCallRouter(bridge: openClawBridge) startStateObservation() do { try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) } catch { - resetToIdle(receiptMessage: "Audio setup failed: \(error.localizedDescription)") + await resetToIdle(message: "Audio setup failed: \(error.localizedDescription)") return } - let resolvedInstruction = resolvedSystemInstruction() let setupOk = await geminiService.connect( - systemInstruction: resolvedInstruction, - credential: credential + systemInstruction: liveConfig.systemInstruction, + credential: liveConfig.credential ) if !setupOk { - let message: String - if case .error(let err) = geminiService.connectionState { - message = err - } else { - message = "Failed to connect to Gemini" - } - resetToIdle(receiptMessage: message) + let message = liveConnectionError( + fallback: "Failed to connect to Gemini", + diagnosticsID: liveConfig.diagnosticsID + ) + await resetToIdle(message: message) + await recordTelemetry( + "gemini_live_connect_failed", + stage: "failed", + payload: [ + "diagnostics_id": liveConfig.diagnosticsID ?? NSNull(), + "error": message + ] + ) return } do { try audioManager.startCapture() } catch { - resetToIdle(receiptMessage: "Mic capture failed: \(error.localizedDescription)") + await resetToIdle(message: "Mic capture failed: \(error.localizedDescription)") return } - connectEventStreamIfNeeded() - currentSessionInstruction = resolvedInstruction + isGeminiActive = true + isAudioReady = true + await recordTelemetry( + "gemini_live_session_started", + stage: "ready", + payload: [ + "model": liveConfig.credential.model, + "provider": liveConfig.provider ?? "gemini", + "diagnostics_id": liveConfig.diagnosticsID ?? NSNull() + ] + ) } - func stopSession() { - finalizeSessionWithReceipt(status: "terminated") + func stopSession() async { + await resetToIdle(message: nil) } func refreshSessionInstruction(_ systemInstruction: String?) async { - pendingSystemInstruction = normalizedSystemInstruction(systemInstruction) guard isGeminiActive else { return } + guard let liveConfig = await resolveLiveSessionConfig(fallbackInstruction: systemInstruction) else { + await resetToIdle(message: "Gemini Live token refresh failed. Check Admin AI Settings and try again.") + return + } - let resolvedInstruction = resolvedSystemInstruction() - guard resolvedInstruction != currentSessionInstruction else { return } + guard liveConfig.systemInstruction != currentSessionInstruction || + liveConfig.credential.model != currentLiveCredential?.model else { + return + } - await reconnectTransport(with: resolvedInstruction) + await reconnectTransport(with: liveConfig) } func sendVideoFrameIfThrottled(image: UIImage) { @@ -120,189 +140,19 @@ class GeminiSessionViewModel: ObservableObject { geminiService.sendVideoFrame(image: image) } - private func handleSopLogToolCall(_ call: GeminiFunctionCall) { - let stepName = (call.args["step_name"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - guard !stepName.isEmpty else { - geminiService.sendToolResponse(buildToolResponse( - callId: call.id, - name: call.name, - result: .failure("Missing required argument: step_name") - )) - return - } - - let sessionId: String - if let existing = currentSopSessionId { - sessionId = existing - } else { - let created = UUID().uuidString - currentSopSessionId = created - isSopSessionTerminated = false - sessionId = created - } - - let imageBase64 = (call.args["frame_data"] as? String)?.isEmpty == false - ? (call.args["frame_data"] as? String ?? "") - : ((call.args["image_base64"] as? String)?.isEmpty == false - ? (call.args["image_base64"] as? String ?? "") - : (geminiService.lastVideoFrameBase64 ?? "")) - - sopRelayClient.postSopLog( - tailscaleIP: GeminiConfig.openClawTailscaleIP, - sessionID: sessionId, - stepName: stepName, - timestampISO8601: ISO8601DateFormatter().string(from: Date()), - imageBase64: imageBase64 - ) - - geminiService.sendToolResponse(buildToolResponse( - callId: call.id, - name: call.name, - result: .success("SOP step forwarded") - )) - } - - private func connectEventStreamIfNeeded() { - eventClient.disconnect() - guard SettingsManager.shared.proactiveNotificationsEnabled else { return } - - eventClient.onNotification = { [weak self] text in - guard let self else { return } - Task { @MainActor in - guard self.isGeminiActive, self.connectionState == .ready else { return } - self.geminiService.sendTextMessage(text) - } - } - eventClient.connect() - } - - private func startSopHeartbeatSession() { - if currentSopSessionId != nil && !isSopSessionTerminated { return } - - let sessionId = UUID().uuidString - currentSopSessionId = sessionId - isSopSessionTerminated = false - - heartbeatTask?.cancel() - heartbeatTimeoutTask?.cancel() - - heartbeatTask = Task { [weak self] in - guard let self else { return } - - self.sopRelayClient.postHeartbeat( - tailscaleIP: GeminiConfig.openClawTailscaleIP, - sessionID: sessionId, - status: "active" - ) - - while !Task.isCancelled && !self.isSopSessionTerminated { - self.sopRelayClient.postHeartbeat( - tailscaleIP: GeminiConfig.openClawTailscaleIP, - sessionID: sessionId, - status: "active" - ) - try? await Task.sleep(nanoseconds: 3_000_000_000) - } - } - - heartbeatTimeoutTask = Task { [weak self] in - try? await Task.sleep(nanoseconds: 60_000_000_000) - await MainActor.run { - self?.finalizeSessionWithReceipt(status: "terminated") - } - } - } - - private func finalizeSessionWithReceipt(status: String) { - if isFinalizingSession { return } - - guard let sessionId = currentSopSessionId, !isSopSessionTerminated else { - resetToIdle(receiptMessage: nil) - return - } - - isFinalizingSession = true - isSopSessionTerminated = true - heartbeatTask?.cancel() - heartbeatTask = nil - heartbeatTimeoutTask?.cancel() - heartbeatTimeoutTask = nil - - Task { [weak self] in - guard let self else { return } - - let receiptMessage = await self.sopRelayClient.postHeartbeatForReceipt( - tailscaleIP: GeminiConfig.openClawTailscaleIP, - sessionID: sessionId, - status: status - ) - - await MainActor.run { - self.resetToIdle(receiptMessage: receiptMessage) - } - } - } - - private func resetToIdle(receiptMessage: String?) { - eventClient.disconnect() - geminiService.onDisconnected = nil - geminiService.onSocketClosed = nil - geminiService.onSocketOpened = nil - - toolCallRouter?.cancelAll() - toolCallRouter = nil - audioManager.stopCapture() - geminiService.disconnect() - stateObservation?.cancel() - stateObservation = nil - heartbeatTask?.cancel() - heartbeatTask = nil - heartbeatTimeoutTask?.cancel() - heartbeatTimeoutTask = nil - - isGeminiActive = false - connectionState = .disconnected - isModelSpeaking = false - userTranscript = "" - aiTranscript = "" - toolCallStatus = .idle - errorMessage = normalizedReceiptMessage(receiptMessage) - currentSessionInstruction = nil - - currentSopSessionId = nil - isSopSessionTerminated = true - isFinalizingSession = false - } - - private func normalizedSystemInstruction(_ instruction: String?) -> String? { - let trimmed = instruction?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - return trimmed.isEmpty ? nil : trimmed - } - - private func resolvedSystemInstruction() -> String { - if let pendingSystemInstruction { - return pendingSystemInstruction - } - - let configured = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) - if !configured.isEmpty { - return configured - } - - return GeminiConfig.defaultSystemInstruction - } - private func configureRealtimeCallbacks() { audioManager.onAudioCaptured = { [weak self] data in guard let self else { return } Task { @MainActor in let speakerOnPhone = self.streamingMode == .iPhone || SettingsManager.shared.speakerOutputEnabled if speakerOnPhone && self.geminiService.isModelSpeaking { return } + self.onInputAudioChunk?(data) self.geminiService.sendAudio(data: data) } } geminiService.onAudioReceived = { [weak self] data in + self?.onOutputAudioChunk?(data) self?.audioManager.playAudio(data: data) } @@ -322,6 +172,7 @@ class GeminiSessionViewModel: ObservableObject { Task { @MainActor in self.userTranscript += text self.aiTranscript = "" + self.onInputCommand?(self.userTranscript) } } @@ -335,46 +186,16 @@ class GeminiSessionViewModel: ObservableObject { geminiService.onDisconnected = { [weak self] reason in guard let self else { return } Task { @MainActor in - guard self.isGeminiActive, !self.isFinalizingSession else { return } - self.resetToIdle(receiptMessage: "Connection lost: \(reason ?? "Unknown error")") - } - } - - geminiService.onSocketOpened = { [weak self] in - guard let self else { return } - Task { @MainActor in - self.startSopHeartbeatSession() - } - } - - geminiService.onSocketClosed = { [weak self] _ in - guard let self else { return } - Task { @MainActor in - guard self.isGeminiActive, !self.isFinalizingSession else { return } - self.finalizeSessionWithReceipt(status: "terminated") - } - } - - geminiService.onToolCall = { [weak self] toolCall in - guard let self else { return } - Task { @MainActor in - for call in toolCall.functionCalls { - if call.name == "log_sop_step" { - self.handleSopLogToolCall(call) - continue - } - - self.toolCallRouter?.handleToolCall(call) { [weak self] response in - self?.geminiService.sendToolResponse(response) - } - } + guard self.isGeminiActive, !self.isStoppingSession else { return } + await self.resetToIdle(message: "Gemini connection lost: \(reason ?? "Unknown error")") } } - geminiService.onToolCallCancellation = { [weak self] cancellation in + geminiService.onSocketClosed = { [weak self] reason in guard let self else { return } Task { @MainActor in - self.toolCallRouter?.cancelToolCalls(ids: cancellation.ids) + guard self.isGeminiActive, !self.isStoppingSession else { return } + await self.resetToIdle(message: "Gemini socket closed: \(reason ?? "Unknown error")") } } } @@ -388,138 +209,199 @@ class GeminiSessionViewModel: ObservableObject { guard !Task.isCancelled else { break } self.connectionState = self.geminiService.connectionState self.isModelSpeaking = self.geminiService.isModelSpeaking - self.toolCallStatus = self.openClawBridge.lastToolCallStatus - self.openClawConnectionState = self.openClawBridge.connectionState } } } - private func reconnectTransport(with systemInstruction: String) async { - eventClient.disconnect() - audioManager.stopCapture() - geminiService.onDisconnected = nil - geminiService.onSocketClosed = nil - geminiService.onSocketOpened = nil - geminiService.disconnect() + private func reconnectTransport(with liveConfig: LiveSessionConfig) async { + let wasActive = isGeminiActive + isGeminiActive = false + isAudioReady = false + isStoppingSession = true + await audioManager.stopCapture() + await Task.yield() + clearGeminiCallbacks() + await geminiService.disconnectAndWaitForClose(timeout: 1.0) stateObservation?.cancel() stateObservation = nil + isStoppingSession = false + + guard wasActive else { return } + errorMessage = nil userTranscript = "" aiTranscript = "" + currentLiveCredential = liveConfig.credential + currentSessionInstruction = liveConfig.systemInstruction + lastDiagnosticsID = liveConfig.diagnosticsID - await openClawBridge.checkConnection() - if toolCallRouter == nil { - toolCallRouter = ToolCallRouter(bridge: openClawBridge) - } configureRealtimeCallbacks() startStateObservation() do { try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) } catch { - resetToIdle(receiptMessage: "Audio setup failed: \(error.localizedDescription)") + await resetToIdle(message: "Audio setup failed: \(error.localizedDescription)") return } - guard let credential = await resolveLiveCredential() else { - resetToIdle(receiptMessage: "Gemini Live token unavailable. Confirm the admin URL, worker bearer token, and server Gemini key.") - return - } - currentLiveCredential = credential - let setupOk = await geminiService.connect( - systemInstruction: systemInstruction, - credential: credential + systemInstruction: liveConfig.systemInstruction, + credential: liveConfig.credential ) - if !setupOk { - let message: String - if case .error(let err) = geminiService.connectionState { - message = err - } else { - message = "Failed to reconnect to Gemini" - } - resetToIdle(receiptMessage: message) + guard setupOk else { + await resetToIdle(message: liveConnectionError( + fallback: "Failed to reconnect to Gemini", + diagnosticsID: liveConfig.diagnosticsID + )) return } do { try audioManager.startCapture() } catch { - resetToIdle(receiptMessage: "Mic capture failed: \(error.localizedDescription)") + await resetToIdle(message: "Mic capture failed: \(error.localizedDescription)") return } - connectEventStreamIfNeeded() - currentSessionInstruction = systemInstruction + isGeminiActive = true + isAudioReady = true } - private func resolveLiveCredential() async -> GeminiLiveCredential? { - if let workerAdminAPI, GeminiConfig.isAdminConfigured { - do { - let token = try await workerAdminAPI.requestGeminiLiveToken( - model: GeminiConfig.model, - sessionID: adminExecutionSessionID - ) - await WorkerTelemetry.shared.record( - "gemini_live_token_received", - source: "gemini_live", - stage: "ready", - payload: [ - "model": token.model, - "expires_at": token.expiresAt - ] - ) - return token.credential - } catch { - await WorkerTelemetry.shared.record( - "gemini_live_token_failed", - source: "gemini_live", - stage: "fallback", - payload: [ - "error": error.localizedDescription, - "api_key_fallback_available": GeminiConfig.isConfigured - ] - ) - } + private func resolveLiveSessionConfig(fallbackInstruction: String?) async -> LiveSessionConfig? { + guard let workerAdminAPI, GeminiConfig.isAdminConfigured else { + await recordTelemetry( + "gemini_live_token_failed", + stage: "not_configured", + payload: ["reason": "admin_api_unavailable"] + ) + return nil } - if let fallback = GeminiLiveCredential.apiKey() { - await WorkerTelemetry.shared.record( - "gemini_live_token_fallback", - source: "gemini_live", - stage: "api_key", - payload: ["model": fallback.model] + do { + let token = try await workerAdminAPI.requestGeminiLiveToken( + model: nil, + sessionID: adminExecutionSessionID ) - return fallback + let instruction = resolvedInstruction( + serverInstruction: token.systemInstruction, + fallbackInstruction: fallbackInstruction + ) + await recordTelemetry( + "gemini_live_token_received", + stage: "ready", + payload: [ + "model": token.credential.model, + "provider": token.provider ?? "gemini", + "expires_at": token.expiresAt, + "diagnostics_id": token.diagnosticsID ?? NSNull() + ] + ) + return LiveSessionConfig( + credential: token.credential, + systemInstruction: instruction, + diagnosticsID: token.diagnosticsID, + provider: token.provider + ) + } catch { + let message = error.localizedDescription + await recordTelemetry( + "gemini_live_token_failed", + stage: "failed", + payload: ["error": message] + ) + errorMessage = "Gemini token request failed: \(message)" + return nil } + } - return nil + private func normalizedSystemInstruction(_ instruction: String?) -> String? { + let trimmed = instruction?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return trimmed.isEmpty ? nil : trimmed } - private func normalizedReceiptMessage(_ receiptMessage: String?) -> String? { - let trimmed = receiptMessage?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - guard !trimmed.isEmpty else { return nil } - if trimmed.localizedCaseInsensitiveContains("legacy sop relay disabled") { - return nil + private func resolvedInstruction( + serverInstruction: String?, + fallbackInstruction: String? + ) -> String { + let server = normalizedSystemInstruction(serverInstruction) + let fallback = normalizedSystemInstruction(fallbackInstruction) + + switch (server, fallback) { + case let (server?, fallback?) where !server.contains(fallback): + return """ + \(server) + + Local active-step context from the phone UI: + \(fallback) + """ + case let (server?, _): + return server + case let (nil, fallback?): + return fallback + default: + return GeminiConfig.defaultSystemInstruction } - return trimmed } - private func buildToolResponse( - callId: String, - name: String, - result: ToolResult - ) -> [String: Any] { - return [ - "toolResponse": [ - "functionResponses": [ - [ - "id": callId, - "name": name, - "response": result.responseValue - ] - ] - ] - ] + private func liveConnectionError(fallback: String, diagnosticsID: String?) -> String { + let base: String + if case .error(let err) = geminiService.connectionState { + base = err + } else { + base = fallback + } + if let diagnosticsID, !diagnosticsID.isEmpty { + return "\(base). Diagnostics: \(diagnosticsID)." + } + return base + } + + private func resetToIdle(message: String?) async { + isStoppingSession = true + stateObservation?.cancel() + stateObservation = nil + // This is the Gemini-to-WebRTC hardware barrier: the engine graph and + // accumulator finish before the socket is allowed to close. + await audioManager.stopCapture() + await Task.yield() + audioManager.stopPlayback() + clearGeminiCallbacks() + await geminiService.disconnectAndWaitForClose(timeout: 1.0) + + isGeminiActive = false + isAudioReady = false + connectionState = .disconnected + isModelSpeaking = false + userTranscript = "" + aiTranscript = "" + currentSessionInstruction = nil + currentLiveCredential = nil + errorMessage = normalizedSystemInstruction(message) + isStoppingSession = false + } + + private func clearGeminiCallbacks() { + geminiService.onDisconnected = nil + geminiService.onSocketClosed = nil + geminiService.onSocketOpened = nil + geminiService.onAudioReceived = nil + geminiService.onInterrupted = nil + geminiService.onTurnComplete = nil + geminiService.onInputTranscription = nil + geminiService.onOutputTranscription = nil + } + + private func recordTelemetry( + _ name: String, + stage: String, + payload: [String: Any] = [:] + ) async { + await WorkerTelemetry.shared.record( + name, + source: "gemini_live", + stage: stage, + payload: payload + ) } } diff --git a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift index 67885263..32fec78b 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift @@ -1324,7 +1324,7 @@ private extension String { } protocol WorkerAdminAPI: AnyObject { - func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws -> WorkerLiveHeartbeatResponse func requestWorkerMediaUploadTarget( sessionID: String, assetType: String, @@ -1341,7 +1341,7 @@ protocol WorkerAdminAPI: AnyObject { ) async throws func sendWorkerTelemetryBatch(_ batch: WorkerTelemetryBatch) async throws func requestGeminiLiveToken( - model: String, + model: String?, sessionID: String? ) async throws -> GeminiLiveTokenResponse func requestGeminiSpotter(_ request: GeminiSpotterRequest) async throws -> GeminiSpotterResponse @@ -1376,6 +1376,65 @@ struct WorkerLiveHeartbeatRequest: Equatable { } } +struct WorkerLiveHeartbeatResponse: Decodable, Equatable { + let sessionID: String + let updatedAt: String? + let isFreshLiveSession: Bool + let webrtcRoomCode: String? + let supportMode: String + let aiSessionStatus: String + let humanSupportStatus: String + let supportUpdatedAt: String? + let shouldOpenLiveRoom: Bool + + private enum CodingKeys: String, CodingKey { + case sessionID = "sessionId" + case updatedAt + case isFreshLiveSession + case webrtcRoomCode + case supportMode + case aiSessionStatus + case humanSupportStatus + case supportUpdatedAt + case shouldOpenLiveRoom + } + + init( + sessionID: String, + updatedAt: String? = nil, + isFreshLiveSession: Bool = false, + webrtcRoomCode: String? = nil, + supportMode: String = "ai", + aiSessionStatus: String = "active", + humanSupportStatus: String = "none", + supportUpdatedAt: String? = nil, + shouldOpenLiveRoom: Bool = false + ) { + self.sessionID = sessionID + self.updatedAt = updatedAt + self.isFreshLiveSession = isFreshLiveSession + self.webrtcRoomCode = webrtcRoomCode + self.supportMode = supportMode + self.aiSessionStatus = aiSessionStatus + self.humanSupportStatus = humanSupportStatus + self.supportUpdatedAt = supportUpdatedAt + self.shouldOpenLiveRoom = shouldOpenLiveRoom + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + sessionID = try container.decode(String.self, forKey: .sessionID) + updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt) + isFreshLiveSession = try container.decodeIfPresent(Bool.self, forKey: .isFreshLiveSession) ?? false + webrtcRoomCode = try container.decodeIfPresent(String.self, forKey: .webrtcRoomCode) + supportMode = try container.decodeIfPresent(String.self, forKey: .supportMode) ?? "ai" + aiSessionStatus = try container.decodeIfPresent(String.self, forKey: .aiSessionStatus) ?? "active" + humanSupportStatus = try container.decodeIfPresent(String.self, forKey: .humanSupportStatus) ?? "none" + supportUpdatedAt = try container.decodeIfPresent(String.self, forKey: .supportUpdatedAt) + shouldOpenLiveRoom = try container.decodeIfPresent(Bool.self, forKey: .shouldOpenLiveRoom) ?? false + } +} + struct WorkerMediaUploadTarget: Decodable, Equatable { let assetID: String let bucket: String @@ -1463,6 +1522,23 @@ struct GeminiLiveTokenResponse: Decodable, Equatable { let model: String let websocketBaseURL: String let queryParameterName: String + let systemInstruction: String? + let runtimeContext: GeminiRuntimeContextEnvelope? + let diagnosticsID: String? + let provider: String? + + private enum CodingKeys: String, CodingKey { + case token + case expiresAt + case newSessionExpiresAt + case model + case websocketBaseURL + case queryParameterName + case systemInstruction + case runtimeContext + case diagnosticsID = "diagnosticsId" + case provider + } var credential: GeminiLiveCredential { GeminiLiveCredential( @@ -1474,6 +1550,19 @@ struct GeminiLiveTokenResponse: Decodable, Equatable { } } +struct GeminiRuntimeContextEnvelope: Decodable, Equatable { + let rawSummary: String + + init(from decoder: Decoder) throws { + if let container = try? decoder.singleValueContainer(), + container.decodeNil() { + rawSummary = "" + return + } + rawSummary = "runtime-context" + } +} + struct GeminiSpotterRequest: Equatable { let sessionID: String let stepID: String @@ -1489,9 +1578,10 @@ struct GeminiSpotterRequest: Equatable { let capturedAt: String let critical: Bool let allowAIComplete: Bool + let elapsedActiveMs: Int? var payload: [String: Any] { - [ + var payload: [String: Any] = [ "sessionId": sessionID, "stepId": stepID, "stepTitle": stepTitle, @@ -1507,6 +1597,10 @@ struct GeminiSpotterRequest: Equatable { "critical": critical, "allowAIComplete": allowAIComplete ] + if let elapsedActiveMs { + payload["elapsedActiveMs"] = elapsedActiveMs + } + return payload } } @@ -1518,6 +1612,36 @@ struct GeminiSpotterResponse: Decodable, Equatable { let threshold: Double? let model: String? let autoComplete: Bool + let modelAutoComplete: Bool? + let evidenceWindowSatisfied: Bool? + let activeDurationSatisfied: Bool? + let elapsedActiveMs: Int? + let minActiveSeconds: Double? + let stableObservations: Int? + let stableObservationsRequired: Int? + let advancedToStepIndex: Int? + let completedSop: Bool? + let packageProgressWarning: String? + + private enum CodingKeys: String, CodingKey { + case matched + case confidence + case reason + case evidenceTimestamp + case threshold + case model + case autoComplete + case modelAutoComplete + case evidenceWindowSatisfied + case activeDurationSatisfied + case elapsedActiveMs + case minActiveSeconds + case stableObservations + case stableObservationsRequired + case advancedToStepIndex + case completedSop + case packageProgressWarning + } } struct BackendMemoryLink: Identifiable, Decodable, Equatable { @@ -1649,7 +1773,7 @@ final class OpsAPIClient: WorkerAdminAPI { return liveToken } - let configuredToken = GeminiConfig.openClawBearerToken.trimmingCharacters(in: .whitespacesAndNewlines) + let configuredToken = GeminiConfig.workerAPIBearerToken.trimmingCharacters(in: .whitespacesAndNewlines) return configuredToken.isEmpty ? nil : configuredToken } @@ -1866,12 +1990,20 @@ final class OpsAPIClient: WorkerAdminAPI { } } - func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws { - _ = try await performWorkerRequest( + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws -> WorkerLiveHeartbeatResponse { + let data = try await performWorkerRequest( path: "/api/worker/live/heartbeat", method: "POST", payload: heartbeat.payload ) + + do { + return try decoder.decode(WorkerLiveHeartbeatResponse.self, from: data) + } catch { + let body = String(data: data, encoding: .utf8) ?? "" + NSLog("[admin-ingest] Failed decoding /api/worker/live/heartbeat -> %@", body) + throw error + } } func requestWorkerMediaUploadTarget( @@ -1924,13 +2056,16 @@ final class OpsAPIClient: WorkerAdminAPI { } func requestGeminiLiveToken( - model: String, + model: String? = nil, sessionID: String? = nil ) async throws -> GeminiLiveTokenResponse { var payload: [String: Any] = [ - "model": model, "responseModalities": ["AUDIO"] ] + if let model = model?.trimmingCharacters(in: .whitespacesAndNewlines), + !model.isEmpty { + payload["model"] = model + } if let sessionID, !sessionID.isEmpty { payload["sessionId"] = sessionID } @@ -2137,67 +2272,3 @@ final class OpsAPIClient: WorkerAdminAPI { return "\(compact[.. String? { - "Legacy SOP relay disabled while ops-api migration is active." - } - - func postHeartbeatForReceiptWithStatus( - tailscaleIP: String, - sessionID: String, - status: String - ) async -> (statusCode: Int?, message: String?) { - (200, "Legacy SOP relay disabled while ops-api migration is active.") - } - - func postFinalPayloadForReceiptWithStatus( - tailscaleIP: String, - payload: [String: Any] - ) async -> (statusCode: Int?, message: String?) { - (200, "Legacy SOP relay disabled while ops-api migration is active.") - } - - func postSopVideoForReceiptWithStatus( - tailscaleIP: String, - sessionID: String, - videoFileURL: URL - ) async -> (statusCode: Int?, message: String?) { - (200, "Video upload deferred until media upload flow is implemented.") - } - - func postSopDossierForReceiptWithStatus( - tailscaleIP: String, - sessionID: String, - sopName: String, - metadataJSONString: String, - videoFileURL: URL, - proofImagesByTargetID: [String: Data] - ) async -> (statusCode: Int?, message: String?) { - (200, "Dossier upload deferred until media upload flow is implemented.") - } -} diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift b/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift deleted file mode 100644 index 104447ab..00000000 --- a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift +++ /dev/null @@ -1,139 +0,0 @@ -import Foundation - -enum OpenClawConnectionState: Equatable { - case notConfigured - case checking - case connected - case unreachable(String) -} - -@MainActor -class OpenClawBridge: ObservableObject { - @Published var lastToolCallStatus: ToolCallStatus = .idle - @Published var connectionState: OpenClawConnectionState = .notConfigured - - private let session: URLSession - private let pingSession: URLSession - private var sessionKey: String - private var conversationHistory: [[String: String]] = [] - private let maxHistoryTurns = 10 - - private static let stableSessionKey = "agent:main:glass" - - init() { - let config = URLSessionConfiguration.default - config.timeoutIntervalForRequest = 120 - self.session = URLSession(configuration: config) - - let pingConfig = URLSessionConfiguration.default - pingConfig.timeoutIntervalForRequest = 5 - self.pingSession = URLSession(configuration: pingConfig) - - self.sessionKey = OpenClawBridge.stableSessionKey - } - - func checkConnection() async { - guard GeminiConfig.isOpenClawConfigured else { - connectionState = .notConfigured - return - } - connectionState = .checking - guard let url = URL(string: "\(GeminiConfig.openClawHost):\(GeminiConfig.openClawPort)/v1/chat/completions") else { - connectionState = .unreachable("Invalid URL") - return - } - var request = URLRequest(url: url) - request.httpMethod = "GET" - request.setValue("Bearer \(GeminiConfig.openClawGatewayToken)", forHTTPHeaderField: "Authorization") - request.setValue("glass", forHTTPHeaderField: "x-openclaw-message-channel") - do { - let (_, response) = try await pingSession.data(for: request) - if let http = response as? HTTPURLResponse, (200...499).contains(http.statusCode) { - connectionState = .connected - NSLog("[OpenClaw] Gateway reachable (HTTP %d)", http.statusCode) - } else { - connectionState = .unreachable("Unexpected response") - } - } catch { - connectionState = .unreachable(error.localizedDescription) - NSLog("[OpenClaw] Gateway unreachable: %@", error.localizedDescription) - } - } - - func resetSession() { - conversationHistory = [] - NSLog("[OpenClaw] Session reset (key retained: %@)", sessionKey) - } - - // MARK: - Agent Chat (session continuity via x-openclaw-session-key header) - - func delegateTask( - task: String, - toolName: String = "execute" - ) async -> ToolResult { - lastToolCallStatus = .executing(toolName) - - guard let url = URL(string: "\(GeminiConfig.openClawHost):\(GeminiConfig.openClawPort)/v1/chat/completions") else { - lastToolCallStatus = .failed(toolName, "Invalid URL") - return .failure("Invalid gateway URL") - } - - // Append the new user message to conversation history - conversationHistory.append(["role": "user", "content": task]) - - // Trim history to keep only the most recent turns (user+assistant pairs) - if conversationHistory.count > maxHistoryTurns * 2 { - conversationHistory = Array(conversationHistory.suffix(maxHistoryTurns * 2)) - } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("Bearer \(GeminiConfig.openClawGatewayToken)", forHTTPHeaderField: "Authorization") - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue(sessionKey, forHTTPHeaderField: "x-openclaw-session-key") - request.setValue("glass", forHTTPHeaderField: "x-openclaw-message-channel") - - let body: [String: Any] = [ - "model": "openclaw", - "messages": conversationHistory, - "stream": false - ] - - NSLog("[OpenClaw] Sending conversation with %d messages", conversationHistory.count) - - do { - request.httpBody = try JSONSerialization.data(withJSONObject: body) - let (data, response) = try await session.data(for: request) - let httpResponse = response as? HTTPURLResponse - - guard let statusCode = httpResponse?.statusCode, (200...299).contains(statusCode) else { - let code = httpResponse?.statusCode ?? 0 - NSLog("[OpenClaw] Chat failed with HTTP %d", code) - lastToolCallStatus = .failed(toolName, "HTTP \(code)") - return .failure("Agent returned HTTP \(code)") - } - - if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let choices = json["choices"] as? [[String: Any]], - let first = choices.first, - let message = first["message"] as? [String: Any], - let content = message["content"] as? String { - // Append assistant response to history for continuity - conversationHistory.append(["role": "assistant", "content": content]) - NSLog("[OpenClaw] Agent response received") - lastToolCallStatus = .completed(toolName) - return .success(content) - } - - let raw = String(data: data, encoding: .utf8) ?? "OK" - conversationHistory.append(["role": "assistant", "content": raw]) - NSLog("[OpenClaw] Agent raw response received") - lastToolCallStatus = .completed(toolName) - return .success(raw) - } catch { - NSLog("[OpenClaw] Agent error: %@", error.localizedDescription) - lastToolCallStatus = .failed(toolName, error.localizedDescription) - return .failure("Agent error: \(error.localizedDescription)") - } - } -} diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawEventClient.swift b/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawEventClient.swift deleted file mode 100644 index 8ceeef59..00000000 --- a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawEventClient.swift +++ /dev/null @@ -1,191 +0,0 @@ -import Foundation - -class OpenClawEventClient { - var onNotification: ((String) -> Void)? - - private var webSocketTask: URLSessionWebSocketTask? - private var session: URLSession? - private var isConnected = false - private var shouldReconnect = false - private var reconnectDelay: TimeInterval = 2 - private let maxReconnectDelay: TimeInterval = 30 - - func connect() { - guard GeminiConfig.isOpenClawConfigured else { - NSLog("[OpenClawWS] Not configured, skipping") - return - } - - shouldReconnect = true - reconnectDelay = 2 - establishConnection() - } - - func disconnect() { - shouldReconnect = false - isConnected = false - webSocketTask?.cancel(with: .normalClosure, reason: nil) - webSocketTask = nil - session?.invalidateAndCancel() - session = nil - NSLog("[OpenClawWS] Disconnected") - } - - // MARK: - Private - - private func establishConnection() { - let host = GeminiConfig.openClawHost - .replacingOccurrences(of: "http://", with: "") - .replacingOccurrences(of: "https://", with: "") - let port = GeminiConfig.openClawPort - guard let url = URL(string: "ws://\(host):\(port)") else { - NSLog("[OpenClawWS] Invalid URL") - return - } - - let config = URLSessionConfiguration.default - config.timeoutIntervalForRequest = 30 - session = URLSession(configuration: config) - webSocketTask = session?.webSocketTask(with: url) - webSocketTask?.resume() - - NSLog("[OpenClawWS] Connecting to %@", url.absoluteString) - startReceiving() - } - - private func startReceiving() { - webSocketTask?.receive { [weak self] result in - guard let self else { return } - switch result { - case .success(let message): - switch message { - case .string(let text): - self.handleMessage(text) - case .data(let data): - if let text = String(data: data, encoding: .utf8) { - self.handleMessage(text) - } - @unknown default: - break - } - self.startReceiving() - case .failure(let error): - NSLog("[OpenClawWS] Receive error: %@", error.localizedDescription) - self.isConnected = false - self.scheduleReconnect() - } - } - } - - private func handleMessage(_ text: String) { - guard let data = text.data(using: .utf8), - let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let type = json["type"] as? String else { return } - - if type == "event" { - handleEvent(json) - } else if type == "res" { - let ok = json["ok"] as? Bool ?? false - if ok { - NSLog("[OpenClawWS] Connected and authenticated") - isConnected = true - reconnectDelay = 2 - } else { - let error = json["error"] as? [String: Any] - let msg = error?["message"] as? String ?? "unknown" - NSLog("[OpenClawWS] Connect failed: %@", msg) - } - } - } - - private func handleEvent(_ json: [String: Any]) { - guard let event = json["event"] as? String else { return } - let payload = json["payload"] as? [String: Any] ?? [:] - - switch event { - case "connect.challenge": - sendConnectHandshake() - - case "heartbeat": - handleHeartbeatEvent(payload) - - case "cron": - handleCronEvent(payload) - - default: - break - } - } - - private func sendConnectHandshake() { - let connectMsg: [String: Any] = [ - "type": "req", - "id": UUID().uuidString, - "method": "connect", - "params": [ - "minProtocol": 3, - "maxProtocol": 3, - "client": [ - "id": "ios-node", - "displayName": "VisionClaw Glass", - "version": "1.0", - "platform": "ios", - "mode": "node" - ], - "role": "node", - "scopes": [] as [String], - "caps": ["camera", "voice"], - "commands": [] as [String], - "permissions": [:] as [String: Any], - "auth": [ - "token": GeminiConfig.openClawGatewayToken - ] - ] as [String: Any] - ] - - guard let data = try? JSONSerialization.data(withJSONObject: connectMsg), - let string = String(data: data, encoding: .utf8) else { return } - webSocketTask?.send(.string(string)) { error in - if let error { - NSLog("[OpenClawWS] Handshake send error: %@", error.localizedDescription) - } - } - } - - private func handleHeartbeatEvent(_ payload: [String: Any]) { - let status = payload["status"] as? String ?? "" - // Only notify if there's actual content (not empty/silent heartbeats) - guard status == "sent", let preview = payload["preview"] as? String, !preview.isEmpty else { - return - } - - let silent = payload["silent"] as? Bool ?? false - guard !silent else { return } - - NSLog("[OpenClawWS] Heartbeat notification: %@", String(preview.prefix(100))) - onNotification?("[Notification from your assistant] \(preview)") - } - - private func handleCronEvent(_ payload: [String: Any]) { - let action = payload["action"] as? String ?? "" - guard action == "finished" else { return } - - let summary = payload["summary"] as? String - ?? payload["result"] as? String - ?? "" - guard !summary.isEmpty else { return } - - NSLog("[OpenClawWS] Cron notification: %@", String(summary.prefix(100))) - onNotification?("[Scheduled update] \(summary)") - } - - private func scheduleReconnect() { - guard shouldReconnect else { return } - NSLog("[OpenClawWS] Reconnecting in %.0fs", reconnectDelay) - DispatchQueue.main.asyncAfter(deadline: .now() + reconnectDelay) { [weak self] in - guard let self, self.shouldReconnect else { return } - self.reconnectDelay = min(self.reconnectDelay * 2, self.maxReconnectDelay) - self.establishConnection() - } - } -} diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift deleted file mode 100644 index b3604022..00000000 --- a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift +++ /dev/null @@ -1,142 +0,0 @@ -import Foundation - -// MARK: - Gemini Tool Call (parsed from server JSON) - -struct GeminiFunctionCall { - let id: String - let name: String - let args: [String: Any] -} - -struct GeminiToolCall { - let functionCalls: [GeminiFunctionCall] - - init?(json: [String: Any]) { - guard let toolCall = json["toolCall"] as? [String: Any], - let calls = toolCall["functionCalls"] as? [[String: Any]] else { - return nil - } - self.functionCalls = calls.compactMap { call in - guard let id = call["id"] as? String, - let name = call["name"] as? String else { return nil } - let args = call["args"] as? [String: Any] ?? [:] - return GeminiFunctionCall(id: id, name: name, args: args) - } - } -} - -// MARK: - Gemini Tool Call Cancellation - -struct GeminiToolCallCancellation { - let ids: [String] - - init?(json: [String: Any]) { - guard let cancellation = json["toolCallCancellation"] as? [String: Any], - let ids = cancellation["ids"] as? [String] else { - return nil - } - self.ids = ids - } -} - -// MARK: - Tool Result - -enum ToolResult { - case success(String) - case failure(String) - - var responseValue: [String: Any] { - switch self { - case .success(let result): - return ["result": result] - case .failure(let error): - return ["error": error] - } - } -} - -// MARK: - Tool Call Status (for UI) - -enum ToolCallStatus: Equatable { - case idle - case executing(String) - case completed(String) - case failed(String, String) - case cancelled(String) - - var displayText: String { - switch self { - case .idle: return "" - case .executing(let name): return "Running: \(name)..." - case .completed(let name): return "Done: \(name)" - case .failed(let name, let err): return "Failed: \(name) - \(err)" - case .cancelled(let name): return "Cancelled: \(name)" - } - } - - var isActive: Bool { - if case .executing = self { return true } - return false - } -} - -// MARK: - Tool Declarations (for Gemini setup message) - -enum ToolDeclarations { - - static func allDeclarations() -> [[String: Any]] { - return [execute, logSopStep] - } - - static let execute: [String: Any] = [ - "name": "execute", - "description": "Your only way to take action. You have no memory, storage, or ability to do anything on your own -- use this tool for everything: sending messages, searching the web, adding to lists, setting reminders, creating notes, research, drafts, scheduling, smart home control, app interactions, or any request that goes beyond answering a question. When in doubt, use this tool.", - "parameters": [ - "type": "object", - "properties": [ - "task": [ - "type": "string", - "description": "Clear, detailed description of what to do. Include all relevant context: names, content, platforms, quantities, etc." - ] - ], - "required": ["task"] - ] as [String: Any], - "behavior": "BLOCKING" - ] - - static let logSopStep: [String: Any] = [ - "name": "log_sop_step", - "description": "Log an SOP step to the external SOP processor.", - "parameters": [ - "type": "object", - "properties": [ - "step_number": [ - "type": "integer", - "description": "Current SOP step number (1-based)" - ], - "step_name": [ - "type": "string", - "description": "SOP step label to record" - ], - "action": [ - "type": "string", - "description": "Step action state: started, completed, failed, or skipped" - ], - "total_steps": [ - "type": "integer", - "description": "Total number of SOP steps" - ], - "frame_data": [ - "type": "string", - "description": "Optional current frame JPEG as raw base64 bytes (no data URI)." - ], - "notes": [ - "type": "string", - "description": "Optional operator/model notes for this step." - ] - ], - "required": ["step_name"] - ] as [String: Any], - "behavior": "NON_BLOCKING" - ] -} diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift deleted file mode 100644 index ed406c43..00000000 --- a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift +++ /dev/null @@ -1,165 +0,0 @@ -import Foundation - -@MainActor -class ToolCallRouter { - private let bridge: OpenClawBridge - private var inFlightTasks: [String: Task] = [:] - private var consecutiveFailures = 0 - private let maxConsecutiveFailures = 3 - - init(bridge: OpenClawBridge) { - self.bridge = bridge - } - - /// Route a tool call from Gemini to OpenClaw. Calls sendResponse with the - /// JSON dictionary to send back as a toolResponse message. - func handleToolCall( - _ call: GeminiFunctionCall, - sendResponse: @escaping ([String: Any]) -> Void - ) { - let callId = call.id - let callName = call.name - - NSLog("[ToolCall] Received: %@ (id: %@) args: %@", - callName, callId, String(describing: call.args)) - Task { - await WorkerTelemetry.shared.record( - "tool_call_received", - source: "gemini_live", - stage: "tool", - payload: [ - "tool_name": callName, - "arg_keys": Array(call.args.keys).sorted() - ] - ) - } - - // Circuit breaker: stop sending tool calls after repeated failures - if consecutiveFailures >= maxConsecutiveFailures { - NSLog("[ToolCall] Circuit breaker open (%d consecutive failures), rejecting %@", - consecutiveFailures, callId) - let errorResult: ToolResult = .failure( - "Tool execution is temporarily unavailable after \(consecutiveFailures) consecutive failures. " + - "Please tell the user you cannot complete this action right now and suggest they check their Video AI Analyst gateway connection." - ) - Task { - await WorkerTelemetry.shared.record( - "tool_call_rejected", - source: "gemini_live", - stage: "failed", - payload: [ - "tool_name": callName, - "consecutive_failures": consecutiveFailures - ] - ) - } - let response = buildToolResponse(callId: callId, name: callName, result: errorResult) - sendResponse(response) - return - } - - let task = Task { @MainActor in - let taskDesc = call.args["task"] as? String ?? String(describing: call.args) - let startedAt = Date() - let result = await bridge.delegateTask(task: taskDesc, toolName: callName) - - guard !Task.isCancelled else { - NSLog("[ToolCall] Task %@ was cancelled, skipping response", callId) - Task { - await WorkerTelemetry.shared.record( - "tool_call_cancelled", - source: "gemini_live", - stage: "cancelled", - payload: ["tool_name": callName] - ) - } - return - } - - switch result { - case .success: - self.consecutiveFailures = 0 - case .failure: - self.consecutiveFailures += 1 - } - - NSLog("[ToolCall] Result for %@ (id: %@): %@", - callName, callId, String(describing: result)) - let resultStage: String - switch result { - case .success: - resultStage = "success" - case .failure: - resultStage = "failed" - } - Task { - await WorkerTelemetry.shared.record( - "tool_call_result", - source: "gemini_live", - stage: resultStage, - durationMs: Date().timeIntervalSince(startedAt) * 1000, - payload: [ - "tool_name": callName, - "consecutive_failures": self.consecutiveFailures - ] - ) - } - - let response = self.buildToolResponse(callId: callId, name: callName, result: result) - sendResponse(response) - - self.inFlightTasks.removeValue(forKey: callId) - } - - inFlightTasks[callId] = task - } - - /// Cancel specific in-flight tool calls (from toolCallCancellation) - func cancelToolCalls(ids: [String]) { - for id in ids { - if let task = inFlightTasks[id] { - NSLog("[ToolCall] Cancelling in-flight call: %@", id) - task.cancel() - inFlightTasks.removeValue(forKey: id) - Task { - await WorkerTelemetry.shared.record( - "tool_call_cancellation_requested", - source: "gemini_live", - stage: "cancelled" - ) - } - } - } - bridge.lastToolCallStatus = .cancelled(ids.first ?? "unknown") - } - - /// Cancel all in-flight tool calls (on session stop) - func cancelAll() { - for (id, task) in inFlightTasks { - NSLog("[ToolCall] Cancelling in-flight call: %@", id) - task.cancel() - } - inFlightTasks.removeAll() - consecutiveFailures = 0 - } - - // MARK: - Private - - private func buildToolResponse( - callId: String, - name: String, - result: ToolResult - ) -> [String: Any] { - return [ - "toolResponse": [ - "functionResponses": [ - [ - "id": callId, - "name": name, - "response": result.responseValue - ] - ] - ] - ] - } -} diff --git a/samples/CameraAccess/CameraAccess/Secrets.swift.example b/samples/CameraAccess/CameraAccess/Secrets.swift.example index 457381e1..e2a47705 100644 --- a/samples/CameraAccess/CameraAccess/Secrets.swift.example +++ b/samples/CameraAccess/CameraAccess/Secrets.swift.example @@ -8,19 +8,12 @@ enum Secrets { static let deviceID = "YOUR_DEVICE_UUID" // Prototype worker identity used during ops-api bootstrap - static let workerLoginCode = "EMBC-0001" + static let workerLoginCode = "PD-0101" static let workerEmail = "" - // DEV-ONLY fallback. Production Gemini Live uses /api/worker/gemini/live-token. - static let geminiAPIKey = "YOUR_GEMINI_API_KEY" - - // OPTIONAL: Private OpenClaw gateway config (for agentic tool-calling / memory) - static let openClawHost = "http://YOUR_MAC_HOSTNAME.local" - static let openClawPort = 18789 - static let openClawTailscaleIP = "srv1338555" - static let openClawBearerToken = "" - static let openClawHookToken = "YOUR_OPENCLAW_HOOK_TOKEN" - static let openClawGatewayToken = "YOUR_OPENCLAW_GATEWAY_TOKEN" + // Optional fallback bearer for /api/worker/* before bootstrap returns a session token. + // Gemini credentials and prompts are owned by Admin AI Settings on the server. + static let workerAPIBearerToken = "" // Operations API URL for worker bootstrap, sessions, events, and media registration. static let opsBaseURL = "https://admin.embarcaderolabs.cloud" diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift index 4284c993..fa1ee1af 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift @@ -18,27 +18,32 @@ final class SettingsManager { private enum Key: String { case deviceID case workerLoginCode + case workerLoginCodeMigratedFromFastFoodDefault case workerEmail - case geminiAPIKey + case workerAPIBearerToken case opsBaseURL case adminBaseURL case signalBaseURL - case openClawHost - case openClawPort - case openClawBearerToken - case openClawHookToken - case openClawGatewayToken - case geminiSystemPrompt case webrtcSignalingURL case speakerOutputEnabled case videoStreamingEnabled + case videoStreamingDefaultMigratedToOnDemand case proactiveNotificationsEnabled - case openClawTailscaleIP } - private init() {} + private init() { + migrateOnDemandVideoDefaultIfNeeded() + } - // MARK: - Gemini + private func migrateOnDemandVideoDefaultIfNeeded() { + guard !defaults.bool(forKey: Key.videoStreamingDefaultMigratedToOnDemand.rawValue) else { return } + if defaults.object(forKey: Key.videoStreamingEnabled.rawValue) != nil { + defaults.set(false, forKey: Key.videoStreamingEnabled.rawValue) + } + defaults.set(true, forKey: Key.videoStreamingDefaultMigratedToOnDemand.rawValue) + } + + // MARK: - Worker var deviceID: String { get { @@ -59,7 +64,22 @@ final class SettingsManager { } var workerLoginCode: String { - get { defaults.string(forKey: Key.workerLoginCode.rawValue) ?? Secrets.workerLoginCode } + get { + if let stored = defaults.string(forKey: Key.workerLoginCode.rawValue) { + let trimmed = stored.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.caseInsensitiveCompare("EMBC-0001") == .orderedSame, + Secrets.workerLoginCode.caseInsensitiveCompare("EMBC-0001") != .orderedSame, + defaults.bool(forKey: Key.workerLoginCodeMigratedFromFastFoodDefault.rawValue) == false { + defaults.set(Secrets.workerLoginCode, forKey: Key.workerLoginCode.rawValue) + defaults.set(true, forKey: Key.workerLoginCodeMigratedFromFastFoodDefault.rawValue) + return Secrets.workerLoginCode + } + + return stored + } + + return Secrets.workerLoginCode + } set { defaults.set(newValue, forKey: Key.workerLoginCode.rawValue) } } @@ -68,9 +88,27 @@ final class SettingsManager { set { defaults.set(newValue, forKey: Key.workerEmail.rawValue) } } - var geminiAPIKey: String { - get { secureString(for: .geminiAPIKey, fallback: Secrets.geminiAPIKey) } - set { setSecureString(newValue, for: .geminiAPIKey) } + var workerAPIBearerToken: String { + get { + let current = secureString(for: .workerAPIBearerToken, fallback: Secrets.workerAPIBearerToken) + if !current.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return current + } + if let legacy = keychain.string(for: "openClawBearerToken"), + !legacy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + keychain.set(legacy, for: Key.workerAPIBearerToken.rawValue) + keychain.removeValue(for: "openClawBearerToken") + return legacy + } + if let legacy = defaults.string(forKey: "openClawBearerToken"), + !legacy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + keychain.set(legacy, for: Key.workerAPIBearerToken.rawValue) + defaults.removeObject(forKey: "openClawBearerToken") + return legacy + } + return current + } + set { setSecureString(newValue, for: .workerAPIBearerToken) } } var opsBaseURL: String { @@ -110,46 +148,6 @@ final class SettingsManager { set { defaults.set(newValue, forKey: Key.signalBaseURL.rawValue) } } - var geminiSystemPrompt: String { - get { defaults.string(forKey: Key.geminiSystemPrompt.rawValue) ?? GeminiConfig.defaultSystemInstruction } - set { defaults.set(newValue, forKey: Key.geminiSystemPrompt.rawValue) } - } - - // MARK: - OpenClaw - - var openClawHost: String { - get { defaults.string(forKey: Key.openClawHost.rawValue) ?? Secrets.openClawHost } - set { defaults.set(newValue, forKey: Key.openClawHost.rawValue) } - } - - var openClawPort: Int { - get { - let stored = defaults.integer(forKey: Key.openClawPort.rawValue) - return stored != 0 ? stored : Secrets.openClawPort - } - set { defaults.set(newValue, forKey: Key.openClawPort.rawValue) } - } - - var openClawHookToken: String { - get { secureString(for: .openClawHookToken, fallback: Secrets.openClawHookToken) } - set { setSecureString(newValue, for: .openClawHookToken) } - } - - var openClawGatewayToken: String { - get { secureString(for: .openClawGatewayToken, fallback: Secrets.openClawGatewayToken) } - set { setSecureString(newValue, for: .openClawGatewayToken) } - } - - var openClawTailscaleIP: String { - get { defaults.string(forKey: Key.openClawTailscaleIP.rawValue) ?? Secrets.openClawTailscaleIP } - set { defaults.set(newValue, forKey: Key.openClawTailscaleIP.rawValue) } - } - - var openClawBearerToken: String { - get { secureString(for: .openClawBearerToken, fallback: Secrets.openClawBearerToken) } - set { setSecureString(newValue, for: .openClawBearerToken) } - } - // MARK: - WebRTC var webrtcSignalingURL: String { @@ -171,7 +169,7 @@ final class SettingsManager { // MARK: - Video var videoStreamingEnabled: Bool { - get { defaults.object(forKey: Key.videoStreamingEnabled.rawValue) as? Bool ?? true } + get { defaults.object(forKey: Key.videoStreamingEnabled.rawValue) as? Bool ?? false } set { defaults.set(newValue, forKey: Key.videoStreamingEnabled.rawValue) } } @@ -185,16 +183,29 @@ final class SettingsManager { // MARK: - Reset func resetAll() { - for key in [Key.geminiSystemPrompt, .workerLoginCode, .workerEmail, .opsBaseURL, .adminBaseURL, .signalBaseURL, - .openClawHost, .openClawPort, .webrtcSignalingURL, .openClawTailscaleIP, + for key in [Key.workerLoginCode, .workerLoginCodeMigratedFromFastFoodDefault, .workerEmail, .opsBaseURL, .adminBaseURL, .signalBaseURL, + .webrtcSignalingURL, .deviceID, .speakerOutputEnabled, .videoStreamingEnabled, .proactiveNotificationsEnabled] { defaults.removeObject(forKey: key.rawValue) } - for key in [Key.geminiAPIKey, .openClawBearerToken, .openClawHookToken, .openClawGatewayToken] { + for key in [Key.workerAPIBearerToken] { defaults.removeObject(forKey: key.rawValue) keychain.removeValue(for: key.rawValue) } + for legacy in [ + "geminiAPIKey", + "openClawBearerToken", + "openClawHookToken", + "openClawGatewayToken", + "openClawHost", + "openClawPort", + "openClawTailscaleIP", + "geminiSystemPrompt" + ] { + defaults.removeObject(forKey: legacy) + keychain.removeValue(for: legacy) + } } private func secureString(for key: Key, fallback: String) -> String { diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift index eb2dcab4..976e610c 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift @@ -6,20 +6,15 @@ struct SettingsView: View { @State private var workerLoginCode: String = "" @State private var workerEmail: String = "" - @State private var geminiAPIKey: String = "" + @State private var workerAPIBearerToken: String = "" @State private var opsBaseURL: String = "" @State private var adminBaseURL: String = "" @State private var signalBaseURL: String = "" - @State private var openClawHost: String = "" - @State private var openClawPort: String = "" - @State private var openClawTailscaleIP: String = "" - @State private var openClawHookToken: String = "" - @State private var openClawGatewayToken: String = "" - @State private var geminiSystemPrompt: String = "" @State private var webrtcSignalingURL: String = "" @State private var speakerOutputEnabled: Bool = false - @State private var videoStreamingEnabled: Bool = true + @State private var videoStreamingEnabled: Bool = false @State private var proactiveNotificationsEnabled: Bool = true + @State private var showAdvancedBackend = false @State private var showResetConfirmation = false var body: some View { @@ -42,144 +37,40 @@ struct SettingsView: View { Text("Login Code") .font(.caption) .foregroundColor(.secondary) - TextField("EMBC-0001", text: $workerLoginCode) + TextField("PD-0101", text: $workerLoginCode) .autocapitalization(.none) .disableAutocorrection(true) .font(.system(.body, design: .monospaced)) } } - Section(header: Text("Operations Backend"), footer: Text("Ops Base URL handles worker bootstrap, sessions, events, interventions, and evidence uploads. Admin Base URL handles the /api/worker live ingest endpoints used for live frames, heartbeats, and final video replay sync.")) { - VStack(alignment: .leading, spacing: 4) { - Text("Ops Base URL") - .font(.caption) - .foregroundColor(.secondary) - TextField("https://admin.embarcaderolabs.cloud", text: $opsBaseURL) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.URL) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Admin Base URL") - .font(.caption) - .foregroundColor(.secondary) - TextField("https://admin.embarcaderolabs.cloud", text: $adminBaseURL) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.URL) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Signal Base URL") - .font(.caption) - .foregroundColor(.secondary) - TextField("https://signal.embarcaderolabs.cloud", text: $signalBaseURL) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.URL) - .font(.system(.body, design: .monospaced)) - } + Section( + header: Text("AI Guide"), + footer: Text("Gemini key, model, prompt, checklist brain, and manual context come from Admin AI Settings and the server-minted Live token.") + ) { + Label("Server-managed Gemini Live", systemImage: "sparkles") + Label("Checklist context loads from the assigned patient checklist", systemImage: "checklist") } - Section(header: Text("Gemini API")) { - VStack(alignment: .leading, spacing: 4) { - Text("API Key") - .font(.caption) - .foregroundColor(.secondary) - SecureField("Enter Gemini API key", text: $geminiAPIKey) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.system(.body, design: .monospaced)) - } + Section(header: Text("Audio"), footer: Text("Route audio output to the iPhone speaker instead of glasses. Useful for demos where others need to hear.")) { + Toggle("Speaker Output", isOn: $speakerOutputEnabled) } - Section(header: Text("System Prompt"), footer: Text("Customize the AI assistant's behavior and personality. Changes take effect on the next Gemini session.")) { - TextEditor(text: $geminiSystemPrompt) - .font(.system(.body, design: .monospaced)) - .frame(minHeight: 200) + Section(header: Text("Video"), footer: Text("Continuous Gemini video frames are optional. Step checks still use the camera on demand when you say \"I'm done\" or tap Check Step.")) { + Toggle("Continuous AI Video Frames", isOn: $videoStreamingEnabled) } - Section(header: Text("Video AI Analyst"), footer: Text("Private analyst connectivity stays separate from ops-api so memory links and agent-style actions can evolve independently.")) { - VStack(alignment: .leading, spacing: 4) { - Text("Host") - .font(.caption) - .foregroundColor(.secondary) - TextField("http://your-mac.local", text: $openClawHost) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.URL) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Port") - .font(.caption) - .foregroundColor(.secondary) - TextField("18789", text: $openClawPort) - .keyboardType(.numberPad) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Tailscale Host") - .font(.caption) - .foregroundColor(.secondary) - TextField("srv1338555", text: $openClawTailscaleIP) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.numbersAndPunctuation) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Hook Token") - .font(.caption) - .foregroundColor(.secondary) - SecureField("Hook token", text: $openClawHookToken) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.system(.body, design: .monospaced)) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Gateway Token") - .font(.caption) - .foregroundColor(.secondary) - SecureField("Gateway auth token", text: $openClawGatewayToken) - .autocapitalization(.none) - .disableAutocorrection(true) - .font(.system(.body, design: .monospaced)) - } + Section(header: Text("Notifications"), footer: Text("Receive AI guide status updates spoken through the glasses.")) { + Toggle("Proactive Guide Updates", isOn: $proactiveNotificationsEnabled) } - Section(header: Text("WebRTC")) { - VStack(alignment: .leading, spacing: 4) { - Text("Signaling URL") - .font(.caption) - .foregroundColor(.secondary) - TextField("wss://your-server.example.com", text: $webrtcSignalingURL) - .autocapitalization(.none) - .disableAutocorrection(true) - .keyboardType(.URL) - .font(.system(.body, design: .monospaced)) + Section(header: Text("Advanced")) { + Toggle("Developer Backend Settings", isOn: $showAdvancedBackend) + if showAdvancedBackend { + backendFields } } - Section(header: Text("Audio"), footer: Text("Route audio output to the iPhone speaker instead of glasses. Useful for demos where others need to hear.")) { - Toggle("Speaker Output", isOn: $speakerOutputEnabled) - } - - Section(header: Text("Video"), footer: Text("Disable video streaming to save battery. Audio remains active for voice-only interaction.")) { - Toggle("Video Streaming", isOn: $videoStreamingEnabled) - } - - Section(header: Text("Notifications"), footer: Text("Receive proactive updates from Video AI Analyst (heartbeat, scheduled tasks) spoken through the glasses.")) { - Toggle("Proactive Notifications", isOn: $proactiveNotificationsEnabled) - } - Section { Button("Reset to Defaults") { showResetConfirmation = true @@ -218,19 +109,72 @@ struct SettingsView: View { } } + private var backendFields: some View { + Group { + VStack(alignment: .leading, spacing: 4) { + Text("Ops Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://admin.embarcaderolabs.cloud", text: $opsBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Admin Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://admin.embarcaderolabs.cloud", text: $adminBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Signal Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://signal.embarcaderolabs.cloud", text: $signalBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("WebRTC Signaling URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("wss://signal.embarcaderolabs.cloud", text: $webrtcSignalingURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Worker API Bearer Token") + .font(.caption) + .foregroundColor(.secondary) + SecureField("Optional fallback token", text: $workerAPIBearerToken) + .autocapitalization(.none) + .disableAutocorrection(true) + .textInputAutocapitalization(.never) + .font(.system(.body, design: .monospaced)) + } + } + } + private func loadCurrentValues() { workerLoginCode = settings.workerLoginCode workerEmail = settings.workerEmail - geminiAPIKey = settings.geminiAPIKey + workerAPIBearerToken = settings.workerAPIBearerToken opsBaseURL = settings.opsBaseURL adminBaseURL = settings.adminBaseURL signalBaseURL = settings.signalBaseURL - geminiSystemPrompt = settings.geminiSystemPrompt - openClawHost = settings.openClawHost - openClawPort = String(settings.openClawPort) - openClawTailscaleIP = settings.openClawTailscaleIP - openClawHookToken = settings.openClawHookToken - openClawGatewayToken = settings.openClawGatewayToken webrtcSignalingURL = settings.webrtcSignalingURL speakerOutputEnabled = settings.speakerOutputEnabled videoStreamingEnabled = settings.videoStreamingEnabled @@ -240,18 +184,10 @@ struct SettingsView: View { private func save() { settings.workerLoginCode = workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) settings.workerEmail = workerEmail.trimmingCharacters(in: .whitespacesAndNewlines) - settings.geminiAPIKey = geminiAPIKey.trimmingCharacters(in: .whitespacesAndNewlines) + settings.workerAPIBearerToken = workerAPIBearerToken.trimmingCharacters(in: .whitespacesAndNewlines) settings.opsBaseURL = opsBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) settings.adminBaseURL = adminBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) settings.signalBaseURL = signalBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) - settings.geminiSystemPrompt = geminiSystemPrompt.trimmingCharacters(in: .whitespacesAndNewlines) - settings.openClawHost = openClawHost.trimmingCharacters(in: .whitespacesAndNewlines) - if let port = Int(openClawPort.trimmingCharacters(in: .whitespacesAndNewlines)) { - settings.openClawPort = port - } - settings.openClawTailscaleIP = openClawTailscaleIP.trimmingCharacters(in: .whitespacesAndNewlines) - settings.openClawHookToken = openClawHookToken.trimmingCharacters(in: .whitespacesAndNewlines) - settings.openClawGatewayToken = openClawGatewayToken.trimmingCharacters(in: .whitespacesAndNewlines) settings.webrtcSignalingURL = webrtcSignalingURL.trimmingCharacters(in: .whitespacesAndNewlines) settings.speakerOutputEnabled = speakerOutputEnabled settings.videoStreamingEnabled = videoStreamingEnabled diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 6af765e4..19312ce5 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -349,16 +349,490 @@ private struct RemoteSOPItem: Decodable { } } +private final class ConversationAudioRecorder: @unchecked Sendable { + private enum Source: String { + case input = "worker_input" + case output = "gemini_output" + } + + private struct AudioChunk { + let data: Data + let sampleRate: Double + let source: Source + let hostTime: CFTimeInterval + } + + private let queue = DispatchQueue(label: "sop.conversation.audio.recorder", qos: .userInitiated) + private let sessionID: String? + private let recordingStartHostTime: CFTimeInterval + private let outputURL: URL + private var chunks: [AudioChunk] = [] + private var isFinishing = false + private var inputAudioChunkCount = 0 + private var outputAudioChunkCount = 0 + + init(sessionID: String?, recordingStartHostTime: CFTimeInterval = CACurrentMediaTime()) { + self.sessionID = sessionID + self.recordingStartHostTime = recordingStartHostTime + self.outputURL = FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(sessionID ?? UUID().uuidString)_conversation") + .appendingPathExtension("m4a") + try? FileManager.default.removeItem(at: outputURL) + } + + func appendInputAudio(_ data: Data) { + append(data, sampleRate: GeminiConfig.inputAudioSampleRate, source: .input) + } + + func appendOutputAudio(_ data: Data) { + append(data, sampleRate: GeminiConfig.outputAudioSampleRate, source: .output) + } + + func finishAudioFile() async -> URL? { + await withCheckedContinuation { continuation in + queue.async { [weak self] in + guard let self else { + continuation.resume(returning: nil) + return + } + + self.isFinishing = true + let chunks = self.chunks + let inputCount = self.inputAudioChunkCount + let outputCount = self.outputAudioChunkCount + guard !chunks.isEmpty else { + continuation.resume(returning: nil) + return + } + + let mixedPCM = Self.renderMixedPCM( + chunks: chunks, + recordingStartHostTime: self.recordingStartHostTime + ) + guard !mixedPCM.isEmpty else { + continuation.resume(returning: nil) + return + } + + Self.writeAACAudio(data: mixedPCM, outputURL: self.outputURL) { url in + let byteCount = url.flatMap { url -> Int? in + guard let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) else { + return nil + } + return attributes[.size] as? Int + } ?? 0 + Task { + await WorkerTelemetry.shared.record( + "conversation_audio_finish", + source: "media_upload", + stage: url == nil ? "failed" : "completed", + sessionID: self.sessionID, + metricValue: Double(byteCount), + metricUnit: "bytes", + payload: [ + "bytes": byteCount, + "input_audio_chunks": inputCount, + "output_audio_chunks": outputCount + ] + ) + } + continuation.resume(returning: url) + } + } + } + } + + private func append(_ data: Data, sampleRate: Double, source: Source) { + guard !data.isEmpty else { return } + let hostTime = CACurrentMediaTime() + queue.async { [weak self] in + guard let self, !self.isFinishing else { return } + switch source { + case .input: + self.inputAudioChunkCount += 1 + case .output: + self.outputAudioChunkCount += 1 + } + self.chunks.append( + AudioChunk( + data: data, + sampleRate: sampleRate, + source: source, + hostTime: hostTime + ) + ) + } + } + + private static func renderMixedPCM( + chunks: [AudioChunk], + recordingStartHostTime: CFTimeInterval + ) -> Data { + let targetSampleRate = GeminiConfig.outputAudioSampleRate + var renderedChunks: [(startFrame: Int, samples: [Float])] = [] + var totalFrameCount = 0 + + for chunk in chunks { + let samples = resampledFloatSamples( + from: chunk.data, + sourceSampleRate: chunk.sampleRate, + targetSampleRate: targetSampleRate + ) + guard !samples.isEmpty else { continue } + let startFrame = max(0, Int((chunk.hostTime - recordingStartHostTime) * targetSampleRate)) + totalFrameCount = max(totalFrameCount, startFrame + samples.count) + renderedChunks.append((startFrame, samples)) + } + + guard totalFrameCount > 0 else { return Data() } + + var mixed = [Float](repeating: 0, count: totalFrameCount) + var contributors = [UInt8](repeating: 0, count: totalFrameCount) + for rendered in renderedChunks { + for (offset, sample) in rendered.samples.enumerated() { + let index = rendered.startFrame + offset + guard index < mixed.count else { continue } + mixed[index] += sample + if contributors[index] < UInt8.max { + contributors[index] += 1 + } + } + } + + var int16Samples = [Int16](repeating: 0, count: totalFrameCount) + for index in mixed.indices { + let count = contributors[index] + let normalized = count > 1 ? mixed[index] / Float(count) : mixed[index] + let clamped = max(-1.0, min(1.0, normalized)) + int16Samples[index] = Int16(clamped * Float(Int16.max)) + } + + return int16Samples.withUnsafeBufferPointer { Data(buffer: $0) } + } + + private static func resampledFloatSamples( + from data: Data, + sourceSampleRate: Double, + targetSampleRate: Double + ) -> [Float] { + let sourceFrameCount = data.count / MemoryLayout.size + guard sourceFrameCount > 0 else { return [] } + + let sourceSamples: [Float] = data.withUnsafeBytes { rawBuffer in + guard let baseAddress = rawBuffer.bindMemory(to: Int16.self).baseAddress else { return [] } + return (0.. Void + ) { + try? FileManager.default.removeItem(at: outputURL) + guard let writer = try? AVAssetWriter(outputURL: outputURL, fileType: .m4a) else { + completion(nil) + return + } + + let audioInput = AVAssetWriterInput( + mediaType: .audio, + outputSettings: [ + AVFormatIDKey: kAudioFormatMPEG4AAC, + AVSampleRateKey: GeminiConfig.outputAudioSampleRate, + AVNumberOfChannelsKey: Int(GeminiConfig.audioChannels), + AVEncoderBitRateKey: 64_000 + ] + ) + audioInput.expectsMediaDataInRealTime = false + guard writer.canAdd(audioInput) else { + completion(nil) + return + } + writer.add(audioInput) + guard writer.startWriting() else { + completion(nil) + return + } + writer.startSession(atSourceTime: .zero) + + guard let formatDescription = makePCMFormatDescription() else { + completion(nil) + return + } + + let bytesPerFrame = MemoryLayout.size * Int(GeminiConfig.audioChannels) + let framesPerChunk = Int(GeminiConfig.outputAudioSampleRate) + let bytesPerChunk = framesPerChunk * bytesPerFrame + var byteOffset = 0 + var frameOffset = 0 + var appendFailed = false + + while byteOffset < data.count, !appendFailed { + while !audioInput.isReadyForMoreMediaData { + Thread.sleep(forTimeInterval: 0.005) + } + let byteCount = min(bytesPerChunk, data.count - byteOffset) + let alignedByteCount = byteCount - (byteCount % bytesPerFrame) + guard alignedByteCount > 0 else { break } + let chunkData = data.subdata(in: byteOffset..<(byteOffset + alignedByteCount)) + let presentationTime = CMTime( + value: CMTimeValue(frameOffset), + timescale: CMTimeScale(GeminiConfig.outputAudioSampleRate) + ) + guard let sampleBuffer = makeAudioSampleBuffer( + data: chunkData, + sampleRate: GeminiConfig.outputAudioSampleRate, + channels: GeminiConfig.audioChannels, + formatDescription: formatDescription, + presentationTime: presentationTime + ) else { + appendFailed = true + break + } + appendFailed = !audioInput.append(sampleBuffer) + byteOffset += alignedByteCount + frameOffset += alignedByteCount / bytesPerFrame + } + + audioInput.markAsFinished() + writer.finishWriting { + completion(!appendFailed && writer.status == .completed ? outputURL : nil) + } + } + + private static func makePCMFormatDescription() -> CMAudioFormatDescription? { + var streamDescription = AudioStreamBasicDescription( + mSampleRate: GeminiConfig.outputAudioSampleRate, + mFormatID: kAudioFormatLinearPCM, + mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked), + mBytesPerPacket: UInt32(MemoryLayout.size) * GeminiConfig.audioChannels, + mFramesPerPacket: 1, + mBytesPerFrame: UInt32(MemoryLayout.size) * GeminiConfig.audioChannels, + mChannelsPerFrame: GeminiConfig.audioChannels, + mBitsPerChannel: UInt32(MemoryLayout.size * 8), + mReserved: 0 + ) + var formatDescription: CMAudioFormatDescription? + let status = CMAudioFormatDescriptionCreate( + allocator: kCFAllocatorDefault, + asbd: &streamDescription, + layoutSize: 0, + layout: nil, + magicCookieSize: 0, + magicCookie: nil, + extensions: nil, + formatDescriptionOut: &formatDescription + ) + guard status == noErr else { return nil } + return formatDescription + } + + private static func makeAudioSampleBuffer( + data: Data, + sampleRate: Double, + channels: UInt32, + formatDescription: CMAudioFormatDescription, + presentationTime: CMTime + ) -> CMSampleBuffer? { + let bytesPerFrame = Int(MemoryLayout.size * Int(channels)) + guard bytesPerFrame > 0 else { return nil } + let sampleCount = data.count / bytesPerFrame + guard sampleCount > 0 else { return nil } + + var blockBuffer: CMBlockBuffer? + var status = CMBlockBufferCreateWithMemoryBlock( + allocator: kCFAllocatorDefault, + memoryBlock: nil, + blockLength: data.count, + blockAllocator: nil, + customBlockSource: nil, + offsetToData: 0, + dataLength: data.count, + flags: 0, + blockBufferOut: &blockBuffer + ) + guard status == noErr, let blockBuffer else { return nil } + + status = data.withUnsafeBytes { rawBuffer in + guard let baseAddress = rawBuffer.baseAddress else { return OSStatus(-1) } + return CMBlockBufferReplaceDataBytes( + with: baseAddress, + blockBuffer: blockBuffer, + offsetIntoDestination: 0, + dataLength: data.count + ) + } + guard status == noErr else { return nil } + + var timing = CMSampleTimingInfo( + duration: CMTime( + value: CMTimeValue(sampleCount), + timescale: CMTimeScale(sampleRate.rounded()) + ), + presentationTimeStamp: presentationTime, + decodeTimeStamp: .invalid + ) + var sampleBuffer: CMSampleBuffer? + status = CMSampleBufferCreate( + allocator: kCFAllocatorDefault, + dataBuffer: blockBuffer, + dataReady: true, + makeDataReadyCallback: nil, + refcon: nil, + formatDescription: formatDescription, + sampleCount: sampleCount, + sampleTimingEntryCount: 1, + sampleTimingArray: &timing, + sampleSizeEntryCount: 0, + sampleSizeArray: nil, + sampleBufferOut: &sampleBuffer + ) + guard status == noErr else { return nil } + return sampleBuffer + } +} + +private enum ConversationAudioMuxer { + static func mux(videoURL: URL, audioURL: URL, outputURL: URL) async -> URL? { + await withCheckedContinuation { continuation in + let videoAsset = AVURLAsset(url: videoURL) + let audioAsset = AVURLAsset(url: audioURL) + let composition = AVMutableComposition() + + guard + let videoTrack = videoAsset.tracks(withMediaType: .video).first, + let compositionVideoTrack = composition.addMutableTrack( + withMediaType: .video, + preferredTrackID: kCMPersistentTrackID_Invalid + ) + else { + continuation.resume(returning: nil) + return + } + + do { + try compositionVideoTrack.insertTimeRange( + CMTimeRange(start: .zero, duration: videoAsset.duration), + of: videoTrack, + at: .zero + ) + compositionVideoTrack.preferredTransform = videoTrack.preferredTransform + + if let audioTrack = audioAsset.tracks(withMediaType: .audio).first, + let compositionAudioTrack = composition.addMutableTrack( + withMediaType: .audio, + preferredTrackID: kCMPersistentTrackID_Invalid + ) { + let audioDuration = CMTimeCompare(audioAsset.duration, videoAsset.duration) > 0 + ? videoAsset.duration + : audioAsset.duration + if CMTimeCompare(audioDuration, .zero) > 0 { + try compositionAudioTrack.insertTimeRange( + CMTimeRange(start: .zero, duration: audioDuration), + of: audioTrack, + at: .zero + ) + } + } + } catch { + NSLog("[SOPRecorder] Failed to build mixed replay composition: %@", error.localizedDescription) + continuation.resume(returning: nil) + return + } + + try? FileManager.default.removeItem(at: outputURL) + guard let exporter = AVAssetExportSession( + asset: composition, + presetName: AVAssetExportPresetHighestQuality + ) else { + continuation.resume(returning: nil) + return + } + exporter.outputURL = outputURL + exporter.outputFileType = .mp4 + exporter.shouldOptimizeForNetworkUse = true + exporter.exportAsynchronously { + continuation.resume(returning: exporter.status == .completed ? outputURL : nil) + } + } + } +} + private final class SopVideoRecorder: @unchecked Sendable { + private enum AudioTrackKind: String { + case input = "worker_input" + case output = "gemini_output" + } + + private struct PendingAudioChunk { + let data: Data + let sampleRate: Double + let source: AudioTrackKind + let hostTime: CFTimeInterval + } + private let queue = DispatchQueue(label: "sop.video.recorder", qos: .userInitiated) + private let sessionID: String? private var writer: AVAssetWriter? private var writerInput: AVAssetWriterInput? + private var inputAudioInput: AVAssetWriterInput? + private var outputAudioInput: AVAssetWriterInput? + private var inputAudioFormatDescription: CMAudioFormatDescription? + private var outputAudioFormatDescription: CMAudioFormatDescription? private var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor? - private var recordingStartHostTime: CFTimeInterval? + private let recordingStartHostTime: CFTimeInterval + private let conversationRecorder: ConversationAudioRecorder private(set) var outputURL: URL? private var isFinishing = false private var appendedFrameCount = 0 + private var inputAudioChunkCount = 0 + private var outputAudioChunkCount = 0 + private var droppedAudioChunkCount = 0 + private var pendingAudioChunks: [PendingAudioChunk] = [] private let sourcePixelFormat = VideoFrameBufferFactory.pixelFormat + private let maxPendingAudioChunks = 160 + + init(sessionID: String? = nil) { + self.sessionID = sessionID + let startHostTime = CACurrentMediaTime() + self.recordingStartHostTime = startHostTime + self.conversationRecorder = ConversationAudioRecorder( + sessionID: sessionID, + recordingStartHostTime: startHostTime + ) + let fileURL = FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(sessionID ?? UUID().uuidString)") + .appendingPathExtension("mp4") + try? FileManager.default.removeItem(at: fileURL) + self.outputURL = fileURL + NSLog("[SOPRecorder] Prepared output path at %@", fileURL.path) + Task { + await WorkerTelemetry.shared.record( + "sop_recorder_start", + source: "media_upload", + stage: "prepared", + sessionID: sessionID, + payload: ["path_ready": true] + ) + } + } func appendFrame(_ image: UIImage) { queue.async { [weak self] in @@ -390,6 +864,22 @@ private final class SopVideoRecorder: @unchecked Sendable { } } + func appendInputAudio(_ data: Data) { + guard !data.isEmpty else { return } + conversationRecorder.appendInputAudio(data) + queue.async { [weak self] in + self?.inputAudioChunkCount += 1 + } + } + + func appendOutputAudio(_ data: Data) { + guard !data.isEmpty else { return } + conversationRecorder.appendOutputAudio(data) + queue.async { [weak self] in + self?.outputAudioChunkCount += 1 + } + } + func finishRecording() async -> URL? { await withCheckedContinuation { continuation in queue.async { [weak self] in @@ -399,8 +889,11 @@ private final class SopVideoRecorder: @unchecked Sendable { } NSLog( - "[SOPRecorder] finishRecording called (frames=%d, hasWriter=%@, outputURL=%@)", + "[SOPRecorder] finishRecording called (frames=%d, inputAudio=%d, outputAudio=%d, droppedAudio=%d, hasWriter=%@, outputURL=%@)", self.appendedFrameCount, + self.inputAudioChunkCount, + self.outputAudioChunkCount, + self.droppedAudioChunkCount, self.writer == nil ? "no" : "yes", self.outputURL?.path ?? "nil") @@ -410,7 +903,22 @@ private final class SopVideoRecorder: @unchecked Sendable { if let writer = self.writer { NSLog("[SOPRecorder] finishRecording returning nil because writer status=%d", writer.status.rawValue) } else { - NSLog("[SOPRecorder] finishRecording returning nil because writer was never created") + NSLog("[SOPRecorder] finishRecording returning nil because no video frames were recorded") + } + Task { + await WorkerTelemetry.shared.record( + "sop_recorder_finish", + source: "media_upload", + stage: "missing_video", + sessionID: self.sessionID, + payload: [ + "frame_count": self.appendedFrameCount, + "audio_input_chunks": self.inputAudioChunkCount, + "audio_output_chunks": self.outputAudioChunkCount, + "dropped_audio_chunks": self.droppedAudioChunkCount, + "reason": "no_video_frames_recorded" + ] + ) } continuation.resume(returning: nil) return @@ -419,11 +927,60 @@ private final class SopVideoRecorder: @unchecked Sendable { self.isFinishing = true writerInput.markAsFinished() writer.finishWriting { - NSLog( - "[SOPRecorder] finishWriting completed (status=%d, outputURL=%@)", - writer.status.rawValue, - self.outputURL?.path ?? "nil") - continuation.resume(returning: writer.status == .completed ? self.outputURL : nil) + Task { + let videoURL = self.outputURL + let audioURL = await self.conversationRecorder.finishAudioFile() + var finalURL = videoURL + if writer.status == .completed, let videoURL, let audioURL { + let mixedURL = videoURL + .deletingLastPathComponent() + .appendingPathComponent(videoURL.deletingPathExtension().lastPathComponent + "_mixed") + .appendingPathExtension("mp4") + if let muxedURL = await ConversationAudioMuxer.mux( + videoURL: videoURL, + audioURL: audioURL, + outputURL: mixedURL + ) { + finalURL = muxedURL + try? FileManager.default.removeItem(at: videoURL) + try? FileManager.default.removeItem(at: audioURL) + } else { + NSLog("[SOPRecorder] Mixed audio mux failed; returning video-only recording") + } + } + + let fileSize = finalURL.flatMap { url -> Int? in + guard let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) else { + return nil + } + return attributes[.size] as? Int + } + NSLog( + "[SOPRecorder] finishWriting completed (status=%d, outputURL=%@, bytes=%d)", + writer.status.rawValue, + finalURL?.path ?? "nil", + fileSize ?? 0) + Task { + await WorkerTelemetry.shared.record( + "sop_recorder_finish", + source: "media_upload", + stage: writer.status == .completed ? "completed" : "failed", + sessionID: self.sessionID, + metricValue: Double(fileSize ?? 0), + metricUnit: "bytes", + payload: [ + "frame_count": self.appendedFrameCount, + "file_size": fileSize ?? 0, + "audio_input_chunks": self.inputAudioChunkCount, + "audio_output_chunks": self.outputAudioChunkCount, + "dropped_audio_chunks": self.droppedAudioChunkCount, + "writer_status": writer.status.rawValue, + "writer_error": writer.error?.localizedDescription ?? NSNull() + ] + ) + } + continuation.resume(returning: writer.status == .completed ? finalURL : nil) + } } } } @@ -434,8 +991,7 @@ private final class SopVideoRecorder: @unchecked Sendable { writer.status == .writing, let writerInput = writerInput, let adaptor = pixelBufferAdaptor, - writerInput.isReadyForMoreMediaData, - let start = recordingStartHostTime else { + writerInput.isReadyForMoreMediaData else { if writer == nil { NSLog("[SOPRecorder] Dropping frame because writer was never configured") } else if let writer { @@ -444,7 +1000,7 @@ private final class SopVideoRecorder: @unchecked Sendable { return } - let elapsed = CACurrentMediaTime() - start + let elapsed = CACurrentMediaTime() - recordingStartHostTime let presentationTime = CMTime(seconds: max(0, elapsed), preferredTimescale: 600) let bufferForWriter = VideoFrameBufferFactory.copyPixelBuffer(pixelBuffer, using: adaptor.pixelBufferPool) @@ -455,6 +1011,14 @@ private final class SopVideoRecorder: @unchecked Sendable { appendedFrameCount += 1 if appendedFrameCount == 1 { NSLog("[SOPRecorder] First frame appended successfully") + Task { + await WorkerTelemetry.shared.record( + "sop_recorder_first_frame", + source: "media_upload", + stage: "recording", + sessionID: sessionID + ) + } } else if appendedFrameCount % 60 == 0 { NSLog("[SOPRecorder] Appended %d frames", appendedFrameCount) } @@ -476,8 +1040,8 @@ private final class SopVideoRecorder: @unchecked Sendable { return } - let fileURL = FileManager.default.temporaryDirectory - .appendingPathComponent("sop_\(UUID().uuidString)") + let fileURL = outputURL ?? FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(sessionID ?? UUID().uuidString)") .appendingPathExtension("mp4") try? FileManager.default.removeItem(at: fileURL) @@ -516,6 +1080,8 @@ private final class SopVideoRecorder: @unchecked Sendable { return } writer.add(input) + // Conversation audio is mixed into one AAC track during finishRecording(), + // so the live writer stays focused on video frames. guard writer.startWriting() else { NSLog("[SOPRecorder] startWriting failed: %@", writer.error?.localizedDescription ?? "unknown") @@ -527,8 +1093,198 @@ private final class SopVideoRecorder: @unchecked Sendable { self.writer = writer self.writerInput = input self.pixelBufferAdaptor = adaptor - self.recordingStartHostTime = CACurrentMediaTime() self.outputURL = fileURL + drainPendingAudioChunks() + } + + private func appendAudio( + _ data: Data, + sampleRate: Double, + source: AudioTrackKind + ) { + guard !data.isEmpty else { return } + let hostTime = CACurrentMediaTime() + queue.async { [weak self] in + guard let self, !self.isFinishing else { return } + switch source { + case .input: + self.inputAudioChunkCount += 1 + case .output: + self.outputAudioChunkCount += 1 + } + let chunk = PendingAudioChunk( + data: data, + sampleRate: sampleRate, + source: source, + hostTime: hostTime + ) + + guard self.writer?.status == .writing else { + self.pendingAudioChunks.append(chunk) + if self.pendingAudioChunks.count > self.maxPendingAudioChunks { + self.pendingAudioChunks.removeFirst(self.pendingAudioChunks.count - self.maxPendingAudioChunks) + self.droppedAudioChunkCount += 1 + } + return + } + + self.appendAudioChunkInternal(chunk) + } + } + + private func drainPendingAudioChunks() { + guard !pendingAudioChunks.isEmpty else { return } + let chunks = pendingAudioChunks + pendingAudioChunks.removeAll() + for chunk in chunks { + appendAudioChunkInternal(chunk) + } + } + + private func appendAudioChunkInternal(_ chunk: PendingAudioChunk) { + let audioInput: AVAssetWriterInput? + let formatDescription: CMAudioFormatDescription? + switch chunk.source { + case .input: + audioInput = inputAudioInput + formatDescription = inputAudioFormatDescription + case .output: + audioInput = outputAudioInput + formatDescription = outputAudioFormatDescription + } + + guard let audioInput, let formatDescription else { + droppedAudioChunkCount += 1 + return + } + guard audioInput.isReadyForMoreMediaData else { + droppedAudioChunkCount += 1 + return + } + guard let sampleBuffer = Self.makeAudioSampleBuffer( + data: chunk.data, + sampleRate: chunk.sampleRate, + channels: GeminiConfig.audioChannels, + formatDescription: formatDescription, + presentationTime: CMTime( + seconds: max(0, chunk.hostTime - recordingStartHostTime), + preferredTimescale: 600 + ) + ) else { + droppedAudioChunkCount += 1 + return + } + + if !audioInput.append(sampleBuffer) { + droppedAudioChunkCount += 1 + NSLog("[SOPRecorder] Failed appending %@ audio chunk", chunk.source.rawValue) + } + } + + private func makeAudioInput(sampleRate: Double, channels: UInt32) -> AVAssetWriterInput { + let outputSettings: [String: Any] = [ + AVFormatIDKey: kAudioFormatMPEG4AAC, + AVSampleRateKey: sampleRate, + AVNumberOfChannelsKey: Int(channels), + AVEncoderBitRateKey: 64_000 + ] + let input = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings) + input.expectsMediaDataInRealTime = true + return input + } + + private static func makePCMFormatDescription( + sampleRate: Double, + channels: UInt32 + ) -> CMAudioFormatDescription? { + var streamDescription = AudioStreamBasicDescription( + mSampleRate: sampleRate, + mFormatID: kAudioFormatLinearPCM, + mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked), + mBytesPerPacket: UInt32(MemoryLayout.size) * channels, + mFramesPerPacket: 1, + mBytesPerFrame: UInt32(MemoryLayout.size) * channels, + mChannelsPerFrame: channels, + mBitsPerChannel: UInt32(MemoryLayout.size * 8), + mReserved: 0 + ) + var formatDescription: CMAudioFormatDescription? + let status = CMAudioFormatDescriptionCreate( + allocator: kCFAllocatorDefault, + asbd: &streamDescription, + layoutSize: 0, + layout: nil, + magicCookieSize: 0, + magicCookie: nil, + extensions: nil, + formatDescriptionOut: &formatDescription + ) + guard status == noErr else { return nil } + return formatDescription + } + + private static func makeAudioSampleBuffer( + data: Data, + sampleRate: Double, + channels: UInt32, + formatDescription: CMAudioFormatDescription, + presentationTime: CMTime + ) -> CMSampleBuffer? { + let bytesPerFrame = Int(MemoryLayout.size * Int(channels)) + guard bytesPerFrame > 0 else { return nil } + let sampleCount = data.count / bytesPerFrame + guard sampleCount > 0 else { return nil } + + var blockBuffer: CMBlockBuffer? + var status = CMBlockBufferCreateWithMemoryBlock( + allocator: kCFAllocatorDefault, + memoryBlock: nil, + blockLength: data.count, + blockAllocator: nil, + customBlockSource: nil, + offsetToData: 0, + dataLength: data.count, + flags: 0, + blockBufferOut: &blockBuffer + ) + guard status == noErr, let blockBuffer else { return nil } + + status = data.withUnsafeBytes { rawBuffer in + guard let baseAddress = rawBuffer.baseAddress else { return OSStatus(-1) } + return CMBlockBufferReplaceDataBytes( + with: baseAddress, + blockBuffer: blockBuffer, + offsetIntoDestination: 0, + dataLength: data.count + ) + } + guard status == noErr else { return nil } + + var timing = CMSampleTimingInfo( + duration: CMTime( + value: CMTimeValue(sampleCount), + timescale: CMTimeScale(sampleRate.rounded()) + ), + presentationTimeStamp: presentationTime, + decodeTimeStamp: .invalid + ) + var sampleBuffer: CMSampleBuffer? + status = CMSampleBufferCreate( + allocator: kCFAllocatorDefault, + dataBuffer: blockBuffer, + dataReady: true, + makeDataReadyCallback: nil, + refcon: nil, + formatDescription: formatDescription, + sampleCount: sampleCount, + sampleTimingEntryCount: 1, + sampleTimingArray: &timing, + sampleSizeEntryCount: 0, + sampleSizeArray: nil, + sampleBufferOut: &sampleBuffer + ) + guard status == noErr else { return nil } + return sampleBuffer } private static func normalizedSize(width: Int, height: Int) -> CGSize { @@ -810,12 +1566,14 @@ struct WorkerMediaUploadResult: Equatable { actor WorkerAdminLiveSessionCoordinator { typealias Sleeper = @Sendable (UInt64) async -> Void typealias FileLoader = @Sendable (URL) async -> Data? + typealias HeartbeatResponseHandler = @Sendable (WorkerLiveHeartbeatResponse) async -> Void private let api: WorkerAdminAPI private let telemetry: WorkerTelemetry? private let heartbeatIntervalNanoseconds: UInt64 private let sleeper: Sleeper private let fileLoader: FileLoader + private let onHeartbeatResponse: HeartbeatResponseHandler? private var sessionID: String? private var roomCode: String? @@ -832,6 +1590,7 @@ actor WorkerAdminLiveSessionCoordinator { sessionID: String? = nil, heartbeatIntervalNanoseconds: UInt64 = 7_000_000_000, telemetry: WorkerTelemetry? = nil, + onHeartbeatResponse: HeartbeatResponseHandler? = nil, sleeper: @escaping Sleeper = { nanoseconds in guard nanoseconds > 0 else { return } try? await Task.sleep(nanoseconds: nanoseconds) @@ -848,6 +1607,7 @@ actor WorkerAdminLiveSessionCoordinator { self.heartbeatIntervalNanoseconds = heartbeatIntervalNanoseconds self.sleeper = sleeper self.fileLoader = fileLoader + self.onHeartbeatResponse = onHeartbeatResponse } func start( @@ -956,7 +1716,29 @@ actor WorkerAdminLiveSessionCoordinator { } else { data = nil byteSize = 0 - missingDataError = "Recording file was not created." + missingDataError = "Recording file was not created because no video frames were recorded." + } + + guard let data, !data.isEmpty else { + WorkerLiveLogger.log( + "recording_missing", + sessionID: sessionID, + roomCode: roomCode, + assetType: "video", + byteSize: byteSize, + uploadState: "failed", + error: missingDataError, + telemetry: telemetry + ) + return WorkerMediaUploadResult( + assetType: "video", + assetID: nil, + bucket: nil, + path: nil, + byteSize: byteSize, + uploadState: "failed", + errorMessage: missingDataError + ) } return await uploadAsset( @@ -1050,7 +1832,8 @@ actor WorkerAdminLiveSessionCoordinator { path: lastFramePath, uploadState: "active" ) { - try await api.sendWorkerLiveHeartbeat(heartbeat) + let response = try await api.sendWorkerLiveHeartbeat(heartbeat) + await onHeartbeatResponse?(response) } WorkerLiveLogger.log( @@ -1542,6 +2325,77 @@ private struct IPhoneAnalysisFrameEnvelope: @unchecked Sendable { let enqueuedAt: CFTimeInterval } +private struct SendableStreamPixelBuffer: @unchecked Sendable { + let pixelBuffer: CVPixelBuffer +} + +private struct SendableSampleBuffer: @unchecked Sendable { + let sampleBuffer: CMSampleBuffer +} + +private struct SendableDecodedVideoFrame: @unchecked Sendable { + let pixelBuffer: CVPixelBuffer + let presentationTimeStamp: CMTime + + init(_ frame: VideoDecoder.DecodedFrame) { + pixelBuffer = frame.pixelBuffer + presentationTimeStamp = frame.presentationTimeStamp + } +} + +private final class StreamPixelBufferImageRenderer: @unchecked Sendable { + private let context = CIContext(options: [.useSoftwareRenderer: true]) + + func makeUIImage(from pixelBuffer: CVPixelBuffer) -> UIImage? { + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + guard width > 0, height > 0 else { return nil } + + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let rect = CGRect(x: 0, y: 0, width: width, height: height) + guard let cgImage = context.createCGImage(ciImage, from: rect) else { return nil } + return UIImage(cgImage: cgImage) + } +} + +private final class GlassesVideoDecodeLane: @unchecked Sendable { + private let queue = DispatchQueue( + label: "visionclaw.glasses-video-decode-lane", + qos: .userInitiated + ) + private let decoder = VideoDecoder() + private var onFrameDecoded: (@Sendable (SendableDecodedVideoFrame) -> Void)? + + init() { + decoder.setFrameCallback { [weak self] frame in + self?.onFrameDecoded?(SendableDecodedVideoFrame(frame)) + } + } + + func setFrameCallback(_ callback: @escaping @Sendable (SendableDecodedVideoFrame) -> Void) { + queue.sync { + self.onFrameDecoded = callback + } + } + + func decode(_ sampleBuffer: CMSampleBuffer, onError: @escaping @Sendable (String) -> Void) { + let sendableSampleBuffer = SendableSampleBuffer(sampleBuffer: sampleBuffer) + queue.async { + do { + try self.decoder.decode(sendableSampleBuffer.sampleBuffer) + } catch { + onError(String(describing: error)) + } + } + } + + func invalidateSession() { + queue.async { + self.decoder.invalidateSession() + } + } +} + private final class IPhoneAnalysisLane: @unchecked Sendable { private let queue = DispatchQueue( label: "visionclaw.iphone.analysis-lane", @@ -1621,6 +2475,26 @@ private final class IPhoneAnalysisLane: @unchecked Sendable { } } +private final class LivePreviewFrameEncoder: @unchecked Sendable { + private let queue = DispatchQueue( + label: "visionclaw.live-preview-frame-encoder", + qos: .utility + ) + + func encode( + image: UIImage, + maxDimension: CGFloat, + compressionQuality: CGFloat + ) async -> Data? { + await withCheckedContinuation { continuation in + queue.async { + let previewImage = image.resizedForLivePreview(maxDimension: maxDimension) + continuation.resume(returning: previewImage.jpegData(compressionQuality: compressionQuality)) + } + } + } +} + @MainActor class StreamSessionViewModel: ObservableObject { @Published var currentVideoFrame: UIImage? @@ -1640,7 +2514,11 @@ class StreamSessionViewModel: ObservableObject { @Published var shouldDismissCapture: Bool = false @Published var showShipSuccessToast: Bool = false @Published var isListeningForVoice: Bool = false + @Published var isAiGuideStarting: Bool = false + @Published var isStepValidationRunning: Bool = false + @Published var aiGuideStatusMessage: String = "" @Published var isDossierUploading: Bool = false + @Published var isSwitchingCaptureMode: Bool = false @Published var dossierPipelineStatusMessage: String = "" @Published var dossierPipelineStatusKind: DossierPipelineStatusKind = .info @Published var dossierPipelineStatusTimestamp: String = "" @@ -1757,6 +2635,23 @@ class StreamSessionViewModel: ObservableObject { isSopAuditRunning } + var canTapBackOfficeCall: Bool { + canRequestHelp && !isRequestingHelp && !hasActiveHelpEscalation + } + + var backOfficeCallButtonTitle: String { + if webrtcViewModel.isActive { + return "LIVE" + } + if isRequestingHelp { + return "CALLING" + } + if hasActiveHelpEscalation { + return "RINGING" + } + return "CALL" + } + var canCloseCurrentPackage: Bool { guard activePackageRunID != nil else { return false } guard let key = currentPackageCompletionKey, !currentPackageRequiredRemoteIDs.isEmpty else { return false } @@ -1799,6 +2694,35 @@ class StreamSessionViewModel: ObservableObject { : "Using iPhone until glasses are available." } + var aiGuideButtonTitle: String { + if isAiGuideStarting { + return "STARTING AI" + } + if geminiAssistant.isGeminiActive && geminiAssistant.isAudioReady { + return "AI LISTENING" + } + if geminiAssistant.isGeminiActive { + return "AI CONNECTING" + } + return "RESUME AI" + } + + var canToggleAiGuide: Bool { + isSopAuditRunning && !isAiGuideStarting && !hasActiveHelpEscalation + } + + var canSwitchCaptureMode: Bool { + !isSwitchingCaptureMode && + !isRequestingHelp && + !webrtcViewModel.isActive && + !isDossierUploading && + !isFinalizingAndShipping + } + + var canRequestStepValidation: Bool { + isSopAuditRunning && !isStepValidationRunning && !hasActiveHelpEscalation + } + // Photo capture properties @Published var capturedPhoto: UIImage? @Published var showPhotoPreview: Bool = false @@ -1819,21 +2743,24 @@ class StreamSessionViewModel: ObservableObject { private var proofImagesByTargetID: [String: Data] = [:] private var spotterEvidenceWindow = SpotterEvidenceWindow() private var lastSpotterInferenceTime: Date = .distantPast + private var currentStepBecameActiveAt: Date = Date() private var isSpotterInferenceInFlight = false private var isFinalizingAndShipping = false private var successToastTask: Task? private var hasLoadedWorkerContext = false private var hasEnteredWorkerHome = false - private var autoPresentedAssignmentKeys: Set = [] private var isUsingLocalSessionFallback = false private var roomCodeCancellable: AnyCancellable? private var connectionStateCancellable: AnyCancellable? + private var livePressureCancellable: AnyCancellable? private var locallyCompletedSopsByPackageKey: [String: Set] = [:] private var lastLivePreviewSyncAt: Date = .distantPast private var hasActiveHelpEscalation = false private var hasLoggedRoomCreatedForSession = false private var hasLoggedRoomJoinedForSession = false private var didAttemptPendingRecordingRecovery = false + private var shouldResumeAiSupportAfterBackOffice = false + private var isLiveRoomHandoffInProgress = false private var isDemoWorkerMode: Bool { let configuredCode = GeminiConfig.workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) @@ -1857,6 +2784,8 @@ class StreamSessionViewModel: ObservableObject { private var speechRequest: SFSpeechAudioBufferRecognitionRequest? private var speechTask: SFSpeechRecognitionTask? private var lastProcessedTranscript: String = "" + private var lastAiCommandKey: String = "" + private var lastAiCommandAt: Date = .distantPast private var currentPackageCompletionKey: String? { if let runID = activePackageRunID { @@ -1902,14 +2831,17 @@ class StreamSessionViewModel: ObservableObject { private let deviceSelector: AutoDeviceSelector private var deviceMonitorTask: Task? private var iPhoneCameraManager: IPhoneCameraManager? + private var conversationAudioRecorder: ConversationAudioRecorder? + private var holdToTalkAudioLease: WorkerAudioRouteLease? + private var viewerAudioRouteLease: WorkerAudioRouteLease? private let iPhoneAnalysisLane = IPhoneAnalysisLane() + private let livePreviewFrameEncoder = LivePreviewFrameEncoder() + private let streamImageRenderer = StreamPixelBufferImageRenderer() + private let videoDecodeLane = GlassesVideoDecodeLane() - // CPU-based CIContext for rendering decoded pixel buffers in background - private let cpuCIContext = CIContext(options: [.useSoftwareRenderer: true]) - // VideoDecoder for decompressing HEVC/H.264 frames in background - private let videoDecoder = VideoDecoder() private var backgroundFrameCount = 0 private var bgDiagLogged = false + private var lastGlassesAnalysisFrameQueuedAt: CFTimeInterval = 0 init(wearables: WearablesInterface) { self.wearables = wearables @@ -1934,6 +2866,19 @@ class StreamSessionViewModel: ObservableObject { loadHistoryFromDefaults() requestSpeechPermissionsIfNeeded() observeWebRTCSession() + geminiAssistant.onInputCommand = { [weak self] transcript in + Task { @MainActor [weak self] in + self?.handleVoiceTranscript(transcript) + } + } + geminiAssistant.onInputAudioChunk = { [weak self] data in + self?.sopVideoRecorder?.appendInputAudio(data) + self?.conversationAudioRecorder?.appendInputAudio(data) + } + geminiAssistant.onOutputAudioChunk = { [weak self] data in + self?.sopVideoRecorder?.appendOutputAudio(data) + self?.conversationAudioRecorder?.appendOutputAudio(data) + } iPhoneAnalysisLane.onFrameReady = { [weak self] frame, completion in Task { @MainActor [weak self] in guard let self else { @@ -1947,14 +2892,13 @@ class StreamSessionViewModel: ObservableObject { } private func setupVideoDecoder() { - videoDecoder.setFrameCallback { [weak self] decodedFrame in + let imageRenderer = streamImageRenderer + videoDecodeLane.setFrameCallback { [weak self, imageRenderer] decodedFrame in Task { @MainActor [weak self] in guard let self else { return } let pixelBuffer = decodedFrame.pixelBuffer let width = CVPixelBufferGetWidth(pixelBuffer) let height = CVPixelBufferGetHeight(pixelBuffer) - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - let rect = CGRect(x: 0, y: 0, width: width, height: height) let timeStampNs = decodedFrame.presentationTimeStamp.isValid ? Int64(CMTimeGetSeconds(decodedFrame.presentationTimeStamp) * 1_000_000_000) : VideoFrameBufferFactory.currentTimestampNs() @@ -1964,18 +2908,22 @@ class StreamSessionViewModel: ObservableObject { timeStampNs: timeStampNs ) } - if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { - let image = UIImage(cgImage: cgImage) - self.handleProcessedLiveFrame( - image: image, - pixelBuffer: pixelBuffer, - timeStampNs: timeStampNs, - shouldForwardToWebRTC: false, - shouldRecordAudit: self.isSopAuditRunning - ) - if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { - NSLog("[Stream] Background frame #%d decoded and forwarded (%dx%d)", - self.backgroundFrameCount, width, height) + let shouldRecordAudit = self.isSopAuditRunning + if shouldRecordAudit { + self.sopVideoRecorder?.appendPixelBuffer(pixelBuffer) + } + + guard self.shouldQueueGlassesAnalysisFrame(now: CACurrentMediaTime()) else { return } + let sendablePixelBuffer = SendableStreamPixelBuffer(pixelBuffer: pixelBuffer) + self.liveFrameProcessingQueue.async { [weak self] in + guard let image = imageRenderer.makeUIImage(from: sendablePixelBuffer.pixelBuffer) else { return } + Task { @MainActor [weak self] in + guard let self else { return } + self.handleAnalysisImageFrame(image, shouldRecordAudit: shouldRecordAudit) + if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { + NSLog("[Stream] Background frame #%d decoded and forwarded (%dx%d)", + self.backgroundFrameCount, width, height) + } } } } @@ -2021,32 +2969,49 @@ class StreamSessionViewModel: ObservableObject { self.backgroundFrameCount = 0 self.bgDiagLogged = false - self.liveFrameProcessingQueue.async { [weak self] in - guard let self else { return } - guard let image = videoFrame.makeUIImage() else { return } - - let pixelBuffer = - (shouldForwardToWebRTC || shouldRecordAudit) - ? VideoFrameBufferFactory.makePixelBuffer(from: image) - : nil - let timeStampNs = VideoFrameBufferFactory.currentTimestampNs() - + let sampleBuffer = videoFrame.sampleBuffer + let timeStampNs = Self.rtcTimestampNs(from: sampleBuffer) + if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { if shouldForwardToWebRTC { - if let pixelBuffer { - realtimeVideoForwarder.enqueuePixelBuffer(pixelBuffer, timeStampNs: timeStampNs) - } else { - realtimeVideoForwarder.enqueueImage(image) + realtimeVideoForwarder.enqueuePixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } + if shouldRecordAudit { + self.sopVideoRecorder?.appendPixelBuffer(pixelBuffer) + } + guard self.shouldQueueGlassesAnalysisFrame(now: CACurrentMediaTime()) else { return } + let sendablePixelBuffer = SendableStreamPixelBuffer(pixelBuffer: pixelBuffer) + let imageRenderer = self.streamImageRenderer + self.liveFrameProcessingQueue.async { [weak self] in + guard let image = imageRenderer.makeUIImage(from: sendablePixelBuffer.pixelBuffer) else { return } + Task { @MainActor [weak self] in + self?.handleAnalysisImageFrame(image, shouldRecordAudit: shouldRecordAudit) } } + } else if CMSampleBufferGetDataBuffer(sampleBuffer) != nil { + let decodeLane = self.videoDecodeLane + decodeLane.decode(sampleBuffer) { errorMessage in + NSLog("[Stream] Foreground decode error: %@", errorMessage) + } + } else { + guard self.shouldQueueGlassesAnalysisFrame(now: CACurrentMediaTime()) else { return } + self.liveFrameProcessingQueue.async { [weak self] in + guard let self else { return } + guard let image = videoFrame.makeUIImage() else { return } + let timeStampNs = VideoFrameBufferFactory.currentTimestampNs() - Task { @MainActor [weak self] in - self?.handleProcessedLiveFrame( - image: image, - pixelBuffer: pixelBuffer, - timeStampNs: timeStampNs, - shouldForwardToWebRTC: false, - shouldRecordAudit: shouldRecordAudit - ) + if shouldForwardToWebRTC { + realtimeVideoForwarder.enqueueImage(image) + } + + Task { @MainActor [weak self] in + self?.handleProcessedLiveFrame( + image: image, + pixelBuffer: nil, + timeStampNs: timeStampNs, + shouldForwardToWebRTC: false, + shouldRecordAudit: shouldRecordAudit + ) + } } } } else { @@ -2060,35 +3025,34 @@ class StreamSessionViewModel: ObservableObject { if hasCompressedData { // Compressed frame (HEVC/H.264) - decode via VTDecompressionSession - do { - try self.videoDecoder.decode(sampleBuffer) - } catch { - if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { + let decodeLane = self.videoDecodeLane + let frameCount = self.backgroundFrameCount + decodeLane.decode(sampleBuffer) { errorMessage in + if frameCount <= 5 || frameCount % 120 == 0 { NSLog("[Stream] Background frame #%d decode error: %@", - self.backgroundFrameCount, String(describing: error)) + frameCount, errorMessage) } } } else if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { // Raw pixel buffer - convert directly via CPU CIContext - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) let timeStampNs = Self.rtcTimestampNs(from: sampleBuffer) if shouldForwardToWebRTC { realtimeVideoForwarder.enqueuePixelBuffer(pixelBuffer, timeStampNs: timeStampNs) } - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - let rect = CGRect(x: 0, y: 0, width: width, height: height) - if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { - let image = UIImage(cgImage: cgImage) - self.handleProcessedLiveFrame( - image: image, - pixelBuffer: pixelBuffer, - timeStampNs: timeStampNs, - shouldForwardToWebRTC: false, - shouldRecordAudit: shouldRecordAudit - ) + if shouldRecordAudit { + self.sopVideoRecorder?.appendPixelBuffer(pixelBuffer) + } + if self.shouldQueueGlassesAnalysisFrame(now: CACurrentMediaTime()) { + let sendablePixelBuffer = SendableStreamPixelBuffer(pixelBuffer: pixelBuffer) + let imageRenderer = self.streamImageRenderer + self.liveFrameProcessingQueue.async { [weak self] in + guard let image = imageRenderer.makeUIImage(from: sendablePixelBuffer.pixelBuffer) else { return } + Task { @MainActor [weak self] in + self?.handleAnalysisImageFrame(image, shouldRecordAudit: shouldRecordAudit) + } + } } - self.videoDecoder.invalidateSession() + self.videoDecodeLane.invalidateSession() } } } @@ -2168,6 +3132,18 @@ class StreamSessionViewModel: ObservableObject { } } + handleAnalysisImageFrame(image, shouldRecordAudit: shouldRecordAudit) + + if shouldRecordAudit { + if let pixelBuffer { + sopVideoRecorder?.appendPixelBuffer(pixelBuffer) + } else { + sopVideoRecorder?.appendFrame(image) + } + } + } + + private func handleAnalysisImageFrame(_ image: UIImage, shouldRecordAudit: Bool) { currentVideoFrame = image if !hasReceivedFirstFrame { hasReceivedFirstFrame = true @@ -2177,27 +3153,24 @@ class StreamSessionViewModel: ObservableObject { if shouldRecordAudit { Task { await syncLivePreviewFrameIfNeeded(image: image) } - if let pixelBuffer { - sopVideoRecorder?.appendPixelBuffer(pixelBuffer) - } else { - sopVideoRecorder?.appendFrame(image) - } - spotChecklistItemsIfThrottled(image: image) } } + private func shouldQueueGlassesAnalysisFrame(now: CFTimeInterval) -> Bool { + let interval: CFTimeInterval = webrtcViewModel.isUnderLiveVideoPressure ? 0.2 : 0.1 + if !hasReceivedFirstFrame || now - lastGlassesAnalysisFrameQueuedAt >= interval { + lastGlassesAnalysisFrameQueuedAt = now + return true + } + return false + } + private func enqueueIPhoneAnalysisFrame(_ image: UIImage, shouldRecordAudit: Bool) { iPhoneAnalysisLane.submit(image, shouldRecordAudit: shouldRecordAudit) } private func handleIPhoneAnalysisFrame(_ frame: IPhoneAnalysisFrameEnvelope) { - currentVideoFrame = frame.image - geminiAssistant.sendVideoFrameIfThrottled(image: frame.image) - - if frame.shouldRecordAudit { - Task { await syncLivePreviewFrameIfNeeded(image: frame.image) } - spotChecklistItemsIfThrottled(image: frame.image) - } + handleAnalysisImageFrame(frame.image, shouldRecordAudit: frame.shouldRecordAudit) } private func resetIPhoneAnalysisLane() { @@ -2213,7 +3186,10 @@ class StreamSessionViewModel: ObservableObject { } func stopSession() async { - geminiAssistant.stopSession() + await geminiAssistant.stopSession() + isAiGuideStarting = false + isStepValidationRunning = false + aiGuideStatusMessage = "" if isSopAuditRunning { await endAndShip(status: .userEnded) } else { @@ -2234,20 +3210,198 @@ class StreamSessionViewModel: ObservableObject { } func toggleGeminiAssistant() async { + guard canToggleAiGuide else { return } + if geminiAssistant.isGeminiActive { + await geminiAssistant.stopSession() + aiGuideStatusMessage = "AI guide paused." + sopAuditStatusMessage = aiGuideStatusMessage + return + } + + await startGeminiAssistant( + startingMessage: "Loading checklist guide...", + listeningMessage: "AI guide listening. Say \"I'm done\" when you want me to check this step." + ) + } + + @discardableResult + private func startGeminiAssistant( + startingMessage: String, + listeningMessage: String + ) async -> Bool { + guard isSopAuditRunning, !isAiGuideStarting, !hasActiveHelpEscalation else { return false } geminiAssistant.streamingMode = streamingMode geminiAssistant.configureWorkerAdminAPI( opsAPIClient, sessionID: activeExecutionSession?.id ) - if geminiAssistant.isGeminiActive { - geminiAssistant.stopSession() - return - } + isAiGuideStarting = true + aiGuideStatusMessage = startingMessage + sopAuditStatusMessage = aiGuideStatusMessage await geminiAssistant.startSession(systemInstruction: buildGeminiSessionInstruction()) + isAiGuideStarting = false if let errorMessage = geminiAssistant.errorMessage, !errorMessage.isEmpty { sopAuditStatusMessage = errorMessage + aiGuideStatusMessage = errorMessage + await postExecutionEvent( + type: "ai_guide_failed", + payload: [ + "error": errorMessage, + "capture_mode": selectedCaptureModeLabel.lowercased() + ] + ) + return false + } else if geminiAssistant.isGeminiActive { + aiGuideStatusMessage = listeningMessage + sopAuditStatusMessage = aiGuideStatusMessage + await postExecutionEvent( + type: "ai_guide_started", + payload: [ + "capture_mode": selectedCaptureModeLabel.lowercased() + ] + ) + return true + } + return false + } + + private func autoStartAiGuideWhenCaptureIsReady() async { + await ensureAiGuideStarted(reason: "capture_ready") + } + + private func waitForMediaReadyBeforeAiStart(reason: String) async -> Bool { + guard streamingMode == .iPhone else { return true } + guard let camera = iPhoneCameraManager else { + await recordAiGuideMediaReadyTimeout(reason: reason, cameraReady: false) + return false + } + + aiGuideStatusMessage = "Waiting for phone camera and mic..." + sopAuditStatusMessage = aiGuideStatusMessage + + let timeout: CFTimeInterval = 3.0 + let deadline = CACurrentMediaTime() + timeout + var cameraReady = false + + while CACurrentMediaTime() < deadline { + guard isSopAuditRunning, !hasActiveHelpEscalation else { return false } + let remaining = max(0, deadline - CACurrentMediaTime()) + cameraReady = await camera.waitUntilRunningAndAudioConfigured( + timeout: min(0.25, remaining) + ) + if cameraReady && hasReceivedFirstFrame { + try? await Task.sleep(nanoseconds: 150_000_000) + return true + } + try? await Task.sleep(nanoseconds: 100_000_000) + } + + await recordAiGuideMediaReadyTimeout(reason: reason, cameraReady: cameraReady) + return false + } + + private func recordAiGuideMediaReadyTimeout(reason: String, cameraReady: Bool) async { + let message = "Phone camera/mic still warming up. Tap Start AI to retry." + aiGuideStatusMessage = message + sopAuditStatusMessage = message + await WorkerTelemetry.shared.record( + "ai_guide_media_ready_timeout", + source: "gemini_live", + stage: "timeout", + sessionID: currentSopSessionId, + payload: [ + "reason": reason, + "camera_ready": cameraReady, + "has_first_frame": hasReceivedFirstFrame, + "capture_mode": captureModeEventValue(streamingMode) + ] + ) + await postExecutionEvent( + type: "ai_guide_media_ready_timeout", + payload: [ + "reason": reason, + "camera_ready": cameraReady, + "has_first_frame": hasReceivedFirstFrame, + "capture_mode": captureModeEventValue(streamingMode) + ] + ) + } + + @discardableResult + private func ensureAiGuideStarted( + reason: String, + maxAttempts: Int = 3 + ) async -> Bool { + guard isSopAuditRunning, !hasActiveHelpEscalation else { return false } + if geminiAssistant.isGeminiActive { + return true } + guard !isAiGuideStarting else { return false } + guard await waitForMediaReadyBeforeAiStart(reason: reason) else { return false } + + aiGuideStatusMessage = "Connecting AI voice..." + sopAuditStatusMessage = aiGuideStatusMessage + + for attempt in 1...maxAttempts { + guard isSopAuditRunning, !hasActiveHelpEscalation else { return false } + await WorkerTelemetry.shared.record( + "ai_guide_autostart_attempt", + source: "gemini_live", + stage: "attempt", + sessionID: currentSopSessionId, + payload: [ + "reason": reason, + "attempt": attempt, + "max_attempts": maxAttempts, + "has_first_frame": hasReceivedFirstFrame, + "capture_mode": captureModeEventValue(streamingMode) + ] + ) + await postExecutionEvent( + type: "ai_guide_autostart_attempt", + payload: [ + "reason": reason, + "attempt": attempt, + "has_first_frame": hasReceivedFirstFrame, + "capture_mode": captureModeEventValue(streamingMode) + ] + ) + + let started = await startGeminiAssistant( + startingMessage: attempt == 1 ? "Connecting AI voice..." : "Retrying AI voice...", + listeningMessage: "AI guide listening. Say \"I'm done\" or \"next step\" when you finish a step." + ) + if started { + await WorkerTelemetry.shared.record( + "ai_guide_autostart_ready", + source: "gemini_live", + stage: "ready", + sessionID: currentSopSessionId, + payload: [ + "reason": reason, + "attempt": attempt, + "has_first_frame": hasReceivedFirstFrame + ] + ) + return true + } + + guard attempt < maxAttempts else { break } + try? await Task.sleep(nanoseconds: UInt64(attempt) * 850_000_000) + } + + await WorkerTelemetry.shared.record( + "ai_guide_autostart_failed", + source: "gemini_live", + stage: "failed", + sessionID: currentSopSessionId, + payload: [ + "reason": reason, + "error": geminiAssistant.errorMessage ?? NSNull() + ] + ) + return false } func beginLiveCapture(for sop: SOPTemplate) async { @@ -2256,7 +3410,10 @@ class StreamSessionViewModel: ObservableObject { configureChecklist(for: sop) showShipSuccessToast = false shouldDismissCapture = false - sopAuditStatusMessage = "" + sopAuditStatusMessage = "Loading checklist guide..." + aiGuideStatusMessage = sopAuditStatusMessage + isAiGuideStarting = false + isStepValidationRunning = false helpStatusMessage = "" if !hasLoadedWorkerContext { @@ -2270,6 +3427,9 @@ class StreamSessionViewModel: ObservableObject { guard isStreaming else { return } await startSopAudit(for: sop) + if isSopAuditRunning && !geminiAssistant.isGeminiActive { + await ensureAiGuideStarted(reason: "begin_live_capture") + } } func selectCaptureMode(_ mode: StreamingMode) { @@ -2278,7 +3438,7 @@ class StreamSessionViewModel: ObservableObject { return } preferredCaptureMode = mode - if webrtcViewModel.isActive { + if webrtcViewModel.isActive && webrtcViewModel.isSupportMode { do { if let routeWarning = try configureWorkerAudioRoute(for: mode, reason: .viewer) { helpStatusMessage = routeWarning @@ -2289,6 +3449,13 @@ class StreamSessionViewModel: ObservableObject { } } + func selectCaptureModeFromUI(_ mode: StreamingMode) { + selectCaptureMode(mode) + Task { @MainActor [weak self] in + await self?.switchToPreferredCaptureModeIfNeeded() + } + } + func startCurrentAssignmentFromHome() { reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) guard let sop = currentAssignedSOP else { @@ -2317,18 +3484,18 @@ class StreamSessionViewModel: ObservableObject { await loadWorkerContextIfNeeded() } reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) + await startHomeCameraPreviewIfNeeded() guard !hasEnteredWorkerHome else { return } hasEnteredWorkerHome = true await resetDemoShiftForHomeIfNeeded(reloadAssignments: false) - autoPresentCurrentAssignmentIfNeeded() } func handleWorkerAppBecameActive() async { guard hasEnteredWorkerHome else { return } guard !isSopAuditRunning, activeCaptureSOP == nil else { return } await resetDemoShiftForHomeIfNeeded(reloadAssignments: true) - autoPresentCurrentAssignmentIfNeeded() + await startHomeCameraPreviewIfNeeded() } func restoreActiveCaptureIfNeeded() { @@ -2340,7 +3507,7 @@ class StreamSessionViewModel: ObservableObject { } func switchToPreferredCaptureModeIfNeeded() async { - guard let _ = selectedSOP else { return } + guard canSwitchCaptureMode else { return } if preferredCaptureMode == .glasses, !hasActiveDevice { sopAuditStatusMessage = "Meta camera not connected." @@ -2351,8 +3518,26 @@ class StreamSessionViewModel: ObservableObject { return } + let previousMode = streamingMode + isSwitchingCaptureMode = true + defer { isSwitchingCaptureMode = false } + await stopCurrentCameraTransportOnly() await startPreferredCamera() + + if isStreaming, streamingMode == preferredCaptureMode, previousMode != streamingMode { + geminiAssistant.streamingMode = streamingMode + if isSopAuditRunning { + await postExecutionEvent( + type: "capture_mode_switched", + payload: [ + "from": captureModeEventValue(previousMode), + "to": captureModeEventValue(streamingMode), + "label": selectedCaptureModeLabel + ] + ) + } + } } func loadWorkerContextIfNeeded() async { @@ -2360,15 +3545,6 @@ class StreamSessionViewModel: ObservableObject { await refreshWorkerContext() } - private func autoPresentCurrentAssignmentIfNeeded() { - guard activeCaptureSOP == nil, !isSopAuditRunning else { return } - guard let sop = currentAssignedSOP else { return } - let key = pendingTaskKey(for: sop) - guard !autoPresentedAssignmentKeys.contains(key) else { return } - autoPresentedAssignmentKeys.insert(key) - startCurrentAssignmentFromHome() - } - func refreshWorkerContext() async { guard GeminiConfig.isOpsConfigured else { setCriticalOperationsSyncIssue( @@ -2786,7 +3962,12 @@ class StreamSessionViewModel: ObservableObject { await workerAdminSync?.stop() workerAdminSync = WorkerAdminLiveSessionCoordinator( api: opsAPIClient, - telemetry: WorkerTelemetry.shared + telemetry: WorkerTelemetry.shared, + onHeartbeatResponse: { [weak self] response in + Task { @MainActor [weak self] in + await self?.handleWorkerLiveHeartbeatResponse(response) + } + } ) geminiLiveSpotter.configure(api: opsAPIClient) currentSopSessionId = sessionId @@ -2794,14 +3975,23 @@ class StreamSessionViewModel: ObservableObject { isSopAuditRunning = true sopAuditSecondsRemaining = sop.estimatedDuration sopAuditStatusMessage = "" + aiGuideStatusMessage = "" + isAiGuideStarting = false + isStepValidationRunning = false proofImagesByTargetID = [:] lastLivePreviewSyncAt = .distantPast + lastGlassesAnalysisFrameQueuedAt = 0 hasLoggedRoomCreatedForSession = false hasLoggedRoomJoinedForSession = false if streamingMode == .iPhone { sopVideoRecorder = nil + conversationAudioRecorder = ConversationAudioRecorder(sessionID: sessionId) } else { - sopVideoRecorder = SopVideoRecorder() + conversationAudioRecorder = nil + sopVideoRecorder = SopVideoRecorder(sessionID: sessionId) + if let fileURL = sopVideoRecorder?.outputURL { + rememberPendingRecording(sessionID: sessionId, fileURL: fileURL) + } } isDossierUploading = false dossierSpotterHitCount = 0 @@ -2812,36 +4002,37 @@ class StreamSessionViewModel: ObservableObject { lastProcessedTranscript = "" helpStatusMessage = "" hasActiveHelpEscalation = false + shouldResumeAiSupportAfterBackOffice = false packageClosureStatusMessage = "" clearOperationsSyncState() + if webrtcViewModel.isActive { + webrtcViewModel.stopSession() + } + WorkerLiveLogger.log( "session_start", sessionID: sessionId, - roomCode: webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode, + roomCode: nil, uploadState: "active" ) await workerAdminSync?.start( sessionID: sessionId, currentStepIndex: nextIncompleteStepIndex(), helpRequested: false, - roomCode: webrtcViewModel.roomCode + roomCode: nil ) - do { - if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { - helpStatusMessage = routeWarning - } - } catch { - helpStatusMessage = "Audio route error: \(error.localizedDescription)" - } - if streamingMode == .iPhone { iPhoneCameraManager?.startRecording(sessionID: sessionId) rememberPendingRecording(sessionID: sessionId, fileURL: expectedIPhoneRecordingURL(for: sessionId)) } - await ensureLiveRoomSession() + if isSopAuditRunning && !geminiAssistant.isGeminiActive { + await ensureAiGuideStarted(reason: "sop_started") + } + + await ensureObservationLiveRoomSession() // No countdown/auto-timeout for long SOP runs. sopCountdownTask?.cancel() @@ -2897,6 +4088,7 @@ class StreamSessionViewModel: ObservableObject { webrtcViewModel.stopSession() helpStatusMessage = "Supervisor room closed." hasActiveHelpEscalation = false + shouldResumeAiSupportAfterBackOffice = false Task { @MainActor in await workerAdminSync?.updateHelpRequested(false) await patchActiveExecutionSession( @@ -2904,6 +4096,10 @@ class StreamSessionViewModel: ObservableObject { helpRequested: false ) ) + await ensureObservationLiveRoomSession() + if isSopAuditRunning, !geminiAssistant.isGeminiActive { + await ensureAiGuideStarted(reason: "support_closed") + } } } @@ -2984,10 +4180,36 @@ class StreamSessionViewModel: ObservableObject { } sopCountdownTask = nil + if geminiAssistant.isGeminiActive { + await geminiAssistant.stopSession() + try? await Task.sleep(nanoseconds: 150_000_000) + } + let wasIPhoneRecording = streamingMode == .iPhone var recordedVideoURL: URL? if wasIPhoneRecording { - recordedVideoURL = await iPhoneCameraManager?.stopRecording() + let phoneVideoURL = await iPhoneCameraManager?.stopRecording() + let conversationAudioURL = await conversationAudioRecorder?.finishAudioFile() + if let phoneVideoURL, let conversationAudioURL { + let mixedURL = phoneVideoURL + .deletingLastPathComponent() + .appendingPathComponent(phoneVideoURL.deletingPathExtension().lastPathComponent + "_mixed") + .appendingPathExtension("mp4") + if let muxedURL = await ConversationAudioMuxer.mux( + videoURL: phoneVideoURL, + audioURL: conversationAudioURL, + outputURL: mixedURL + ) { + recordedVideoURL = muxedURL + try? FileManager.default.removeItem(at: phoneVideoURL) + try? FileManager.default.removeItem(at: conversationAudioURL) + } else { + NSLog("[SOPRecorder] iPhone mixed audio mux failed; returning phone recording") + recordedVideoURL = phoneVideoURL + } + } else { + recordedVideoURL = phoneVideoURL + } stopIPhoneSession() } else { await streamSession.stop() @@ -2998,6 +4220,7 @@ class StreamSessionViewModel: ObservableObject { let proofImages = proofImagesByTargetID sopVideoRecorder = nil + conversationAudioRecorder = nil proofImagesByTargetID = [:] let checklistPayload: [[String: Any]] = checklistItems.map { [ @@ -3156,11 +4379,12 @@ class StreamSessionViewModel: ObservableObject { if webrtcViewModel.isActive { webrtcViewModel.stopSession() } - geminiAssistant.stopSession() + await geminiAssistant.stopSession() await workerAdminSync?.stop() workerAdminSync = nil hasActiveHelpEscalation = false + shouldResumeAiSupportAfterBackOffice = false activeExecutionSession = nil currentSopSessionId = nil activeCaptureSOP = nil @@ -3176,6 +4400,7 @@ class StreamSessionViewModel: ObservableObject { isSpotterInferenceInFlight = false shouldDismissCapture = true showShipSuccessToast = true + await startHomeCameraPreviewIfNeeded() successToastTask?.cancel() successToastTask = Task { @MainActor [weak self] in try? await Task.sleep(nanoseconds: 2_000_000_000) @@ -3186,6 +4411,30 @@ class StreamSessionViewModel: ObservableObject { // MARK: - iPhone Camera Mode + private func startHomeCameraPreviewIfNeeded() async { + guard !isSopAuditRunning, activeCaptureSOP == nil else { return } + if isStreaming, streamingMode == preferredCaptureMode { + return + } + + if isStreaming { + await stopCurrentCameraTransportOnly() + } + + switch preferredCaptureMode { + case .glasses where hasActiveDevice: + await handleStartStreaming() + default: + let granted = await IPhoneCameraManager.requestPermission() + if granted { + preferredCaptureMode = .iPhone + startIPhoneSession() + } else if !showError { + showError("Camera permission denied. Please grant access in Settings.") + } + } + } + func handleStartIPhone() async { let granted = await IPhoneCameraManager.requestPermission() if granted { @@ -3202,6 +4451,7 @@ class StreamSessionViewModel: ObservableObject { hasReceivedFirstFrame = false resetIPhoneAnalysisLane() let camera = IPhoneCameraManager() + camera.analysisFrameInterval = webrtcViewModel.isUnderLiveVideoPressure ? 0.45 : 0.2 let realtimeVideoForwarder = webrtcViewModel.realtimeVideoForwarder camera.onFirstPreviewFrame = { [weak self] in Task { @MainActor [weak self] in @@ -3258,6 +4508,15 @@ class StreamSessionViewModel: ObservableObject { self?.updateHelpStatus(for: state) } } + + livePressureCancellable = webrtcViewModel.$isUnderLiveVideoPressure + .removeDuplicates() + .sink { [weak self] isUnderPressure in + guard let self else { return } + Task { @MainActor [weak self] in + self?.iPhoneCameraManager?.analysisFrameInterval = isUnderPressure ? 0.45 : 0.2 + } + } } private func stopIPhoneSession() { @@ -3354,6 +4613,7 @@ class StreamSessionViewModel: ObservableObject { ) } spotterEvidenceWindow.resetAll() + currentStepBecameActiveAt = Date() updateGuidancePolicy(.nextInstruction, reason: "A new SOP assignment started.") Task { @MainActor [weak self] in @@ -3469,6 +4729,11 @@ class StreamSessionViewModel: ObservableObject { func startHoldToTalk() { guard !isListeningForVoice else { return } + guard !geminiAssistant.isGeminiActive else { + sopAuditStatusMessage = "AI guide is already listening through the active audio route." + aiGuideStatusMessage = sopAuditStatusMessage + return + } guard let speechRecognizer, speechRecognizer.isAvailable else { sopAuditStatusMessage = "Speech recognizer unavailable" return @@ -3535,8 +4800,15 @@ class StreamSessionViewModel: ObservableObject { speechTask?.cancel() speechTask = nil isListeningForVoice = false + let lease = holdToTalkAudioLease + holdToTalkAudioLease = nil + Task { + if let lease { + await WorkerAudioRouteCoordinator.shared.release(lease: lease) + } + } - if webrtcViewModel.isActive { + if webrtcViewModel.isActive && webrtcViewModel.isSupportMode { do { if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { helpStatusMessage = routeWarning @@ -3544,31 +4816,133 @@ class StreamSessionViewModel: ObservableObject { } catch { NSLog("[Speech] Failed to restore talkback audio route: %@", error.localizedDescription) } - } else { - do { - try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) - } catch { - NSLog("[Speech] Failed to deactivate audio session: %@", error.localizedDescription) - } } } private func handleVoiceTranscript(_ transcript: String) { - guard transcript != lastProcessedTranscript else { return } - lastProcessedTranscript = transcript + let normalized = transcript + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + guard !normalized.isEmpty, normalized != lastProcessedTranscript else { return } + lastProcessedTranscript = normalized + + if isBackOfficeCallIntent(normalized) { + recordAiCommandDetected("call_back_office", transcript: normalized) + requestSupervisorHelp() + return + } + + if isStopAiGuideIntent(normalized) { + if geminiAssistant.isGeminiActive { + Task { @MainActor [weak self] in + await self?.geminiAssistant.stopSession() + } + } + aiGuideStatusMessage = "AI guide paused." + sopAuditStatusMessage = aiGuideStatusMessage + recordAiCommandDetected("pause_ai", transcript: normalized) + return + } + + if isVoiceStepAdvanceIntent(normalized) { + guard shouldProcessAiCommand("advance_step", transcript: normalized) else { return } + recordAiCommandDetected("advance_step", transcript: normalized) + completeActiveStepByVoice(transcript: normalized) + return + } - if transcript.contains("done") { - markFirstUncheckedAsVoice() + if isGuidedStepCheckIntent(normalized) { + guard shouldProcessAiCommand("check_step", transcript: normalized) else { return } + recordAiCommandDetected("check_step", transcript: normalized) + requestGuidedStepValidation(trigger: "voice_check") return } - guard let checkRange = transcript.range(of: "check ") else { return } - let spokenItem = transcript[checkRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines) + guard let checkRange = normalized.range(of: "check ") else { return } + let spokenItem = normalized[checkRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines) guard !spokenItem.isEmpty else { return } - if let matched = checklistItems.first(where: { $0.name.lowercased().contains(spokenItem) || spokenItem.contains($0.name.lowercased()) }) { - setChecklistItemChecked(itemID: matched.id, source: .voice) + if let active = activeSpotterRequestItems().first, + active.name.lowercased().contains(spokenItem) || spokenItem.contains(active.name.lowercased()) { + guard shouldProcessAiCommand("check_named_step", transcript: normalized) else { return } + recordAiCommandDetected("check_named_step", transcript: normalized) + requestGuidedStepValidation(trigger: "voice_check") + } + } + + private func isStopAiGuideIntent(_ transcript: String) -> Bool { + transcript.contains("stop ai") || + transcript.contains("pause ai") || + transcript.contains("stop guide") || + transcript.contains("pause guide") + } + + private func isVoiceStepAdvanceIntent(_ transcript: String) -> Bool { + transcript.contains("i'm done") || + transcript.contains("im done") || + transcript.contains("done with this") || + transcript.contains("done with the step") || + transcript.contains("next step") || + transcript.contains("ready for next") || + transcript.contains("move on") + } + + private func isGuidedStepCheckIntent(_ transcript: String) -> Bool { + transcript.contains("check this step") || + transcript.contains("check step") || + transcript.contains("check again") || + transcript.contains("validate this") || + transcript.contains("validate step") || + transcript.contains("what is missing") || + transcript.contains("what am i missing") || + transcript.contains("what's missing") || + transcript.contains("did i do it right") + } + + private func shouldProcessAiCommand(_ commandKey: String, transcript: String) -> Bool { + let now = Date() + if commandKey == lastAiCommandKey, + now.timeIntervalSince(lastAiCommandAt) < 3.0 { + return false + } + lastAiCommandKey = commandKey + lastAiCommandAt = now + return true + } + + private func recordAiCommandDetected(_ commandKey: String, transcript: String) { + Task { [weak self] in + guard let self else { return } + await WorkerTelemetry.shared.record( + "ai_command_detected", + source: "gemini_live", + stage: commandKey, + sessionID: self.currentSopSessionId, + payload: [ + "command": commandKey, + "transcript": transcript + ] + ) + await self.postExecutionEvent( + type: "ai_command_detected", + payload: [ + "command": commandKey, + "transcript": transcript + ] + ) + } + } + + private func isBackOfficeCallIntent(_ transcript: String) -> Bool { + let normalized = transcript.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + guard normalized.contains("back office") || normalized.contains("supervisor") || normalized.contains("support") else { + return false } + return normalized.contains("call") || + normalized.contains("dial") || + normalized.contains("ring") || + normalized.contains("request help") || + normalized.contains("need help") } private func markFirstUncheckedAsVoice() { @@ -3576,7 +4950,22 @@ class StreamSessionViewModel: ObservableObject { setChecklistItemChecked(itemID: firstUnchecked.id, source: .voice) } - private func setChecklistItemChecked(itemID: UUID, source: ChecklistCompletionSource) { + private func completeActiveStepByVoice(transcript: String) { + guard let firstUnchecked = checklistItems.first(where: { !$0.isChecked }) else { + sopAuditStatusMessage = "All checklist steps are complete." + aiGuideStatusMessage = sopAuditStatusMessage + return + } + setChecklistItemChecked(itemID: firstUnchecked.id, source: .voice, voiceTranscript: transcript) + sopAuditStatusMessage = "Step confirmed by voice. Moving to the next step." + aiGuideStatusMessage = sopAuditStatusMessage + } + + private func setChecklistItemChecked( + itemID: UUID, + source: ChecklistCompletionSource, + voiceTranscript: String? = nil + ) { guard let index = checklistItems.firstIndex(where: { $0.id == itemID }) else { return } guard !checklistItems[index].isChecked else { return } checklistItems[index].isChecked = true @@ -3584,6 +4973,7 @@ class StreamSessionViewModel: ObservableObject { let item = checklistItems[index] spotterEvidenceWindow.reset(stepID: item.itemID) + currentStepBecameActiveAt = Date() updateGuidancePolicy(.nextInstruction, reason: "Step completed by \(source.rawValue).") Task { await handleChecklistMutation( @@ -3591,6 +4981,32 @@ class StreamSessionViewModel: ObservableObject { stepIndex: index, eventType: "step_complete" ) + if source == .voice { + let nextIndex = nextIncompleteStepIndex() + await WorkerTelemetry.shared.record( + "voice_step_advanced", + source: "gemini_live", + stage: "advanced", + sessionID: currentSopSessionId, + payload: [ + "step_index": index, + "step_id": item.itemID, + "step_name": item.name, + "next_step_index": nextIndex, + "transcript": voiceTranscript ?? NSNull() + ] + ) + await postExecutionEvent( + type: "voice_step_advanced", + payload: [ + "step_index": index, + "step_id": item.itemID, + "step_name": item.name, + "next_step_index": nextIndex, + "transcript": voiceTranscript ?? NSNull() + ] + ) + } } if checklistItems.allSatisfy({ $0.isChecked }) { @@ -3610,6 +5026,7 @@ class StreamSessionViewModel: ObservableObject { checklistItems[index].completionSource = .vision dossierSpotterHitCount += 1 spotterEvidenceWindow.reset(stepID: itemID) + currentStepBecameActiveAt = Date() updateGuidancePolicy(.nextInstruction, reason: "Step completed after stable visual evidence.") updateDossierPipelineStatus("Live spotter hit #\(dossierSpotterHitCount)", kind: .active) @@ -3628,6 +5045,57 @@ class StreamSessionViewModel: ObservableObject { } } + private func reconcileChecklistAfterServerAdvance( + match: GeminiLiveSpotter.SpotterMatch, + evidence: [String: Any] + ) { + guard let serverStepIndex = match.advancedToStepIndex else { return } + + let completedCount = match.completedSop + ? checklistItems.count + : min(max(serverStepIndex, 0), checklistItems.count) + guard completedCount > 0 || match.completedSop else { return } + let matchedWasAlreadyChecked = + checklistItems.first(where: { $0.itemID == match.id })?.isChecked == true + + for index in checklistItems.indices { + guard index < completedCount else { continue } + if !checklistItems[index].isChecked { + checklistItems[index].isChecked = true + checklistItems[index].completionSource = .vision + } + spotterEvidenceWindow.reset(stepID: checklistItems[index].itemID) + } + + if !matchedWasAlreadyChecked { + dossierSpotterHitCount += 1 + } + + updateGuidancePolicy(.nextInstruction, reason: "Step advanced by server-confirmed visual evidence.") + updateDossierPipelineStatus("Live spotter server advance", kind: .active) + currentStepBecameActiveAt = Date() + + let nextIndex = nextIncompleteStepIndex() + Task { + await workerAdminSync?.updateCurrentStepIndex(nextIndex, sendImmediateHeartbeat: true) + await postExecutionEvent( + type: "phone_step_reconciled", + payload: [ + "step_id": match.id, + "server_step_index": serverStepIndex, + "local_next_step_index": nextIndex, + "completed_sop": match.completedSop, + "evidence": evidence + ] + ) + await syncGeminiSessionInstruction() + } + + if checklistItems.allSatisfy({ $0.isChecked }) || match.completedSop { + Task { await endAndShip(status: .allItemsChecked) } + } + } + private func captureProofImageIfNeeded(for itemID: String, from image: UIImage) { guard proofImagesByTargetID[itemID] == nil else { return } guard let jpegData = image.jpegData(compressionQuality: 0.9) else { return } @@ -3690,38 +5158,50 @@ class StreamSessionViewModel: ObservableObject { return json } - private func spotChecklistItemsIfThrottled(image: UIImage) { + func requestGuidedStepValidation(trigger: String = "tap") { guard isSopAuditRunning else { return } - guard !isSpotterInferenceInFlight else { return } - - let now = Date() - guard now.timeIntervalSince(lastSpotterInferenceTime) >= 0.6 else { return } // ~1.6 FPS - lastSpotterInferenceTime = now - isSpotterInferenceInFlight = true - + guard !isSpotterInferenceInFlight, !isStepValidationRunning else { return } + guard let image = currentVideoFrame else { + sopAuditStatusMessage = "Keep the current step visible so AI can check it." + aiGuideStatusMessage = sopAuditStatusMessage + return + } let pendingItems = activeSpotterRequestItems() guard !pendingItems.isEmpty else { - isSpotterInferenceInFlight = false + sopAuditStatusMessage = "All checklist steps are complete." + aiGuideStatusMessage = sopAuditStatusMessage return } + lastSpotterInferenceTime = Date() + isSpotterInferenceInFlight = true + isStepValidationRunning = true + sopAuditStatusMessage = "Checking the current step..." + aiGuideStatusMessage = sopAuditStatusMessage + updateDossierPipelineStatus("On-demand AI check running", kind: .active) + Task { [weak self] in guard let self else { return } let matches: [GeminiLiveSpotter.SpotterMatch] + var spotterErrorMessage: String? + var spotterConflict = false let requestStartedAt = CACurrentMediaTime() do { matches = try await self.geminiLiveSpotter.detectVisibleItemMatches( image: image, items: pendingItems, - sessionID: self.currentSopSessionId + sessionID: self.currentSopSessionId, + elapsedActiveMs: self.elapsedActiveMsForCurrentStep() ) } catch { matches = [] + spotterErrorMessage = error.localizedDescription + spotterConflict = self.isStaleSpotterConflict(error) await WorkerTelemetry.shared.record( - "gemini_spotter_failed", + spotterConflict ? "gemini_spotter_conflict" : "gemini_spotter_failed", source: "gemini_spotter", - stage: "failed", + stage: spotterConflict ? "conflict" : "failed", sessionID: self.currentSopSessionId, payload: [ "error": error.localizedDescription, @@ -3730,83 +5210,119 @@ class StreamSessionViewModel: ObservableObject { ) } let durationMs = (CACurrentMediaTime() - requestStartedAt) * 1000 - let stableMatches = await MainActor.run { - matches.compactMap { match -> (GeminiLiveSpotter.SpotterMatch, SpotterEvidenceDecision)? in - guard let target = pendingItems.first(where: { $0.id == match.id }) else { return nil } - let decision = self.spotterEvidenceWindow.record( - stepID: match.id, - matched: match.matched, - autoComplete: match.autoComplete, - confidence: match.confidence, - threshold: match.threshold - ) - if match.matched && !decision.shouldAutoComplete { - self.updateGuidancePolicy( - .confirm, - reason: "Visual evidence is accumulating for \(target.name)." - ) - } else if !match.matched && decision.sampleCount >= self.spotterEvidenceWindow.maxSamples && target.skipRisk == "high" { - self.updateGuidancePolicy( - .helpPrompt, - reason: "High-risk step evidence is still unclear for \(target.name)." - ) - } - return (match, decision) - } - } - let autoCompleteMatches = stableMatches.filter { $0.1.shouldAutoComplete } NSLog( - "[Spotter] Active-step review targets=%@ matched=%@ stableAutoComplete=%@ duration=%.1fms", + "[Spotter] Guided step check trigger=%@ targets=%@ matched=%@ autoComplete=%@ duration=%.1fms", + trigger, pendingItems.map(\.id).joined(separator: ","), matches.filter(\.matched).map(\.id).joined(separator: ","), - autoCompleteMatches.map { $0.0.id }.joined(separator: ","), + matches.filter(\.autoComplete).map(\.id).joined(separator: ","), durationMs ) - if let first = stableMatches.first { - let firstMatch = first.0 - let firstDecision = first.1 + if let firstMatch = matches.first { await WorkerTelemetry.shared.record( "gemini_spotter_result", source: "gemini_spotter", - stage: firstDecision.shouldAutoComplete ? "auto_complete" : firstMatch.matched ? "stabilizing" : "not_matched", + stage: firstMatch.autoComplete ? "auto_complete" : firstMatch.matched ? "matched" : "not_matched", sessionID: self.currentSopSessionId, durationMs: durationMs, metricValue: firstMatch.confidence, metricUnit: "confidence", payload: [ "step_id": firstMatch.id, + "trigger": trigger, + "on_demand": true, "matched": firstMatch.matched, "auto_complete": firstMatch.autoComplete, - "stable_auto_complete": firstDecision.shouldAutoComplete, - "window_samples": firstDecision.sampleCount, - "window_positive_samples": firstDecision.positiveCount, - "window_average_confidence": firstDecision.averagePositiveConfidence, - "threshold": firstDecision.threshold, + "threshold": firstMatch.threshold, "reason": firstMatch.reason ] ) } await MainActor.run { - autoCompleteMatches.forEach { match, decision in - self.captureProofImageIfNeeded(for: match.id, from: image) - self.setChecklistItemCheckedBySpotterID(match.id, evidence: [ - "ai_confidence": match.confidence, - "ai_reason": match.reason, - "evidence_timestamp": match.evidenceTimestamp, - "auto_complete": match.autoComplete, - "stable_auto_complete": decision.shouldAutoComplete, - "window_samples": decision.sampleCount, - "window_positive_samples": decision.positiveCount, - "window_average_confidence": decision.averagePositiveConfidence, - "threshold": decision.threshold - ]) + if let spotterErrorMessage { + if spotterConflict { + self.sopAuditStatusMessage = "Checklist moved on in the backend. Refreshing the active step..." + self.aiGuideStatusMessage = self.sopAuditStatusMessage + self.updateGuidancePolicy(.helpPrompt, reason: self.sopAuditStatusMessage) + self.updateDossierPipelineStatus("AI check refreshed active step", kind: .info) + Task { await self.recoverFromStaleSpotterConflict(message: spotterErrorMessage) } + self.isStepValidationRunning = false + self.isSpotterInferenceInFlight = false + return + } + self.sopAuditStatusMessage = "AI check could not confirm the active step: \(spotterErrorMessage)" + self.aiGuideStatusMessage = self.sopAuditStatusMessage + self.updateGuidancePolicy(.helpPrompt, reason: self.sopAuditStatusMessage) + self.updateDossierPipelineStatus("AI check conflict", kind: .info) + Task { await self.syncGeminiSessionInstruction() } + self.isStepValidationRunning = false + self.isSpotterInferenceInFlight = false + return + } + + if let match = matches.first { + if match.autoComplete, match.advancedToStepIndex != nil { + self.captureProofImageIfNeeded(for: match.id, from: image) + self.reconcileChecklistAfterServerAdvance(match: match, evidence: [ + "guided_trigger": trigger, + "on_demand": true, + "ai_confidence": match.confidence, + "ai_reason": match.reason, + "evidence_timestamp": match.evidenceTimestamp, + "auto_complete": match.autoComplete, + "advanced_to_step_index": match.advancedToStepIndex ?? NSNull(), + "completed_sop": match.completedSop, + "threshold": match.threshold + ]) + self.sopAuditStatusMessage = "Step checked. Moving to the next step." + self.aiGuideStatusMessage = self.sopAuditStatusMessage + } else { + let reason = match.reason.trimmingCharacters(in: .whitespacesAndNewlines) + let message = reason.isEmpty + ? (match.autoComplete + ? "The server has not advanced this step yet. Try again in a moment." + : "I still need clearer evidence before moving to the next step.") + : reason + self.sopAuditStatusMessage = "Step needs more evidence: \(message)" + self.aiGuideStatusMessage = self.sopAuditStatusMessage + self.updateGuidancePolicy(.helpPrompt, reason: self.sopAuditStatusMessage) + self.updateDossierPipelineStatus("AI check incomplete", kind: .info) + } + } else { + self.sopAuditStatusMessage = "AI check could not read the current step. Keep the work area visible and try again." + self.aiGuideStatusMessage = self.sopAuditStatusMessage + self.updateGuidancePolicy(.helpPrompt, reason: self.sopAuditStatusMessage) + self.updateDossierPipelineStatus("AI check unavailable", kind: .info) } + self.isStepValidationRunning = false self.isSpotterInferenceInFlight = false } } } + private func elapsedActiveMsForCurrentStep() -> Int? { + max(0, Int(Date().timeIntervalSince(currentStepBecameActiveAt) * 1000)) + } + + private func isStaleSpotterConflict(_ error: Error) -> Bool { + if case AdminIngestError.server(let statusCode, _, _) = error { + return statusCode == 409 + } + return error.localizedDescription.contains("HTTP 409") + } + + private func recoverFromStaleSpotterConflict(message: String) async { + await postExecutionEvent( + type: "phone_step_validation_conflict", + payload: [ + "message": message, + "local_next_step_index": nextIncompleteStepIndex() + ] + ) + await syncGeminiSessionInstruction() + } + private func startPreferredCamera() async { reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: false) switch preferredCaptureMode { @@ -3822,19 +5338,11 @@ class StreamSessionViewModel: ObservableObject { } private func reconcileCaptureModeWithDeviceAvailability(allowTransportSwitch: Bool) { - let nextMode: StreamingMode = hasActiveDevice ? .glasses : .iPhone - guard preferredCaptureMode != nextMode else { return } - - preferredCaptureMode = nextMode - - if hasActiveDevice { - if isSopAuditRunning && streamingMode == .iPhone { - sopAuditStatusMessage = "Meta camera connected. The next assignment will use glasses." - } else { - sopAuditStatusMessage = "Meta camera ready." - } - } else if streamingMode == .glasses { + if !hasActiveDevice, preferredCaptureMode == .glasses { + preferredCaptureMode = .iPhone sopAuditStatusMessage = "Meta camera disconnected." + } else if hasActiveDevice { + sopAuditStatusMessage = "Meta camera ready. Use the camera selector when you want glasses." } guard allowTransportSwitch, isStreaming, !isSopAuditRunning else { return } @@ -3843,6 +5351,13 @@ class StreamSessionViewModel: ObservableObject { } } + private func captureModeEventValue(_ mode: StreamingMode) -> String { + switch mode { + case .glasses: return "glasses" + case .iPhone: return "iphone" + } + } + private func stopCurrentCameraTransportOnly() async { switch streamingMode { case .iPhone: @@ -3896,57 +5411,51 @@ class StreamSessionViewModel: ObservableObject { } private func hasBluetoothTalkbackRoute(_ route: AVAudioSessionRouteDescription) -> Bool { + route.inputs.contains { + $0.portType == .bluetoothHFP || + $0.portType == .bluetoothLE + } || route.outputs.contains { - $0.portType == .bluetoothA2DP - || $0.portType == .bluetoothHFP + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE } } + private func preferredBluetoothHFPInput(_ session: AVAudioSession) -> AVAudioSessionPortDescription? { + session.availableInputs?.first { + $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE + } + } + @discardableResult private func configureWorkerAudioRoute( for mode: StreamingMode, reason: WorkerAudioRouteReason ) throws -> String? { - let session = AVAudioSession.sharedInstance() - var options: AVAudioSession.CategoryOptions = [.allowBluetoothHFP, .allowBluetoothA2DP] - let audioMode: AVAudioSession.Mode - - switch mode { - case .iPhone: - audioMode = .voiceChat - options.formUnion([.defaultToSpeaker, .duckOthers]) - case .glasses: - audioMode = .videoChat - } - - try session.setCategory(.playAndRecord, mode: audioMode, options: options) - try session.setPreferredIOBufferDuration(0.02) - try session.setActive(true, options: .notifyOthersOnDeactivation) - - switch mode { - case .iPhone: - try session.overrideOutputAudioPort(.speaker) - logWorkerAudioRoute(session: session, mode: mode, reason: reason) - return nil - case .glasses: - if hasBluetoothTalkbackRoute(session.currentRoute) { - try session.overrideOutputAudioPort(.none) - logWorkerAudioRoute(session: session, mode: mode, reason: reason) - return nil - } - try session.overrideOutputAudioPort(.speaker) - switch reason { - case .viewer: - let note = "Meta audio route unavailable. Using phone speaker until glasses/Bluetooth audio connects." - logWorkerAudioRoute(session: session, mode: mode, reason: reason, note: note) - return note - case .holdToTalk: - let note = "Meta mic route unavailable. Hold-to-talk is using the phone until glasses/Bluetooth audio connects." - logWorkerAudioRoute(session: session, mode: mode, reason: reason, note: note) - return note - } + if reason == .viewer, webrtcViewModel.isActive, webrtcViewModel.isSupportMode { + return webrtcViewModel.refreshSupportAudioRoute(captureMode: mode) + } + + let owner: WorkerAudioRouteOwner = + reason == .holdToTalk + ? .holdToTalk + : .viewer + let snapshot = try WorkerAudioRouteCoordinator.shared.acquire( + owner: owner, + mode: mode, + reason: reason == .holdToTalk ? "hold_to_talk" : "live_support", + forceSpeaker: SettingsManager.shared.speakerOutputEnabled, + preferredIOBufferDuration: 0.02 + ) + switch owner { + case .holdToTalk: + holdToTalkAudioLease = snapshot.lease + case .viewer: + viewerAudioRouteLease = snapshot.lease + case .aiGuide, .backOfficeWebRTC: + break } + return snapshot.fallbackMessage } private func liveRoomStatusMessage(localOnly: Bool, helpRequested: Bool, roomCode: String? = nil) -> String { @@ -3959,7 +5468,7 @@ class StreamSessionViewModel: ObservableObject { if let roomCode, !roomCode.isEmpty { return helpRequested ? "Supervisor request sent. Room \(roomCode) is ready for manager join." - : "Live room active: \(roomCode)" + : "Admin video observation active: \(roomCode)" } return helpRequested @@ -4113,8 +5622,7 @@ class StreamSessionViewModel: ObservableObject { } let orderedSteps = sop.steps.sorted { $0.order < $1.order } - let baseInstruction = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) - let resolvedBaseInstruction = baseInstruction.isEmpty ? GeminiConfig.defaultSystemInstruction : baseInstruction + let resolvedBaseInstruction = GeminiConfig.defaultSystemInstruction guard !orderedSteps.isEmpty else { geminiInstructionSyncStatus = "Gemini sync: \(sop.name) · no structured steps" @@ -4208,12 +5716,32 @@ class StreamSessionViewModel: ObservableObject { guard let sessionID = currentSopSessionId else { return } let now = Date() - let uploadInterval = hasActiveHelpEscalation ? 1.0 : 2.0 + let underLiveVideoPressure = webrtcViewModel.isUnderLiveVideoPressure + let uploadInterval: TimeInterval = underLiveVideoPressure + ? (hasActiveHelpEscalation ? 1.5 : 2.0) + : (hasActiveHelpEscalation ? 0.75 : 1.0) guard now.timeIntervalSince(lastLivePreviewSyncAt) >= uploadInterval else { return } lastLivePreviewSyncAt = now - let compressionQuality = hasActiveHelpEscalation ? 0.55 : 0.65 - guard let jpegData = image.jpegData(compressionQuality: compressionQuality) else { return } + let compressionQuality = hasActiveHelpEscalation ? 0.5 : 0.45 + let encodeStartedAt = CACurrentMediaTime() + guard let jpegData = await livePreviewFrameEncoder.encode( + image: image, + maxDimension: 640, + compressionQuality: compressionQuality + ) else { return } + await WorkerTelemetry.shared.record( + "live_preview_encode", + source: "ios_app", + stage: underLiveVideoPressure ? "pressure" : "normal", + sessionID: sessionID, + durationMs: (CACurrentMediaTime() - encodeStartedAt) * 1000, + payload: [ + "bytes": jpegData.count, + "under_live_video_pressure": underLiveVideoPressure, + "upload_interval_seconds": uploadInterval + ] + ) if streamingMode == .glasses, let fileURL = sopVideoRecorder?.outputURL { rememberPendingRecording(sessionID: sessionID, fileURL: fileURL) @@ -4222,6 +5750,52 @@ class StreamSessionViewModel: ObservableObject { await workerAdminSync?.enqueueFrameUpload(data: jpegData) } + private func handleWorkerLiveHeartbeatResponse(_ response: WorkerLiveHeartbeatResponse) async { + guard response.sessionID == currentSopSessionId else { return } + + let humanConnected = + response.shouldOpenLiveRoom || + (response.supportMode == "back_office" && response.humanSupportStatus == "connected") + let humanEnded = + response.supportMode == "ai" && response.humanSupportStatus == "ended" + let humanRinging = + response.supportMode == "handoff_requested" || response.humanSupportStatus == "ringing" + + if humanConnected { + hasActiveHelpEscalation = true + if !webrtcViewModel.isActive { + helpStatusMessage = "Back office answered. Opening live video and audio..." + await ensureLiveRoomSession() + } else if !webrtcViewModel.roomCode.isEmpty { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + } + return + } + + if humanEnded { + if webrtcViewModel.isActive { + webrtcViewModel.stopSession() + } + hasActiveHelpEscalation = false + await workerAdminSync?.updateHelpRequested(false, sendImmediateHeartbeat: false) + helpStatusMessage = "Back office ended. AI support is available again." + await ensureObservationLiveRoomSession() + + if shouldResumeAiSupportAfterBackOffice, isSopAuditRunning, !geminiAssistant.isGeminiActive { + shouldResumeAiSupportAfterBackOffice = false + await ensureAiGuideStarted(reason: "support_ended") + } else { + shouldResumeAiSupportAfterBackOffice = false + } + return + } + + if humanRinging { + hasActiveHelpEscalation = true + helpStatusMessage = "Calling back office. Live video and audio will open after they answer." + } + } + private func requestSupervisorHelpFlow() async { guard canRequestHelp else { helpStatusMessage = "Start an SOP before requesting live support." @@ -4231,12 +5805,18 @@ class StreamSessionViewModel: ObservableObject { isRequestingHelp = true defer { isRequestingHelp = false } - await ensureLiveRoomSession() - let notes = helpRequestNotes.trimmingCharacters(in: .whitespacesAndNewlines) + shouldResumeAiSupportAfterBackOffice = geminiAssistant.isGeminiActive + if geminiAssistant.isGeminiActive { + await geminiAssistant.stopSession() + aiGuideStatusMessage = "AI guide paused while back office support is requested." + sopAuditStatusMessage = aiGuideStatusMessage + } guard let sessionID = activeExecutionSession?.id else { - helpStatusMessage = liveRoomStatusMessage(localOnly: true, helpRequested: true, roomCode: webrtcViewModel.roomCode) + hasActiveHelpEscalation = true + await workerAdminSync?.updateHelpRequested(true) + helpStatusMessage = "Calling back office locally. Backend session sync is required before live media can open." return } @@ -4252,18 +5832,24 @@ class StreamSessionViewModel: ObservableObject { type: "help_requested", payload: [ "notes": notes, - "room_code": webrtcViewModel.roomCode + "room_code": NSNull() ] ) await patchActiveExecutionSession( ExecutionSessionPatch( - helpRequested: true, - webrtcRoomCode: webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode + helpRequested: true ) ) - helpStatusMessage = liveRoomStatusMessage(localOnly: false, helpRequested: true, roomCode: webrtcViewModel.roomCode) + await workerAdminSync?.updateHelpRequested(true) + helpStatusMessage = "Calling back office. Live video and audio will open after they answer." } catch { - helpStatusMessage = "Live room is open locally, but the backend help request failed: \(error.localizedDescription)" + hasActiveHelpEscalation = false + let shouldResumeAI = shouldResumeAiSupportAfterBackOffice + shouldResumeAiSupportAfterBackOffice = false + if shouldResumeAI, isSopAuditRunning, !geminiAssistant.isGeminiActive { + await geminiAssistant.startSession(systemInstruction: buildGeminiSessionInstruction()) + } + helpStatusMessage = "Back office call request failed: \(error.localizedDescription)" } } @@ -4329,7 +5915,7 @@ class StreamSessionViewModel: ObservableObject { } helpStatusMessage = localOnly ? "Local live room connected. Admin join stays unavailable until backend sync succeeds." - : "Live viewer connected." + : (webrtcViewModel.isSupportMode ? "Back office audio connected." : "Admin video observer connected.") case .waitingForPeer: if !webrtcViewModel.roomCode.isEmpty { helpStatusMessage = liveRoomStatusMessage( @@ -4341,7 +5927,7 @@ class StreamSessionViewModel: ObservableObject { case .connecting: helpStatusMessage = localOnly ? "Opening local live room..." - : "Opening live execution room..." + : (webrtcViewModel.isSupportMode ? "Opening back office audio room..." : "Opening admin video observation...") case .backgrounded: helpStatusMessage = "Live room paused in background. Returning will reconnect." case .error(let message): @@ -4354,17 +5940,39 @@ class StreamSessionViewModel: ObservableObject { } private func ensureLiveRoomSession() async { - do { - if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { - helpStatusMessage = routeWarning + // Heartbeats can arrive while Gemini is still yielding the audio route. + // Only one support-room handoff may perform that awaited stop/start path. + if webrtcViewModel.isActive && webrtcViewModel.isSupportMode { + switch webrtcViewModel.connectionState { + case .connected, .waitingForPeer, .connecting: + if !webrtcViewModel.roomCode.isEmpty { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + } + return + case .backgrounded, .error, .disconnected: + break } - } catch { - helpStatusMessage = "Audio route error: \(error.localizedDescription)" + } + + guard !isLiveRoomHandoffInProgress else { + if !webrtcViewModel.roomCode.isEmpty { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + } + return + } + isLiveRoomHandoffInProgress = true + defer { isLiveRoomHandoffInProgress = false } + + if geminiAssistant.isGeminiActive { + shouldResumeAiSupportAfterBackOffice = true + aiGuideStatusMessage = "AI guide paused while back office support connects." + sopAuditStatusMessage = aiGuideStatusMessage + await geminiAssistant.stopSession() } let hasRoomCode = !webrtcViewModel.roomCode.isEmpty - if webrtcViewModel.isActive { + if webrtcViewModel.isActive && webrtcViewModel.isSupportMode { switch webrtcViewModel.connectionState { case .connected, .waitingForPeer: if hasRoomCode { @@ -4382,9 +5990,12 @@ class StreamSessionViewModel: ObservableObject { helpStatusMessage = "Restarting live room for this SOP..." webrtcViewModel.stopSession() + } else if webrtcViewModel.isActive { + helpStatusMessage = "Switching video observation into back office audio..." + webrtcViewModel.stopSession() } - await webrtcViewModel.startSession(captureMode: streamingMode) + await webrtcViewModel.startSession(captureMode: streamingMode, roomMode: .support) if let roomCode = await waitForRoomCode() { await syncLiveRoomState(roomCode: roomCode) } else if activeExecutionSession == nil { @@ -4402,6 +6013,49 @@ class StreamSessionViewModel: ObservableObject { } } + private func ensureObservationLiveRoomSession() async { + guard isSopAuditRunning, !hasActiveHelpEscalation else { return } + guard WebRTCConfig.isConfigured else { + setOperationsSyncWarning( + phase: "live_room", + message: "Video observation room is unavailable because signal settings are not configured." + ) + return + } + + if webrtcViewModel.isActive { + if !webrtcViewModel.isSupportMode { + if !webrtcViewModel.roomCode.isEmpty { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + } + return + } + webrtcViewModel.stopSession() + } + + helpStatusMessage = "Opening admin video observation..." + await webrtcViewModel.startSession(captureMode: streamingMode, roomMode: .observation) + if let roomCode = await waitForRoomCode(timeoutNanoseconds: 4_000_000_000) { + await syncLiveRoomState(roomCode: roomCode) + await WorkerTelemetry.shared.record( + "webrtc_observation_room_ready", + source: "webrtc", + stage: "observation", + sessionID: currentSopSessionId, + payload: [ + "room_code_present": true, + "capture_mode": captureModeEventValue(streamingMode) + ] + ) + } else { + helpStatusMessage = "Admin video observation is still syncing." + setOperationsSyncWarning( + phase: "live_room", + message: "Observation room did not publish a room code yet. Admin can still use frame fallback while signaling catches up." + ) + } + } + private func postExecutionEvent(type: String, payload: [String: Any]) async { guard let sessionID = activeExecutionSession?.id else { return } do { @@ -4528,3 +6182,21 @@ class StreamSessionViewModel: ObservableObject { dossierPipelineStatusTimestamp = Self.pipelineTimestampFormatter.string(from: Date()) } } + +private extension UIImage { + func resizedForLivePreview(maxDimension: CGFloat) -> UIImage { + let width = size.width + let height = size.height + guard width > 0, height > 0, maxDimension > 0 else { return self } + + let longest = max(width, height) + guard longest > maxDimension else { return self } + + let scale = maxDimension / longest + let targetSize = CGSize(width: width * scale, height: height * scale) + let renderer = UIGraphicsImageRenderer(size: targetSize) + return renderer.image { _ in + self.draw(in: CGRect(origin: .zero, size: targetSize)) + } + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/CaptureView.swift b/samples/CameraAccess/CameraAccess/Views/CaptureView.swift index a944870e..d146520f 100644 --- a/samples/CameraAccess/CameraAccess/Views/CaptureView.swift +++ b/samples/CameraAccess/CameraAccess/Views/CaptureView.swift @@ -5,11 +5,18 @@ import UIKit struct CaptureView: View { @Environment(\.dismiss) private var dismiss @ObservedObject var viewModel: StreamSessionViewModel + @ObservedObject private var geminiAssistant: GeminiSessionViewModel let sop: SOPTemplate @State private var highlightedChecklistItemID: UUID? @State private var isFinishingSOP: Bool = false @State private var finishButtonPulse: Bool = false + init(viewModel: StreamSessionViewModel, sop: SOPTemplate) { + self.viewModel = viewModel + self.sop = sop + self._geminiAssistant = ObservedObject(wrappedValue: viewModel.geminiAssistant) + } + var body: some View { GeometryReader { geometry in ZStack { @@ -60,27 +67,21 @@ struct CaptureView: View { } } - VStack { - checklistOverlay(maxHeight: geometry.size.height * 0.2) - .padding(.top, 12) - .padding(.horizontal, 12) - - if !viewModel.dossierPipelineStatusMessage.isEmpty || viewModel.isDossierUploading { - pipelineStatusBadge - .padding(.top, 8) - .padding(.horizontal, 12) - } - - if viewModel.canRequestHelp || viewModel.webrtcViewModel.isActive || !viewModel.helpStatusMessage.isEmpty { - supportStatusBar - .padding(.top, 8) - .padding(.horizontal, 12) - } + VStack(spacing: 10) { + topControls + .padding(.top, 12) + .padding(.horizontal, 12) Spacer() - bottomBar - .padding(12) + VStack(spacing: 10) { + if shouldShowAiConversationPanel { + aiConversationPanel + } + activeStepOverlay + bottomBar + } + .padding(12) } } } @@ -103,114 +104,359 @@ struct CaptureView: View { } } - private func checklistOverlay(maxHeight: CGFloat) -> some View { + private var topControls: some View { + ViewThatFits(in: .horizontal) { + HStack(alignment: .top, spacing: 10) { + topStatusCluster + Spacer(minLength: 10) + callBackOfficeButton + } + + topControlsCompact + } + } + + private var topStatusCluster: some View { VStack(alignment: .leading, spacing: 8) { - HStack { - Text("CHECKLIST") - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.blueGrey) - Spacer() - Text(viewModel.progressText) + captureStatusBadge + cameraSelector + if !viewModel.dossierPipelineStatusMessage.isEmpty || viewModel.isDossierUploading { + pipelineStatusBadge + } + if viewModel.webrtcViewModel.isActive || + !viewModel.helpStatusMessage.isEmpty || + !viewModel.geminiInstructionSyncStatus.isEmpty || + !viewModel.aiGuideStatusMessage.isEmpty { + supportStatusBar + } + } + .frame(maxWidth: 270, alignment: .leading) + } + + private var topControlsCompact: some View { + VStack(alignment: .leading, spacing: 8) { + HStack(alignment: .top, spacing: 10) { + captureStatusBadge + Spacer(minLength: 8) + callBackOfficeButton + } + cameraSelector + if !viewModel.dossierPipelineStatusMessage.isEmpty || viewModel.isDossierUploading { + pipelineStatusBadge + } + if viewModel.webrtcViewModel.isActive || + !viewModel.helpStatusMessage.isEmpty || + !viewModel.geminiInstructionSyncStatus.isEmpty || + !viewModel.aiGuideStatusMessage.isEmpty { + supportStatusBar + } + } + } + + private var bottomBar: some View { + ViewThatFits(in: .horizontal) { + HStack(spacing: 12) { + aiGuideButton + checkStepButton + finishSOPButton + } + + VStack(spacing: 10) { + HStack(spacing: 10) { + aiGuideButton + checkStepButton + } + finishSOPButton + } + } + } + + private var finishSOPButton: some View { + Button { + animateFinishSOPPress() + viewModel.userTappedEndAndShip() + } label: { + HStack(spacing: 8) { + if isFinishingSOP { + Image(systemName: "checkmark.circle.fill") + .transition(.scale.combined(with: .opacity)) + } + Text(isFinishingSOP ? "FINISHING..." : "FINISH SOP") + } + } + .brutalistDangerButton() + .scaleEffect(finishButtonPulse ? 1.04 : 1.0) + .opacity(isFinishingSOP ? 0.92 : 1.0) + .animation(.spring(response: 0.25, dampingFraction: 0.65), value: finishButtonPulse) + .animation(.easeInOut(duration: 0.2), value: isFinishingSOP) + .disabled(isFinishingSOP) + } + + private var aiGuideButton: some View { + let active = geminiAssistant.isGeminiActive + let starting = viewModel.isAiGuideStarting + return Button { + Task { + await viewModel.toggleGeminiAssistant() + } + } label: { + HStack(spacing: 8) { + if starting { + ProgressView() + .progressViewStyle(.circular) + .tint(DesignSystem.colors.deepNavy) + .scaleEffect(0.75) + } else { + Image(systemName: active ? "waveform.circle.fill" : "sparkles") + .font(.system(size: 15, weight: .bold)) + } + Text(viewModel.aiGuideButtonTitle) + .lineLimit(1) + .minimumScaleFactor(0.72) + } + .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) + .foregroundColor(active || starting ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) + .padding(.horizontal, 12) + .frame(maxWidth: .infinity) + .frame(height: 48) + .background(active || starting ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.white.opacity(0.9), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .disabled(!viewModel.canToggleAiGuide) + .opacity(viewModel.canToggleAiGuide ? 1 : 0.66) + } + + private var checkStepButton: some View { + Button { + viewModel.requestGuidedStepValidation(trigger: "tap") + } label: { + HStack(spacing: 6) { + if viewModel.isStepValidationRunning { + ProgressView() + .progressViewStyle(.circular) + .tint(DesignSystem.colors.white) + .scaleEffect(0.7) + } else { + Image(systemName: "checkmark.seal") + .font(.system(size: 14, weight: .bold)) + } + Text(viewModel.isStepValidationRunning ? "CHECKING" : "CHECK STEP") + .lineLimit(1) + .minimumScaleFactor(0.68) + } + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 10) + .frame(width: 118, height: 48) + .background(DesignSystem.colors.deepNavy.opacity(0.9)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.82), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .disabled(!viewModel.canRequestStepValidation) + .opacity(viewModel.canRequestStepValidation ? 1 : 0.62) + } + + private var cameraSelector: some View { + HStack(spacing: 8) { + cameraModeButton( + title: "iPhone", + systemName: "iphone", + mode: .iPhone, + enabled: true + ) + cameraModeButton( + title: viewModel.hasActiveDevice ? "Glasses" : "No Glasses", + systemName: "eyeglasses", + mode: .glasses, + enabled: viewModel.hasActiveDevice + ) + } + .frame(maxWidth: 230) + } + + private func cameraModeButton( + title: String, + systemName: String, + mode: StreamingMode, + enabled: Bool + ) -> some View { + let selected = viewModel.preferredCaptureMode == mode + return Button { + viewModel.selectCaptureModeFromUI(mode) + } label: { + HStack(spacing: 5) { + Image(systemName: systemName) + .font(.system(size: 10, weight: .bold)) + Text(title) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .lineLimit(1) + .minimumScaleFactor(0.72) + } + .foregroundColor(selected ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) + .padding(.horizontal, 8) + .frame(maxWidth: .infinity) + .frame(height: 32) + .background(selected ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy.opacity(0.72)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(selected ? DesignSystem.colors.white.opacity(0.9) : DesignSystem.colors.vibrantTeal.opacity(0.65), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .disabled(!enabled || !viewModel.canSwitchCaptureMode) + .opacity((enabled && viewModel.canSwitchCaptureMode) ? 1 : 0.5) + } + + private var captureStatusBadge: some View { + HStack(spacing: 8) { + Image(systemName: "record.circle") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + Text("EXECUTING") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + Text(viewModel.progressText) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(DesignSystem.colors.deepNavy.opacity(0.72)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.55), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + private var callBackOfficeButton: some View { + Button { + viewModel.requestSupervisorHelp() + } label: { + HStack(spacing: 8) { + Image(systemName: "phone.fill") + .font(.system(size: 15, weight: .bold)) + Text(viewModel.backOfficeCallButtonTitle) .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.white) + .lineLimit(1) + .minimumScaleFactor(0.72) } + .foregroundColor(DesignSystem.colors.deepNavy) + .padding(.horizontal, 12) + .frame(height: 44) + .background(DesignSystem.colors.vibrantTeal) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.white.opacity(0.92), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .shadow(color: DesignSystem.colors.vibrantTeal.opacity(0.32), radius: 16, x: 0, y: 6) + } + .buttonStyle(.plain) + .disabled(!viewModel.canTapBackOfficeCall) + .opacity(viewModel.canTapBackOfficeCall ? 1 : 0.72) + } - ScrollView(showsIndicators: true) { - VStack(alignment: .leading, spacing: 6) { - ForEach(viewModel.checklistItems) { item in - let isHighlighted = highlightedChecklistItemID == item.id - - Button { - withAnimation(.spring(response: 0.28, dampingFraction: 0.76)) { - viewModel.toggleChecklistItem(itemID: item.id, viaVoice: false) - } - animateChecklistSelection(item.id) - } label: { - VStack(alignment: .leading, spacing: 4) { - HStack(spacing: 8) { - Image(systemName: item.isChecked ? "checkmark" : "minus") - .font(.system(size: 12, weight: .bold, design: .monospaced)) - .foregroundColor(item.isChecked ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.blueGrey) - .symbolEffect(.bounce, value: item.isChecked) - - Text(item.name) - .font(DesignSystem.fonts.mono(size: 14, weight: .semibold)) - .foregroundColor(item.isChecked ? DesignSystem.colors.white : DesignSystem.colors.blueGrey) - .strikethrough(item.isChecked, color: DesignSystem.colors.vibrantTeal) - .multilineTextAlignment(.leading) - - Spacer(minLength: 0) - } - - HStack(spacing: 8) { - if !item.duration.isEmpty { - Text(item.duration.uppercased()) - .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) - .foregroundColor(DesignSystem.colors.blueGrey) - } - - Text(item.validation.uppercased()) - .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) - .foregroundColor(DesignSystem.colors.vibrantTeal) - - if item.critical { - Text("CRITICAL") - .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) - .foregroundColor(.red) - } - - if !item.allowManualComplete && !item.isChecked { - Text("VISION") - .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) - .foregroundColor(.orange) - } - } - } - .padding(.vertical, 4) - .padding(.horizontal, 6) - .background(DesignSystem.colors.vibrantTeal.opacity(isHighlighted ? 0.16 : 0.0)) - .overlay( - RoundedRectangle(cornerRadius: 6) - .stroke(DesignSystem.colors.vibrantTeal.opacity(isHighlighted ? 0.85 : 0.0), lineWidth: 1) - ) - .clipShape(RoundedRectangle(cornerRadius: 6)) - .scaleEffect(isHighlighted ? 1.015 : 1.0) - .animation(.easeInOut(duration: 0.22), value: isHighlighted) + private var activeStepOverlay: some View { + let step = currentStep + let isHighlighted = step.map { highlightedChecklistItemID == $0.id } ?? false + + return VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .center, spacing: 8) { + Text("STEP \(currentStepNumber)/\(max(viewModel.checklistItems.count, 1))") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + + Spacer(minLength: 8) + + if let step, step.allowManualComplete || step.isChecked { + Button { + withAnimation(.spring(response: 0.28, dampingFraction: 0.76)) { + viewModel.toggleChecklistItem(itemID: step.id, viaVoice: false) + } + animateChecklistSelection(step.id) + } label: { + HStack(spacing: 6) { + Image(systemName: step.isChecked ? "arrow.uturn.backward" : "checkmark") + .font(.system(size: 11, weight: .bold)) + Text(step.isChecked ? "REOPEN" : "DONE") + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) } - .disabled(!item.allowManualComplete && !item.isChecked) - .opacity((!item.allowManualComplete && !item.isChecked) ? 0.75 : 1) + .foregroundColor(step.isChecked ? DesignSystem.colors.white : DesignSystem.colors.deepNavy) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(step.isChecked ? DesignSystem.colors.deepNavy.opacity(0.88) : DesignSystem.colors.vibrantTeal) + .clipShape(RoundedRectangle(cornerRadius: 8)) } + .buttonStyle(.plain) } } - } - .padding(12) - .frame(maxWidth: .infinity, maxHeight: maxHeight, alignment: .topLeading) - .background(DesignSystem.colors.deepNavy.opacity(0.62)) - .overlay(Rectangle().stroke(DesignSystem.colors.blueGrey.opacity(0.5), lineWidth: 1)) - } - private var bottomBar: some View { - HStack(spacing: 12) { - holdToTalkButton - Button { - animateFinishSOPPress() - viewModel.userTappedEndAndShip() - } label: { + if let step { + Text(step.name) + .font(DesignSystem.fonts.body(size: 20, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .lineLimit(2) + .minimumScaleFactor(0.75) + HStack(spacing: 8) { - if isFinishingSOP { - Image(systemName: "checkmark.circle.fill") - .transition(.scale.combined(with: .opacity)) + if !step.duration.isEmpty { + stepTag(step.duration.uppercased(), color: DesignSystem.colors.blueGrey) + } + stepTag(step.validation.uppercased(), color: DesignSystem.colors.vibrantTeal) + if step.critical { + stepTag("CRITICAL", color: .red) + } + if !step.allowManualComplete && !step.isChecked { + stepTag("VISION", color: .orange) } - Text(isFinishingSOP ? "FINISHING..." : "FINISH SOP") } + } else { + Text("All steps complete") + .font(DesignSystem.fonts.body(size: 20, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) } - .brutalistDangerButton() - .scaleEffect(finishButtonPulse ? 1.04 : 1.0) - .opacity(isFinishingSOP ? 0.92 : 1.0) - .animation(.spring(response: 0.25, dampingFraction: 0.65), value: finishButtonPulse) - .animation(.easeInOut(duration: 0.2), value: isFinishingSOP) - .disabled(isFinishingSOP) } + .padding(14) + .frame(maxWidth: .infinity, alignment: .leading) + .background(DesignSystem.colors.deepNavy.opacity(0.78)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.vibrantTeal.opacity(isHighlighted ? 0.95 : 0.5), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + private func stepTag(_ title: String, color: Color) -> some View { + Text(title) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(color) + .lineLimit(1) + .padding(.horizontal, 7) + .padding(.vertical, 4) + .background(color.opacity(0.14)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + + private var currentStep: ChecklistItemState? { + viewModel.checklistItems.first(where: { !$0.isChecked }) ?? viewModel.checklistItems.last + } + + private var currentStepNumber: Int { + guard let step = currentStep, + let index = viewModel.checklistItems.firstIndex(where: { $0.id == step.id }) + else { return 0 } + return index + 1 } private var pipelineStatusBadge: some View { @@ -327,21 +573,17 @@ struct CaptureView: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } - Button(viewModel.isRequestingHelp ? "REQUESTING..." : "REQUEST HELP") { - viewModel.requestSupervisorHelp() - } - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.deepNavy) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background(DesignSystem.colors.vibrantTeal) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(DesignSystem.colors.white, lineWidth: 1) - ) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .disabled(!viewModel.canRequestHelp || viewModel.isRequestingHelp) - .opacity(viewModel.canRequestHelp ? 1 : 0.5) + Text(viewModel.backOfficeCallButtonTitle) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + .padding(.horizontal, 10) + .padding(.vertical, 7) + .background(DesignSystem.colors.deepNavy.opacity(0.65)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.6), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } if !viewModel.helpStatusMessage.isEmpty { @@ -351,6 +593,13 @@ struct CaptureView: View { .multilineTextAlignment(.leading) } + if !viewModel.aiGuideStatusMessage.isEmpty { + Text(viewModel.aiGuideStatusMessage) + .font(DesignSystem.fonts.body(size: 12)) + .foregroundColor(DesignSystem.colors.white) + .multilineTextAlignment(.leading) + } + if !viewModel.geminiInstructionSyncStatus.isEmpty { Text(viewModel.geminiInstructionSyncStatus) .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) @@ -368,28 +617,105 @@ struct CaptureView: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } - private var holdToTalkButton: some View { - let listening = viewModel.isListeningForVoice - return Text(listening ? "LISTENING..." : "HOLD TO TALK") - .font(DesignSystem.fonts.mono(size: 15, weight: .semibold)) - .foregroundColor(listening ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) - .padding(.vertical, 14) - .frame(maxWidth: .infinity) - .background(listening ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy) - .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) - .scaleEffect(listening ? 1.03 : 1.0) - .animation(.easeInOut(duration: 0.18), value: listening) - .simultaneousGesture( - DragGesture(minimumDistance: 0) - .onChanged { _ in - if !viewModel.isListeningForVoice { - viewModel.startHoldToTalk() - } - } - .onEnded { _ in - viewModel.stopHoldToTalk() - } - ) + private var shouldShowAiConversationPanel: Bool { + geminiAssistant.isGeminiActive || + viewModel.isAiGuideStarting || + !geminiAssistant.userTranscript.isEmpty || + !geminiAssistant.aiTranscript.isEmpty || + !(geminiAssistant.errorMessage ?? "").isEmpty + } + + private var aiConversationPanel: some View { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + Image(systemName: geminiAssistant.isModelSpeaking ? "speaker.wave.2.fill" : "waveform.circle.fill") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(aiConversationColor) + + Text(aiConversationTitle) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + + Spacer(minLength: 6) + + Text(aiConnectionLabel) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(aiConversationColor) + } + + if !geminiAssistant.userTranscript.isEmpty { + Text("You: \(geminiAssistant.userTranscript)") + .font(DesignSystem.fonts.body(size: 12, weight: .medium)) + .foregroundColor(DesignSystem.colors.white.opacity(0.78)) + .lineLimit(2) + } + + if !geminiAssistant.aiTranscript.isEmpty { + Text("AI: \(geminiAssistant.aiTranscript)") + .font(DesignSystem.fonts.body(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .lineLimit(3) + } + + if let error = geminiAssistant.errorMessage, !error.isEmpty { + Text(error) + .font(DesignSystem.fonts.body(size: 12, weight: .medium)) + .foregroundColor(.white) + .lineLimit(3) + } + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(DesignSystem.colors.deepNavy.opacity(0.82)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(aiConversationColor.opacity(0.75), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + private var aiConversationTitle: String { + if geminiAssistant.isModelSpeaking { + return "AI speaking" + } + if viewModel.isAiGuideStarting { + return "Starting AI guide" + } + if geminiAssistant.isGeminiActive && geminiAssistant.isAudioReady { + return "AI listening" + } + if geminiAssistant.isGeminiActive { + return "AI connecting" + } + return "AI guide" + } + + private var aiConnectionLabel: String { + switch geminiAssistant.connectionState { + case .ready: + return "READY" + case .connecting: + return "CONNECTING" + case .settingUp: + return "SETTING UP" + case .error: + return "ERROR" + case .disconnected: + return "OFF" + } + } + + private var aiConversationColor: Color { + switch geminiAssistant.connectionState { + case .ready: + return DesignSystem.colors.vibrantTeal + case .connecting, .settingUp: + return .orange + case .error: + return .red + case .disconnected: + return DesignSystem.colors.blueGrey + } } private func animateChecklistSelection(_ itemID: UUID) { @@ -427,42 +753,3 @@ struct CaptureView: View { } } } - -private struct IPhoneCameraPreviewSurface: UIViewRepresentable { - let session: AVCaptureSession - - func makeUIView(context: Context) -> PreviewContainerView { - let view = PreviewContainerView() - view.previewLayer.videoGravity = .resizeAspectFill - view.previewLayer.session = session - if let connection = view.previewLayer.connection, - connection.isVideoRotationAngleSupported(90) { - connection.videoRotationAngle = 90 - } - return view - } - - func updateUIView(_ uiView: PreviewContainerView, context: Context) { - if uiView.previewLayer.session !== session { - uiView.previewLayer.session = session - } - if let connection = uiView.previewLayer.connection, - connection.isVideoRotationAngleSupported(90) { - connection.videoRotationAngle = 90 - } - } - - static func dismantleUIView(_ uiView: PreviewContainerView, coordinator: ()) { - uiView.previewLayer.session = nil - } -} - -private final class PreviewContainerView: UIView { - override class var layerClass: AnyClass { - AVCaptureVideoPreviewLayer.self - } - - var previewLayer: AVCaptureVideoPreviewLayer { - layer as! AVCaptureVideoPreviewLayer - } -} diff --git a/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift b/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift index b711a8ce..cf6489e4 100644 --- a/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift +++ b/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift @@ -5,11 +5,7 @@ struct GeminiStatusBar: View { var body: some View { HStack(spacing: 8) { - // Gemini connection pill StatusPill(color: geminiStatusColor, text: geminiStatusText) - - // Video AI Analyst bridge connection pill - StatusPill(color: openClawStatusColor, text: openClawStatusText) } } @@ -31,23 +27,6 @@ struct GeminiStatusBar: View { } } - private var openClawStatusColor: Color { - switch geminiVM.openClawConnectionState { - case .connected: return .green - case .checking: return .yellow - case .unreachable: return .red - case .notConfigured: return .gray - } - } - - private var openClawStatusText: String { - switch geminiVM.openClawConnectionState { - case .connected: return "Video AI Analyst" - case .checking: return "Video AI Analyst..." - case .unreachable: return "Analyst Off" - case .notConfigured: return "No Analyst" - } - } } struct StatusPill: View { @@ -95,60 +74,6 @@ struct TranscriptView: View { } } -struct ToolCallStatusView: View { - let status: ToolCallStatus - - var body: some View { - if status != .idle { - HStack(spacing: 8) { - statusIcon - Text(status.displayText) - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.white) - .lineLimit(1) - } - .padding(.horizontal, 14) - .padding(.vertical, 8) - .background(statusBackground) - .cornerRadius(16) - } - } - - @ViewBuilder - private var statusIcon: some View { - switch status { - case .executing: - ProgressView() - .scaleEffect(0.7) - .tint(.white) - case .completed: - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - .font(.system(size: 14)) - case .failed: - Image(systemName: "exclamationmark.circle.fill") - .foregroundColor(.red) - .font(.system(size: 14)) - case .cancelled: - Image(systemName: "xmark.circle.fill") - .foregroundColor(.yellow) - .font(.system(size: 14)) - case .idle: - EmptyView() - } - } - - private var statusBackground: Color { - switch status { - case .executing: return Color.black.opacity(0.7) - case .completed: return Color.black.opacity(0.6) - case .failed: return Color.red.opacity(0.3) - case .cancelled: return Color.black.opacity(0.6) - case .idle: return Color.clear - } - } -} - struct SpeakingIndicator: View { @State private var animating = false @@ -200,7 +125,6 @@ struct GeminiAssistantOverlay: View { } HStack(spacing: 10) { - ToolCallStatusView(status: geminiVM.toolCallStatus) if geminiVM.isModelSpeaking { SpeakingIndicator() .padding(.horizontal, 10) diff --git a/samples/CameraAccess/CameraAccess/Views/HomeView.swift b/samples/CameraAccess/CameraAccess/Views/HomeView.swift index 8e96775c..2b32f43f 100644 --- a/samples/CameraAccess/CameraAccess/Views/HomeView.swift +++ b/samples/CameraAccess/CameraAccess/Views/HomeView.swift @@ -15,18 +15,27 @@ struct HomeView: View { @State private var activeSheet: WorkerHomeSheet? var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 14) { - header - statusStrip - assignmentPanel - cameraPanel - notices + GeometryReader { geometry in + ZStack { + cameraBackdrop + + VStack(alignment: .leading, spacing: 0) { + header + .padding(.top, geometry.safeAreaInsets.top + 10) + + Spacer(minLength: 16) + + VStack(alignment: .leading, spacing: 12) { + sopQueuePanel(maxHeight: min(geometry.size.height * 0.46, 390)) + notices + } + .padding(.bottom, geometry.safeAreaInsets.bottom + 12) + } + .padding(.horizontal, 16) } - .padding(16) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .background(DesignSystem.colors.adminBackground.ignoresSafeArea()) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) .toolbar(.hidden, for: .navigationBar) .sheet(item: $activeSheet) { sheet in switch sheet { @@ -57,8 +66,45 @@ struct HomeView: View { } } + @ViewBuilder + private var cameraBackdrop: some View { + ZStack { + if viewModel.streamingMode == .iPhone, + let previewSession = viewModel.iPhonePreviewSession { + IPhoneCameraPreviewSurface(session: previewSession) + .ignoresSafeArea() + } else if let frame = viewModel.currentVideoFrame { + Image(uiImage: frame) + .resizable() + .aspectRatio(contentMode: .fill) + .ignoresSafeArea() + } else { + VStack(spacing: 12) { + ProgressView() + .tint(DesignSystem.colors.vibrantTeal) + Text("OPENING CAMERA") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(DesignSystem.colors.deepNavy) + } + + LinearGradient( + colors: [ + .black.opacity(0.58), + .black.opacity(0.12), + .black.opacity(0.72) + ], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + } + } + private var header: some View { - HStack(alignment: .center, spacing: 12) { + HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 6) { Text("EMBARCADERO") .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) @@ -70,13 +116,15 @@ struct HomeView: View { Text(viewModel.workerDisplayName) .font(DesignSystem.fonts.body(size: 28, weight: .semibold)) - .foregroundColor(DesignSystem.colors.adminInk) + .foregroundColor(DesignSystem.colors.white) .lineLimit(1) .minimumScaleFactor(0.72) - Text(viewModel.workerRoleText) + Text("\(viewModel.activePackageTitle) · \(viewModel.currentPackageProgressText)") .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.adminMuted) + .foregroundColor(DesignSystem.colors.white.opacity(0.76)) + .lineLimit(2) + .minimumScaleFactor(0.72) } Spacer(minLength: 12) @@ -99,77 +147,48 @@ struct HomeView: View { } } - private var statusStrip: some View { - HStack(spacing: 8) { - statusPill(title: viewModel.pendingShiftLabel, color: DesignSystem.colors.adminMuted) - statusPill(title: viewModel.assignmentQueueSummary, color: DesignSystem.colors.brandOrange) - } - } - - private var assignmentPanel: some View { + private func sopQueuePanel(maxHeight: CGFloat) -> some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 8) { - Text("CURRENT ASSIGNMENT") + Text("PENDING SOPS") .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) .foregroundColor(DesignSystem.colors.brandOrange) Spacer() - Text(viewModel.currentPackageProgressText) - .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) - .foregroundColor(DesignSystem.colors.adminMuted) - .lineLimit(1) - .minimumScaleFactor(0.75) + statusPill(title: viewModel.assignmentQueueSummary, color: DesignSystem.colors.brandOrange) + } + + HStack(spacing: 8) { + statusPill(title: viewModel.pendingShiftLabel, color: DesignSystem.colors.adminMuted) + statusPill(title: viewModel.selectedCaptureModeLabel, color: DesignSystem.colors.successGreen) } - if let sop = viewModel.currentAssignedSOP { + cameraSelector + + if viewModel.pendingTaskSOPs.isEmpty { VStack(alignment: .leading, spacing: 10) { - Text(sop.name) - .font(DesignSystem.fonts.body(size: 30, weight: .semibold)) + Text("No more SOPs pending") + .font(DesignSystem.fonts.body(size: 24, weight: .semibold)) .foregroundColor(DesignSystem.colors.adminInk) - .lineLimit(3) - .minimumScaleFactor(0.72) - Text(viewModel.currentAssignmentSubtitle) + Text("This package queue is complete. Refresh assignments when the next package is ready.") .font(DesignSystem.fonts.body(size: 15, weight: .medium)) .foregroundColor(DesignSystem.colors.adminMuted) - .lineLimit(2) - - HStack(spacing: 8) { - assignmentMetric("\(sop.steps.count)", "steps") - assignmentMetric(sop.validationSummary, "check") - } } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 10) } else { - VStack(alignment: .leading, spacing: 8) { - Text("No active SOP") - .font(DesignSystem.fonts.body(size: 28, weight: .semibold)) - .foregroundColor(DesignSystem.colors.adminInk) - - Text("Sync assignments or set the worker login in Settings.") - .font(DesignSystem.fonts.body(size: 15)) - .foregroundColor(DesignSystem.colors.adminMuted) + ScrollView(showsIndicators: true) { + VStack(spacing: 8) { + ForEach(Array(viewModel.pendingTaskSOPs.enumerated()), id: \.element.id) { index, sop in + sopQueueRow(sop, isNext: index == 0) + } + } } + .frame(maxHeight: maxHeight) } - - Button { - viewModel.startCurrentAssignmentFromHome() - } label: { - HStack(spacing: 10) { - Image(systemName: "camera.fill") - .font(.system(size: 16, weight: .semibold)) - Text(viewModel.currentAssignedSOP == nil ? "NO ASSIGNMENT" : "START CAMERA") - .font(DesignSystem.fonts.mono(size: 14, weight: .semibold)) - } - .foregroundColor(DesignSystem.colors.white) - .frame(maxWidth: .infinity) - .frame(height: 54) - .background(viewModel.currentAssignedSOP == nil ? DesignSystem.colors.adminSubtle : DesignSystem.colors.adminInk) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - .buttonStyle(.plain) - .disabled(viewModel.currentAssignedSOP == nil || viewModel.isSyncingOperations) } .padding(16) - .background(DesignSystem.colors.adminSurface) + .background(DesignSystem.colors.adminSurface.opacity(0.96)) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) @@ -178,65 +197,116 @@ struct HomeView: View { .shadow(color: .black.opacity(0.05), radius: 18, x: 0, y: 10) } - private var cameraPanel: some View { - VStack(alignment: .leading, spacing: 12) { - HStack(spacing: 10) { - Image(systemName: viewModel.hasActiveDevice ? "eyeglasses" : "iphone") - .font(.system(size: 18, weight: .semibold)) - .foregroundColor(viewModel.hasActiveDevice ? DesignSystem.colors.successGreen : DesignSystem.colors.brandOrange) - .frame(width: 34, height: 34) - .background(DesignSystem.colors.adminBackground) - .clipShape(RoundedRectangle(cornerRadius: 8)) + private var cameraSelector: some View { + HStack(spacing: 8) { + cameraModeButton( + title: "iPhone Camera", + systemName: "iphone", + mode: .iPhone, + enabled: true + ) + cameraModeButton( + title: viewModel.hasActiveDevice ? "Glasses Camera" : "Glasses Unavailable", + systemName: "eyeglasses", + mode: .glasses, + enabled: viewModel.hasActiveDevice + ) + } + } + + private func cameraModeButton( + title: String, + systemName: String, + mode: StreamingMode, + enabled: Bool + ) -> some View { + let selected = viewModel.preferredCaptureMode == mode + return Button { + viewModel.selectCaptureModeFromUI(mode) + } label: { + HStack(spacing: 6) { + Image(systemName: systemName) + .font(.system(size: 11, weight: .semibold)) + Text(title) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .lineLimit(1) + .minimumScaleFactor(0.72) + } + .foregroundColor(selected ? DesignSystem.colors.white : DesignSystem.colors.adminInk) + .padding(.horizontal, 9) + .frame(maxWidth: .infinity) + .frame(height: 34) + .background(selected ? DesignSystem.colors.adminInk : DesignSystem.colors.adminBackground) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(selected ? DesignSystem.colors.successGreen : DesignSystem.colors.adminStroke, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .disabled(!enabled || !viewModel.canSwitchCaptureMode) + .opacity((enabled && viewModel.canSwitchCaptureMode) ? 1 : 0.5) + } - VStack(alignment: .leading, spacing: 3) { - Text(viewModel.cameraReadinessLabel) - .font(DesignSystem.fonts.body(size: 16, weight: .semibold)) + private func sopQueueRow(_ sop: SOPTemplate, isNext: Bool) -> some View { + Button { + viewModel.presentCapture(for: sop) + } label: { + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 8) { + if isNext { + Text("NEXT") + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(DesignSystem.colors.successGreen) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + + Text(sop.packageTitle ?? viewModel.activePackageTitle) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(DesignSystem.colors.adminMuted) + .lineLimit(1) + } + + Text(sop.name) + .font(DesignSystem.fonts.body(size: 17, weight: .semibold)) .foregroundColor(DesignSystem.colors.adminInk) - Text(viewModel.cameraReadinessDetail) - .font(DesignSystem.fonts.body(size: 13)) + .lineLimit(2) + .minimumScaleFactor(0.78) + + Text("\(sop.steps.count) steps · \(sop.validationSummary)") + .font(DesignSystem.fonts.body(size: 12, weight: .medium)) .foregroundColor(DesignSystem.colors.adminMuted) + .lineLimit(1) } - Spacer() - - Text("AUTO") - .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) - .foregroundColor(DesignSystem.colors.brandOrange) - .padding(.horizontal, 8) - .padding(.vertical, 5) - .background(DesignSystem.colors.brandOrange.opacity(0.12)) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } + Spacer(minLength: 8) - if !viewModel.hasActiveDevice { - Button { - wearablesViewModel.connectGlasses() - } label: { - HStack(spacing: 8) { - Image(systemName: "eyeglasses") - Text(wearablesViewModel.registrationState == .registering ? "CONNECTING GLASSES" : "CONNECT GLASSES") - } - .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) - .foregroundColor(DesignSystem.colors.adminInk) - .frame(maxWidth: .infinity) - .frame(height: 42) - .background(DesignSystem.colors.adminBackground) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) - ) + HStack(spacing: 6) { + Image(systemName: "play.fill") + .font(.system(size: 11, weight: .bold)) + Text("START") + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) } - .buttonStyle(.plain) - .disabled(wearablesViewModel.registrationState == .registering) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 10) + .padding(.vertical, 9) + .background(DesignSystem.colors.adminInk) + .clipShape(RoundedRectangle(cornerRadius: 8)) } + .padding(12) + .background(isNext ? DesignSystem.colors.successGreen.opacity(0.08) : DesignSystem.colors.adminBackground) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(isNext ? DesignSystem.colors.successGreen.opacity(0.5) : DesignSystem.colors.adminStroke, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) } - .padding(14) - .background(DesignSystem.colors.adminSurface) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(DesignSystem.colors.adminStroke, lineWidth: 1) - ) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .buttonStyle(.plain) + .disabled(viewModel.isSyncingOperations) } @ViewBuilder diff --git a/samples/CameraAccess/CameraAccess/Views/IPhoneCameraPreviewSurface.swift b/samples/CameraAccess/CameraAccess/Views/IPhoneCameraPreviewSurface.swift new file mode 100644 index 00000000..600f848d --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Views/IPhoneCameraPreviewSurface.swift @@ -0,0 +1,42 @@ +import AVFoundation +import SwiftUI +import UIKit + +struct IPhoneCameraPreviewSurface: UIViewRepresentable { + let session: AVCaptureSession + + func makeUIView(context: Context) -> PreviewContainerView { + let view = PreviewContainerView() + view.previewLayer.videoGravity = .resizeAspectFill + view.previewLayer.session = session + if let connection = view.previewLayer.connection, + connection.isVideoRotationAngleSupported(90) { + connection.videoRotationAngle = 90 + } + return view + } + + func updateUIView(_ uiView: PreviewContainerView, context: Context) { + if uiView.previewLayer.session !== session { + uiView.previewLayer.session = session + } + if let connection = uiView.previewLayer.connection, + connection.isVideoRotationAngleSupported(90) { + connection.videoRotationAngle = 90 + } + } + + static func dismantleUIView(_ uiView: PreviewContainerView, coordinator: ()) { + uiView.previewLayer.session = nil + } +} + +final class PreviewContainerView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var previewLayer: AVCaptureVideoPreviewLayer { + layer as! AVCaptureVideoPreviewLayer + } +} diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift index 5eeaef4c..e6197b01 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift @@ -1,12 +1,23 @@ +import AVFoundation import Foundation import UIKit import WebRTC +enum WebRTCRoomMode: String { + case observation + case support + + var usesAudio: Bool { + self == .support + } +} + protocol WebRTCClientDelegate: AnyObject { func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState) func webRTCClient(_ client: WebRTCClient, didGenerateCandidate candidate: RTCIceCandidate) func webRTCClient(_ client: WebRTCClient, didReceiveRemoteVideoTrack track: RTCVideoTrack) func webRTCClient(_ client: WebRTCClient, didRemoveRemoteVideoTrack track: RTCVideoTrack) + func webRTCClient(_ client: WebRTCClient, didReceiveRemoteAudioTrack track: RTCAudioTrack) func webRTCClient(_ client: WebRTCClient, didUpdateSenderStats stats: WebRTCSenderStats) } @@ -24,7 +35,11 @@ class WebRTCClient: NSObject { private var localAudioTrack: RTCAudioTrack? private var localVideoSender: RTCRtpSender? private(set) var remoteVideoTrack: RTCVideoTrack? + private(set) var remoteAudioTrack: RTCAudioTrack? private var receiveRemoteVideo = true + private var captureMode: StreamingMode = .glasses + private var roomMode: WebRTCRoomMode = .support + private var audioRouteLease: WorkerAudioRouteLease? override init() { RTCInitializeSSL() @@ -40,10 +55,17 @@ class WebRTCClient: NSObject { func setup( iceServers: [RTCIceServer]? = nil, profile: WebRTCStreamProfile = WebRTCConfig.supportModeGlassesProfile, - receiveRemoteVideo: Bool = true + receiveRemoteVideo: Bool = true, + captureMode: StreamingMode = .glasses, + roomMode: WebRTCRoomMode = .support ) { streamProfile = profile self.receiveRemoteVideo = receiveRemoteVideo + self.captureMode = captureMode + self.roomMode = roomMode + if roomMode.usesAudio { + configureSupportAudioRoute(captureMode: captureMode) + } let config = RTCConfiguration() config.iceServers = iceServers ?? [RTCIceServer(urlStrings: WebRTCConfig.stunServers)] config.sdpSemantics = .unifiedPlan @@ -82,6 +104,11 @@ class WebRTCClient: NSObject { applyVideoSenderParameters() } + guard roomMode.usesAudio else { + localAudioTrack = nil + return + } + let audioConstraints = RTCMediaConstraints( mandatoryConstraints: nil, optionalConstraints: nil @@ -91,6 +118,50 @@ class WebRTCClient: NSObject { localAudioTrack?.isEnabled = true if let localAudioTrack { peerConnection?.add(localAudioTrack, streamIds: ["stream0"]) + Task { + await WorkerTelemetry.shared.record( + "webrtc_local_audio_track", + source: "webrtc", + stage: "sender", + payload: [ + "enabled": localAudioTrack.isEnabled, + "track_id": localAudioTrack.trackId, + "room_mode": roomMode.rawValue + ] + ) + } + } + } + + @discardableResult + func configureSupportAudioRoute(captureMode: StreamingMode) -> String? { + guard roomMode.usesAudio else { return nil } + let previousLease = audioRouteLease + do { + let snapshot = try WorkerAudioRouteCoordinator.shared.acquire( + owner: .backOfficeWebRTC, + mode: captureMode, + reason: "webrtc_support_call", + forceSpeaker: SettingsManager.shared.speakerOutputEnabled, + preferredIOBufferDuration: 0.02 + ) + audioRouteLease = snapshot.lease + if let previousLease, previousLease != snapshot.lease { + Task { + await WorkerAudioRouteCoordinator.shared.release(lease: previousLease) + } + } + self.captureMode = captureMode + + let rtcAudioSession = RTCAudioSession.sharedInstance() + rtcAudioSession.lockForConfiguration() + defer { rtcAudioSession.unlockForConfiguration() } + rtcAudioSession.useManualAudio = false + rtcAudioSession.isAudioEnabled = true + return snapshot.fallbackMessage + } catch { + NSLog("[WebRTC] Audio route setup failed: %@", error.localizedDescription) + return nil } } @@ -145,9 +216,23 @@ class WebRTCClient: NSObject { // MARK: - SDP Negotiation func createOffer(completion: @escaping (RTCSessionDescription) -> Void) { + Task { + await WorkerTelemetry.shared.record( + "webrtc_offer_create", + source: "webrtc", + stage: "negotiation", + payload: [ + "receive_audio": roomMode.usesAudio, + "receive_remote_video": receiveRemoteVideo, + "local_audio_enabled": localAudioTrack?.isEnabled ?? false, + "capture_mode": captureMode == .iPhone ? "iphone" : "glasses", + "room_mode": roomMode.rawValue + ] + ) + } let constraints = RTCMediaConstraints( mandatoryConstraints: [ - "OfferToReceiveAudio": "true", + "OfferToReceiveAudio": roomMode.usesAudio ? "true" : "false", "OfferToReceiveVideo": receiveRemoteVideo ? "true" : "false", ], optionalConstraints: nil @@ -178,6 +263,17 @@ class WebRTCClient: NSObject { func muteAudio(_ mute: Bool) { localAudioTrack?.isEnabled = !mute NSLog("[WebRTC] Local mic %@", mute ? "muted" : "live") + Task { + await WorkerTelemetry.shared.record( + "webrtc_local_audio_mute", + source: "webrtc", + stage: mute ? "muted" : "live", + payload: [ + "muted": mute, + "track_live": localAudioTrack != nil + ] + ) + } } func close() { @@ -185,8 +281,18 @@ class WebRTCClient: NSObject { localAudioTrack?.isEnabled = false localVideoSender = nil remoteVideoTrack = nil + remoteAudioTrack = nil peerConnection?.close() peerConnection = nil + let lease = audioRouteLease + audioRouteLease = nil + // Keep close() synchronous for the WebRTC state machine; the coordinator + // releases/deactivates the shared AVAudioSession off the caller thread. + if let lease { + Task { + await WorkerAudioRouteCoordinator.shared.release(lease: lease) + } + } NSLog("[WebRTC] Peer connection closed") } @@ -245,6 +351,31 @@ extension WebRTCClient: RTCPeerConnectionDelegate { remoteVideoTrack = videoTrack delegate?.webRTCClient(self, didReceiveRemoteVideoTrack: videoTrack) } + if let audioTrack = stream.audioTracks.first { + audioTrack.isEnabled = true + remoteAudioTrack = audioTrack + delegate?.webRTCClient(self, didReceiveRemoteAudioTrack: audioTrack) + } + } + + func peerConnection( + _ peerConnection: RTCPeerConnection, + didAdd receiver: RTCRtpReceiver, + streams: [RTCMediaStream] + ) { + guard let track = receiver.track else { return } + if let videoTrack = track as? RTCVideoTrack { + remoteVideoTrack = videoTrack + delegate?.webRTCClient(self, didReceiveRemoteVideoTrack: videoTrack) + NSLog("[WebRTC] Unified Plan remote video track received") + return + } + if let audioTrack = track as? RTCAudioTrack { + audioTrack.isEnabled = true + remoteAudioTrack = audioTrack + delegate?.webRTCClient(self, didReceiveRemoteAudioTrack: audioTrack) + NSLog("[WebRTC] Unified Plan remote audio track received") + } } func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift index 3720bcf3..2b5a40ad 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift @@ -21,10 +21,16 @@ enum WebRTCConfig { ] static let supportModeGlassesProfile = WebRTCStreamProfile( - maxBitrateBps: 1_200_000, - maxFramerate: 15, - maxWidth: 1280, - maxHeight: 720 + maxBitrateBps: 850_000, + maxFramerate: 12, + maxWidth: 960, + maxHeight: 540 + ) + static let supportModeGlassesFallbackProfile = WebRTCStreamProfile( + maxBitrateBps: 550_000, + maxFramerate: 10, + maxWidth: 640, + maxHeight: 360 ) static let supportModePhoneProfile = WebRTCStreamProfile( maxBitrateBps: 900_000, diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift index 85fdab31..b78c5aee 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift @@ -8,15 +8,22 @@ final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { label: "visionclaw.webrtc.realtime-forwarder", qos: .userInteractive ) + private let stateLock = NSLock() private var imageHandler: ((UIImage) -> Void)? private var pixelBufferHandler: ((CVPixelBuffer, Int64) -> Void)? + private var pendingPixelBuffer: CVPixelBuffer? + private var pendingPixelBufferTimestampNs: Int64 = 0 + private var pendingPixelBufferQueuedAt = CACurrentMediaTime() + private var isPixelBufferDrainScheduled = false private var pixelBufferForwardCount: Int64 = 0 + private var stalePixelBufferDropCount: Int64 = 0 private var pixelBufferStatsWindowStart = CACurrentMediaTime() func updateHandlers( imageHandler: ((UIImage) -> Void)?, pixelBufferHandler: ((CVPixelBuffer, Int64) -> Void)? ) { + clearPendingPixelBuffer() queue.async { self.imageHandler = imageHandler self.pixelBufferHandler = pixelBufferHandler @@ -31,10 +38,51 @@ final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { func enqueuePixelBuffer(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64) { let queuedAt = CACurrentMediaTime() - queue.async { - self.pixelBufferHandler?(pixelBuffer, timeStampNs) - self.pixelBufferForwardCount += 1 - self.logPixelBufferForwardStatsIfNeeded(waitDurationMs: (CACurrentMediaTime() - queuedAt) * 1000) + + var shouldScheduleDrain = false + stateLock.lock() + if pendingPixelBuffer != nil { + stalePixelBufferDropCount += 1 + } + pendingPixelBuffer = pixelBuffer + pendingPixelBufferTimestampNs = timeStampNs + pendingPixelBufferQueuedAt = queuedAt + if !isPixelBufferDrainScheduled { + isPixelBufferDrainScheduled = true + shouldScheduleDrain = true + } + stateLock.unlock() + + if shouldScheduleDrain { + queue.async { [weak self] in + self?.drainLatestPixelBuffer() + } + } + } + + private func clearPendingPixelBuffer() { + stateLock.lock() + pendingPixelBuffer = nil + isPixelBufferDrainScheduled = false + stateLock.unlock() + } + + private func drainLatestPixelBuffer() { + while true { + stateLock.lock() + guard let pixelBuffer = pendingPixelBuffer else { + isPixelBufferDrainScheduled = false + stateLock.unlock() + return + } + let timeStampNs = pendingPixelBufferTimestampNs + let queuedAt = pendingPixelBufferQueuedAt + pendingPixelBuffer = nil + stateLock.unlock() + + pixelBufferHandler?(pixelBuffer, timeStampNs) + pixelBufferForwardCount += 1 + logPixelBufferForwardStatsIfNeeded(waitDurationMs: (CACurrentMediaTime() - queuedAt) * 1000) } } @@ -44,9 +92,10 @@ final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { let elapsed = max(now - pixelBufferStatsWindowStart, 0.001) let fps = Double(pixelBufferForwardCount) / elapsed NSLog( - "[WebRTC] Realtime forwarder rate=%.1ffps last-wait=%.2fms", + "[WebRTC] Realtime forwarder rate=%.1ffps last-wait=%.2fms stale-dropped=%lld", fps, - waitDurationMs + waitDurationMs, + stalePixelBufferDropCount ) Task { await WorkerTelemetry.shared.record( @@ -58,12 +107,14 @@ final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { metricUnit: "fps", payload: [ "fps": fps, - "wait_ms": waitDurationMs + "wait_ms": waitDurationMs, + "stale_dropped": stalePixelBufferDropCount ] ) } pixelBufferStatsWindowStart = now pixelBufferForwardCount = 0 + stalePixelBufferDropCount = 0 } } @@ -86,8 +137,12 @@ class WebRTCSessionViewModel: ObservableObject { @Published var isMuted: Bool = false @Published var errorMessage: String? @Published var remoteVideoTrack: RTCVideoTrack? + @Published var remoteAudioTrack: RTCAudioTrack? @Published var hasRemoteVideo: Bool = false + @Published var hasRemoteAudio: Bool = false @Published var incomingRemoteVideoEnabled: Bool = true + @Published var isUnderLiveVideoPressure: Bool = false + @Published private(set) var roomMode: WebRTCRoomMode = .observation nonisolated let realtimeVideoForwarder = WebRTCRealtimeVideoForwarder() @@ -103,7 +158,14 @@ class WebRTCSessionViewModel: ObservableObject { private var savedRoomCode: String? private var foregroundObserver: Any? - func startSession(captureMode: StreamingMode = .glasses) async { + var isSupportMode: Bool { + roomMode == .support + } + + func startSession( + captureMode: StreamingMode = .glasses, + roomMode: WebRTCRoomMode = .support + ) async { guard !isActive else { return } guard WebRTCConfig.isConfigured else { errorMessage = "WebRTC signaling URL not configured." @@ -114,7 +176,8 @@ class WebRTCSessionViewModel: ObservableObject { connectionState = .connecting savedRoomCode = nil currentCaptureMode = captureMode - wantsIncomingRemoteVideo = captureMode != .iPhone + self.roomMode = roomMode + wantsIncomingRemoteVideo = roomMode == .support && captureMode != .iPhone incomingRemoteVideoEnabled = wantsIncomingRemoteVideo isUsingPhoneFallbackProfile = false stablePhoneSenderWindows = 0 @@ -123,7 +186,11 @@ class WebRTCSessionViewModel: ObservableObject { "webrtc_session_start", source: "webrtc", stage: "connecting", - payload: ["capture_mode": captureMode == .iPhone ? "iphone" : "glasses"] + payload: [ + "capture_mode": captureMode == .iPhone ? "iphone" : "glasses", + "room_mode": roomMode.rawValue, + "audio_enabled": roomMode.usesAudio + ] ) } @@ -145,13 +212,17 @@ class WebRTCSessionViewModel: ObservableObject { signalingClient = nil isActive = false connectionState = .disconnected + isUnderLiveVideoPressure = false roomCode = "" savedRoomCode = nil isMuted = false remoteVideoTrack = nil + remoteAudioTrack = nil hasRemoteVideo = false + hasRemoteAudio = false incomingRemoteVideoEnabled = true wantsIncomingRemoteVideo = true + roomMode = .observation isUsingPhoneFallbackProfile = false stablePhoneSenderWindows = 0 Task { @@ -168,6 +239,13 @@ class WebRTCSessionViewModel: ObservableObject { webRTCClient?.muteAudio(isMuted) } + @discardableResult + func refreshSupportAudioRoute(captureMode: StreamingMode) -> String? { + guard roomMode.usesAudio else { return nil } + currentCaptureMode = captureMode + return webRTCClient?.configureSupportAudioRoute(captureMode: captureMode) + } + /// Called by StreamSessionViewModel on each video frame. func pushVideoFrame(_ image: UIImage) { guard isActive, connectionState == .connected else { return } @@ -196,12 +274,16 @@ class WebRTCSessionViewModel: ObservableObject { ? WebRTCConfig.supportModePhoneFallbackProfile : WebRTCConfig.supportModePhoneProfile case .glasses: - profile = WebRTCConfig.supportModeGlassesProfile + profile = isUsingPhoneFallbackProfile + ? WebRTCConfig.supportModeGlassesFallbackProfile + : WebRTCConfig.supportModeGlassesProfile } client.setup( iceServers: iceServers, profile: profile, - receiveRemoteVideo: wantsIncomingRemoteVideo + receiveRemoteVideo: wantsIncomingRemoteVideo, + captureMode: captureMode, + roomMode: roomMode ) webRTCClient = client realtimeVideoForwarder.updateHandlers( @@ -485,10 +567,34 @@ class WebRTCSessionViewModel: ObservableObject { NSLog("[WebRTC] Remote video track removed") } - fileprivate func handleSenderStats(_ stats: WebRTCSenderStats) { - guard currentCaptureMode == .iPhone else { return } + fileprivate func handleRemoteAudioTrackReceived(_ track: RTCAudioTrack) { + guard roomMode.usesAudio else { + track.isEnabled = false + remoteAudioTrack = nil + hasRemoteAudio = false + NSLog("[WebRTC] Ignoring remote audio track in observation mode") + return + } + track.isEnabled = true + remoteAudioTrack = track + hasRemoteAudio = true + NSLog("[WebRTC] Remote audio track received") + Task { + await WorkerTelemetry.shared.record( + "webrtc_remote_audio_track", + source: "webrtc", + stage: "receiver", + payload: [ + "enabled": track.isEnabled, + "track_id": track.trackId + ] + ) + } + } + fileprivate func handleSenderStats(_ stats: WebRTCSenderStats) { let enqueueMs = stats.lastEnqueueDurationMs ?? 0 + let captureModeLabel = currentCaptureMode == .iPhone ? "iphone" : "glasses" Task { await WorkerTelemetry.shared.record( "webrtc_sender_stats", @@ -500,19 +606,23 @@ class WebRTCSessionViewModel: ObservableObject { "sender_fps": stats.windowFramesPerSecond, "dropped_frames": stats.windowDroppedFrames, "enqueue_ms": enqueueMs, - "fallback_profile": isUsingPhoneFallbackProfile + "fallback_profile": isUsingPhoneFallbackProfile, + "capture_mode": captureModeLabel ] ) } - let isUnderPressure = enqueueMs > 20 + let minimumHealthyFps = currentCaptureMode == .glasses ? 9.0 : 14.0 + let maxHealthyEnqueueMs = currentCaptureMode == .glasses ? 28.0 : 20.0 + let isUnderPressure = enqueueMs > maxHealthyEnqueueMs || stats.windowDroppedFrames >= 3 - || stats.windowFramesPerSecond < 14 + || stats.windowFramesPerSecond < minimumHealthyFps if isUnderPressure { + isUnderLiveVideoPressure = true stablePhoneSenderWindows = 0 guard !isUsingPhoneFallbackProfile else { return } isUsingPhoneFallbackProfile = true - webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneFallbackProfile) + webRTCClient?.updateStreamProfile(fallbackProfile(for: currentCaptureMode)) Task { await WorkerTelemetry.shared.record( "webrtc_profile_downgrade", @@ -521,12 +631,14 @@ class WebRTCSessionViewModel: ObservableObject { payload: [ "sender_fps": stats.windowFramesPerSecond, "dropped_frames": stats.windowDroppedFrames, - "enqueue_ms": enqueueMs + "enqueue_ms": enqueueMs, + "capture_mode": captureModeLabel ] ) } NSLog( - "[WebRTC] Phone sender downgraded to fallback profile (fps=%.1f dropped=%lld enqueue=%@ms)", + "[WebRTC] %@ sender downgraded to fallback profile (fps=%.1f dropped=%lld enqueue=%@ms)", + captureModeLabel, stats.windowFramesPerSecond, stats.windowDroppedFrames, stats.lastEnqueueDurationMs.map { String(format: "%.1f", $0) } ?? "direct" @@ -534,22 +646,44 @@ class WebRTCSessionViewModel: ObservableObject { return } + isUnderLiveVideoPressure = false guard isUsingPhoneFallbackProfile else { return } stablePhoneSenderWindows += 1 - if stablePhoneSenderWindows >= 3 { + if stablePhoneSenderWindows >= 6 { stablePhoneSenderWindows = 0 isUsingPhoneFallbackProfile = false - webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneProfile) + webRTCClient?.updateStreamProfile(defaultProfile(for: currentCaptureMode)) Task { await WorkerTelemetry.shared.record( "webrtc_profile_restore", source: "webrtc", stage: "sender", - payload: ["stable_windows": stablePhoneSenderWindows] + payload: [ + "stable_windows": stablePhoneSenderWindows, + "capture_mode": captureModeLabel + ] ) } - NSLog("[WebRTC] Phone sender restored to default support profile") + NSLog("[WebRTC] %@ sender restored to default support profile", captureModeLabel) + } + } + + private func defaultProfile(for mode: StreamingMode) -> WebRTCStreamProfile { + switch mode { + case .iPhone: + return WebRTCConfig.supportModePhoneProfile + case .glasses: + return WebRTCConfig.supportModeGlassesProfile + } + } + + private func fallbackProfile(for mode: StreamingMode) -> WebRTCStreamProfile { + switch mode { + case .iPhone: + return WebRTCConfig.supportModePhoneFallbackProfile + case .glasses: + return WebRTCConfig.supportModeGlassesFallbackProfile } } } @@ -587,6 +721,12 @@ private class WebRTCDelegateAdapter: WebRTCClientDelegate { } } + func webRTCClient(_ client: WebRTCClient, didReceiveRemoteAudioTrack track: RTCAudioTrack) { + Task { @MainActor [weak self] in + self?.viewModel?.handleRemoteAudioTrackReceived(track) + } + } + func webRTCClient(_ client: WebRTCClient, didUpdateSenderStats stats: WebRTCSenderStats) { Task { @MainActor [weak self] in self?.viewModel?.handleSenderStats(stats) diff --git a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift index dbf27e20..6d713b84 100644 --- a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift +++ b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift @@ -1,17 +1,24 @@ import AVFoundation import UIKit +private struct SendablePixelBuffer: @unchecked Sendable { + let pixelBuffer: CVPixelBuffer +} + class IPhoneCameraManager: NSObject, @unchecked Sendable { private let captureSession = AVCaptureSession() private let videoOutput = AVCaptureVideoDataOutput() private let movieOutput = AVCaptureMovieFileOutput() private let sessionQueue = DispatchQueue(label: "iphone-camera-session") + private let analysisQueue = DispatchQueue(label: "iphone-camera-analysis", qos: .userInitiated) private let context = CIContext() private var isRunning = false private var isConfigured = false + private var isAudioInputConfigured = false private var recordingCompletion: ((URL?) -> Void)? private var currentRecordingURL: URL? private var lastAnalysisEmissionAt: CFTimeInterval = 0 + private var isAnalysisConversionInFlight = false private var sampleFrameCount: Int64 = 0 private var analysisFrameCount: Int64 = 0 private var statsWindowStart = CACurrentMediaTime() @@ -32,6 +39,7 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { self?.analysisFrameCount = 0 self?.statsWindowStart = CACurrentMediaTime() self?.lastAnalysisEmissionAt = 0 + self?.isAnalysisConversionInFlight = false self?.hasDeliveredFirstPreviewFrame = false self?.configureSession() self?.captureSession.startRunning() @@ -167,7 +175,9 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { let audioInput = try? AVCaptureDeviceInput(device: microphone), captureSession.canAddInput(audioInput) { captureSession.addInput(audioInput) + isAudioInputConfigured = true } else { + isAudioInputConfigured = false NSLog("[iPhoneCamera] Microphone input unavailable for recording") } @@ -205,6 +215,30 @@ class IPhoneCameraManager: NSObject, @unchecked Sendable { NSLog("[iPhoneCamera] Session configured successfully") } + func waitUntilRunningAndAudioConfigured(timeout: TimeInterval) async -> Bool { + let deadline = CACurrentMediaTime() + max(0, timeout) + while !Task.isCancelled { + let ready = await withCheckedContinuation { continuation in + sessionQueue.async { [weak self] in + guard let self else { + continuation.resume(returning: false) + return + } + continuation.resume( + returning: self.isConfigured && + self.isRunning && + self.captureSession.isRunning && + self.isAudioInputConfigured + ) + } + } + if ready { return true } + if CACurrentMediaTime() >= deadline { return false } + try? await Task.sleep(nanoseconds: 100_000_000) + } + return false + } + static func requestPermission() async -> Bool { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { @@ -243,11 +277,26 @@ extension IPhoneCameraManager: AVCaptureVideoDataOutputSampleBufferDelegate { let now = CACurrentMediaTime() guard now - lastAnalysisEmissionAt >= analysisFrameInterval else { return } + guard !isAnalysisConversionInFlight else { return } lastAnalysisEmissionAt = now + isAnalysisConversionInFlight = true - guard let image = makeUIImage(from: sampleBuffer, maxDimension: analysisMaxDimension) else { return } + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + isAnalysisConversionInFlight = false + return + } analysisFrameCount += 1 - onFrameCaptured?(image) + let frameHandler = onFrameCaptured + let maxDimension = analysisMaxDimension + let analysisPixelBuffer = SendablePixelBuffer(pixelBuffer: pixelBuffer) + analysisQueue.async { [weak self] in + let image = self?.makeUIImage(from: analysisPixelBuffer.pixelBuffer, maxDimension: maxDimension) + self?.sessionQueue.async { [weak self] in + self?.isAnalysisConversionInFlight = false + } + guard let image else { return } + frameHandler?(image) + } } } @@ -313,9 +362,7 @@ extension IPhoneCameraManager: AVCaptureFileOutputRecordingDelegate { } extension IPhoneCameraManager { - fileprivate func makeUIImage(from sampleBuffer: CMSampleBuffer, maxDimension: CGFloat? = nil) -> UIImage? { - guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } - + fileprivate func makeUIImage(from pixelBuffer: CVPixelBuffer, maxDimension: CGFloat? = nil) -> UIImage? { var ciImage = CIImage(cvPixelBuffer: pixelBuffer) let extent = ciImage.extent diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index 1f333387..e67423f8 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -294,7 +294,7 @@ private struct WorkerAdminAPISnapshot { let uploadCalls: [(assetID: String, byteSize: Int, contentType: String)] let finalizeRequests: [WorkerMediaFinalizeRequest] let telemetryBatches: [WorkerTelemetryBatch] - let liveTokenRequests: [(model: String, sessionID: String?)] + let liveTokenRequests: [(model: String?, sessionID: String?)] let spotterRequests: [GeminiSpotterRequest] } @@ -346,10 +346,10 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { private var recordedUploadCalls: [(assetID: String, byteSize: Int, contentType: String)] = [] private var recordedFinalizeRequests: [WorkerMediaFinalizeRequest] = [] private var recordedTelemetryBatches: [WorkerTelemetryBatch] = [] - private var recordedLiveTokenRequests: [(model: String, sessionID: String?)] = [] + private var recordedLiveTokenRequests: [(model: String?, sessionID: String?)] = [] private var recordedSpotterRequests: [GeminiSpotterRequest] = [] - func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws { + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws -> WorkerLiveHeartbeatResponse { let queuedError = lock.withLock { () -> Error? in recordedHeartbeats.append(heartbeat) return heartbeatErrors.isEmpty ? nil : heartbeatErrors.removeFirst() @@ -358,6 +358,17 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { if let queuedError { throw queuedError } + + return WorkerLiveHeartbeatResponse( + sessionID: heartbeat.sessionID, + updatedAt: "2026-05-30T18:31:00.000Z", + isFreshLiveSession: true, + webrtcRoomCode: heartbeat.webrtcRoomCode, + supportMode: heartbeat.helpRequested ? "handoff_requested" : "ai", + aiSessionStatus: heartbeat.helpRequested ? "paused" : "active", + humanSupportStatus: heartbeat.helpRequested ? "ringing" : "none", + shouldOpenLiveRoom: false + ) } func requestWorkerMediaUploadTarget( @@ -442,7 +453,7 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { } func requestGeminiLiveToken( - model: String, + model: String?, sessionID: String? ) async throws -> GeminiLiveTokenResponse { let (queuedError, response) = lock.withLock { () -> (Error?, GeminiLiveTokenResponse) in @@ -453,9 +464,13 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { token: "ephemeral-token", expiresAt: "2026-05-30T19:00:00.000Z", newSessionExpiresAt: "2026-05-30T18:31:00.000Z", - model: model, + model: model ?? GeminiConfig.model, websocketBaseURL: GeminiConfig.ephemeralTokenWebsocketBaseURL, - queryParameterName: "access_token" + queryParameterName: "access_token", + systemInstruction: "Server-built checklist instruction.", + runtimeContext: nil, + diagnosticsID: "test-diagnostics", + provider: "gemini" ) : liveTokenResponses.removeFirst() return (queuedError, response) @@ -939,7 +954,7 @@ final class AssignmentDrivenFlowTests: XCTestCase { try? Wearables.configure() } - func testHomeStartsFirstAssignedSopWithoutPicker() async throws { + func testExplicitHomeSelectionStartsFirstPendingSOP() async throws { let viewModel = StreamSessionViewModel(wearables: Wearables.shared) let secondAssignment = SOPTemplate( name: "Second assigned SOP", @@ -963,7 +978,7 @@ final class AssignmentDrivenFlowTests: XCTestCase { XCTAssertEqual(viewModel.preferredCaptureMode, .iPhone) } - func testHomePrefersGlassesForNextAssignmentWhenAvailable() async throws { + func testExplicitHomeSelectionPrefersGlassesWhenAvailable() async throws { let viewModel = StreamSessionViewModel(wearables: Wearables.shared) viewModel.hasActiveDevice = true viewModel.availableSOPs = [ @@ -1043,15 +1058,15 @@ final class OpsAPIClientRoutingTests: XCTestCase { let settings = SettingsManager.shared let originalOpsBaseURL = settings.opsBaseURL let originalAdminBaseURL = settings.adminBaseURL - let originalBearerToken = settings.openClawBearerToken + let originalBearerToken = settings.workerAPIBearerToken settings.opsBaseURL = "https://ops.example.test" settings.adminBaseURL = "http://admin.example.test:3001" - settings.openClawBearerToken = "worker-bearer-token" + settings.workerAPIBearerToken = "worker-bearer-token" defer { settings.opsBaseURL = originalOpsBaseURL settings.adminBaseURL = originalAdminBaseURL - settings.openClawBearerToken = originalBearerToken + settings.workerAPIBearerToken = originalBearerToken } let lock = NSLock() @@ -1066,7 +1081,10 @@ final class OpsAPIClientRoutingTests: XCTestCase { case "/health": body = Data(#"{"status":"ok","service":"ops"}"#.utf8) case "/api/worker/live/heartbeat": - body = Data("{}".utf8) + body = Data( + #"{"sessionId":"11111111-1111-1111-1111-111111111111","updatedAt":"2026-05-30T18:31:00.000Z","isFreshLiveSession":true,"webrtcRoomCode":"ROOM42","supportMode":"handoff_requested","aiSessionStatus":"paused","humanSupportStatus":"ringing","supportUpdatedAt":"2026-05-30T18:31:00.000Z","shouldOpenLiveRoom":false}"# + .utf8 + ) case "/api/worker/media/upload-target": body = Data( #"{"assetId":"video-asset-1","bucket":"execution-videos","path":"sessions/session-1/recording.mp4","uploadUrl":"https://upload.example/video-1"}"# @@ -1143,7 +1161,8 @@ final class OpsAPIClientRoutingTests: XCTestCase { imageMimeType: "image/jpeg", capturedAt: "2026-05-30T18:31:00.000Z", critical: false, - allowAIComplete: true + allowAIComplete: true, + elapsedActiveMs: nil ) ) @@ -1172,13 +1191,13 @@ final class OpsAPIClientRoutingTests: XCTestCase { func testWorkerRouteErrorsMentionAdminIngest() async throws { let settings = SettingsManager.shared let originalAdminBaseURL = settings.adminBaseURL - let originalBearerToken = settings.openClawBearerToken + let originalBearerToken = settings.workerAPIBearerToken settings.adminBaseURL = "http://admin.example.test:3001" - settings.openClawBearerToken = "worker-bearer-token" + settings.workerAPIBearerToken = "worker-bearer-token" defer { settings.adminBaseURL = originalAdminBaseURL - settings.openClawBearerToken = originalBearerToken + settings.workerAPIBearerToken = originalBearerToken } RequestCaptureURLProtocol.handler = { request in @@ -1216,13 +1235,13 @@ final class OpsAPIClientRoutingTests: XCTestCase { func testTelemetryRouteUsesAdminBaseURL() async throws { let settings = SettingsManager.shared let originalAdminBaseURL = settings.adminBaseURL - let originalBearerToken = settings.openClawBearerToken + let originalBearerToken = settings.workerAPIBearerToken settings.adminBaseURL = "http://admin.example.test:3001" - settings.openClawBearerToken = "worker-bearer-token" + settings.workerAPIBearerToken = "worker-bearer-token" defer { settings.adminBaseURL = originalAdminBaseURL - settings.openClawBearerToken = originalBearerToken + settings.workerAPIBearerToken = originalBearerToken } let lock = NSLock() From 4db3aa72b931580d629c9bffd3405d51c2c28084 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 11 Jun 2026 14:07:29 -0500 Subject: [PATCH 08/11] Guard iOS audio engine and heartbeat handoff --- .../CameraAccess/Gemini/AudioManager.swift | 173 +++++++++++------- .../Gemini/GeminiSessionViewModel.swift | 29 ++- .../ViewModels/StreamSessionViewModel.swift | 44 ++++- 3 files changed, 172 insertions(+), 74 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift index aa901b4d..9bcf8480 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift @@ -328,11 +328,14 @@ final class WorkerAudioRouteCoordinator: @unchecked Sendable { final class AudioManager: @unchecked Sendable { var onAudioCaptured: ((Data) -> Void)? + // Keep the engine container permanent for the process lifetime. Teardown only + // stops and detaches child nodes; it never nils or replaces this engine. private let audioEngine = AVAudioEngine() private let audioLifecycleQueue = DispatchQueue( label: "gemini.audio.lifecycle", qos: .userInitiated ) + private let audioLifecycleQueueKey = DispatchSpecificKey() private let playerNode = AVAudioPlayerNode() private var isCapturing = false private var isInputTapInstalled = false @@ -340,12 +343,14 @@ final class AudioManager: @unchecked Sendable { private var wasCapturingBeforeInterruption = false private var useIPhoneMode = false private var audioRouteLease: WorkerAudioRouteLease? + private var audioGraphGeneration: UInt64 = 0 private let outputFormat: AVAudioFormat // Accumulate resampled PCM into ~100ms chunks before sending private let sendQueue = DispatchQueue(label: "audio.accumulator") private var accumulatedData = Data() + private var accumulatorGeneration: UInt64 = 0 private let minSendBytes = 3200 // 100ms at 16kHz mono Int16 = 1600 frames * 2 bytes // Notification observers for background resilience @@ -361,6 +366,7 @@ final class AudioManager: @unchecked Sendable { channels: GeminiConfig.audioChannels, interleaved: true )! + audioLifecycleQueue.setSpecific(key: audioLifecycleQueueKey, value: ()) } func setupAudioSession(useIPhoneMode: Bool = false) throws { @@ -386,6 +392,12 @@ final class AudioManager: @unchecked Sendable { } func startCapture() throws { + try syncOnAudioLifecycleQueue { + try startCaptureOnAudioLifecycleQueue() + } + } + + private func startCaptureOnAudioLifecycleQueue() throws { guard !isCapturing else { return } if isInputTapInstalled { @@ -422,7 +434,12 @@ final class AudioManager: @unchecked Sendable { NSLog("[Audio] Needs resample: %@", needsResample ? "YES" : "NO") - sendQueue.async { self.accumulatedData = Data() } + audioGraphGeneration &+= 1 + let captureGeneration = audioGraphGeneration + sendQueue.sync { + accumulatedData = Data() + accumulatorGeneration = captureGeneration + } var converter: AVAudioConverter? if needsResample { @@ -440,8 +457,9 @@ final class AudioManager: @unchecked Sendable { guard let self else { return } tapCount += 1 + let currentTapCount = tapCount let rmsValue = self.computeRMS(buffer) - if tapCount % 15 == 0 { + if currentTapCount % 15 == 0 { print("[Audio Monitor] App Mic Level: \(rmsValue)") } let pcmData: Data @@ -454,7 +472,7 @@ final class AudioManager: @unchecked Sendable { interleaved: false )! guard let resampled = self.convertBuffer(buffer, using: converter, targetFormat: resampleFormat) else { - if tapCount <= 3 { NSLog("[Audio] Resample failed for tap #%d", tapCount) } + if currentTapCount <= 3 { NSLog("[Audio] Resample failed for tap #%d", currentTapCount) } return } pcmData = self.float32BufferToInt16Data(resampled) @@ -464,11 +482,12 @@ final class AudioManager: @unchecked Sendable { // Accumulate into ~100ms chunks before sending to Gemini self.sendQueue.async { + guard self.accumulatorGeneration == captureGeneration else { return } self.accumulatedData.append(pcmData) if self.accumulatedData.count >= self.minSendBytes { let chunk = self.accumulatedData self.accumulatedData = Data() - if tapCount <= 3 { + if currentTapCount <= 3 { NSLog("[Audio] Sending chunk: %d bytes (~%dms)", chunk.count, chunk.count / 32) // 16kHz * 2 bytes = 32 bytes/ms } @@ -483,13 +502,20 @@ final class AudioManager: @unchecked Sendable { playerNode.play() isCapturing = true } catch { - tearDownEngineGraph(flushPendingAudio: false) + tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: false) throw error } } func playAudio(data: Data) { - guard isCapturing, !data.isEmpty else { return } + guard !data.isEmpty else { return } + audioLifecycleQueue.async { [weak self] in + self?.playAudioOnAudioLifecycleQueue(data: data) + } + } + + private func playAudioOnAudioLifecycleQueue(data: Data) { + guard isCapturing, isPlayerNodeAttached, audioEngine.isRunning, !data.isEmpty else { return } let playerFormat = AVAudioFormat( commonFormat: .pcmFormatFloat32, @@ -519,9 +545,15 @@ final class AudioManager: @unchecked Sendable { } func stopPlayback() { + audioLifecycleQueue.async { [weak self] in + self?.stopPlaybackOnAudioLifecycleQueue() + } + } + + private func stopPlaybackOnAudioLifecycleQueue() { guard isPlayerNodeAttached else { return } playerNode.stop() - if isCapturing { + if isCapturing, audioEngine.isRunning { playerNode.play() } } @@ -536,13 +568,7 @@ final class AudioManager: @unchecked Sendable { return } - guard self.isCapturing || self.isInputTapInstalled || self.isPlayerNodeAttached else { - self.removeObservers() - continuation.resume() - return - } - - self.tearDownEngineGraph(flushPendingAudio: true) { + self.tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: true) { self.removeObservers() continuation.resume() } @@ -613,10 +639,7 @@ final class AudioManager: @unchecked Sendable { ) { [weak self] _ in guard let self else { return } NSLog("[Audio] App will enter foreground") - if self.isCapturing && !self.audioEngine.isRunning { - NSLog("[Audio] Audio engine stopped while backgrounded, attempting reset") - self.attemptAudioReset() - } + self.checkAndResetStoppedEngine() } } @@ -624,9 +647,12 @@ final class AudioManager: @unchecked Sendable { switch type { case .began: NSLog("[Audio] Audio interruption began (e.g. phone call)") - wasCapturingBeforeInterruption = isCapturing - if isCapturing { - audioEngine.pause() + audioLifecycleQueue.async { [weak self] in + guard let self else { return } + self.wasCapturingBeforeInterruption = self.isCapturing + if self.isCapturing { + self.audioEngine.pause() + } } case .ended: NSLog("[Audio] Audio interruption ended (shouldResume=%@)", shouldResume ? "true" : "false") @@ -644,9 +670,7 @@ final class AudioManager: @unchecked Sendable { NSLog("[Audio] New audio device available") case .oldDeviceUnavailable: NSLog("[Audio] Audio device removed") - if isCapturing { - attemptAudioReset() - } + attemptAudioReset() case .categoryChange, .override, .wakeFromSleep, .routeConfigurationChange: NSLog("[Audio] Audio route change: %d", reason.rawValue) default: @@ -673,54 +697,72 @@ final class AudioManager: @unchecked Sendable { private func resumeAudioAfterInterruption() { NSLog("[Audio] Resuming audio after interruption") - let audioSession = AVAudioSession.sharedInstance() - do { - try audioSession.setActive(true) - try audioEngine.start() - NSLog("[Audio] Audio resumed successfully") - } catch { - NSLog("[Audio] Failed to resume audio: %@", error.localizedDescription) - attemptAudioReset() + audioLifecycleQueue.async { [weak self] in + guard let self else { return } + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setActive(true) + if self.isCapturing, !self.audioEngine.isRunning { + try self.audioEngine.start() + } + if self.isCapturing, self.isPlayerNodeAttached, !self.playerNode.isPlaying { + self.playerNode.play() + } + NSLog("[Audio] Audio resumed successfully") + } catch { + NSLog("[Audio] Failed to resume audio: %@", error.localizedDescription) + self.attemptAudioReset() + } } } private func attemptAudioReset() { NSLog("[Audio] Attempting audio reset") - let wasCapturing = isCapturing - - tearDownEngineGraph(flushPendingAudio: false) - - if wasCapturing { - do { - try setupAudioSession(useIPhoneMode: useIPhoneMode) - try startCapture() - NSLog("[Audio] Audio reset successful") - } catch { - NSLog("[Audio] Audio reset failed: %@", error.localizedDescription) + audioLifecycleQueue.async { [weak self] in + guard let self else { return } + let wasCapturing = self.isCapturing + let useIPhoneMode = self.useIPhoneMode + + self.tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: false) { [weak self] in + guard let self, wasCapturing else { return } + DispatchQueue.main.async { [weak self] in + guard let self else { return } + do { + try self.setupAudioSession(useIPhoneMode: useIPhoneMode) + try self.startCapture() + NSLog("[Audio] Audio reset successful") + } catch { + NSLog("[Audio] Audio reset failed: %@", error.localizedDescription) + } + } } } } - private func tearDownEngineGraph(flushPendingAudio: Bool, completion: (() -> Void)? = nil) { + private func tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: Bool, completion: (() -> Void)? = nil) { + audioGraphGeneration &+= 1 + isCapturing = false + + audioEngine.stop() + if isInputTapInstalled { audioEngine.inputNode.removeTap(onBus: 0) isInputTapInstalled = false } - if audioEngine.isRunning { - audioEngine.stop() - } + if playerNode.isPlaying { playerNode.stop() } + if isPlayerNodeAttached { audioEngine.disconnectNodeOutput(playerNode) audioEngine.detach(playerNode) isPlayerNodeAttached = false } - isCapturing = false sendQueue.async { defer { completion?() } + self.accumulatorGeneration = 0 guard !self.accumulatedData.isEmpty else { return } let chunk = self.accumulatedData self.accumulatedData = Data() @@ -738,27 +780,30 @@ final class AudioManager: @unchecked Sendable { return } - if self.isInputTapInstalled { - self.audioEngine.inputNode.removeTap(onBus: 0) - self.isInputTapInstalled = false - } - if self.audioEngine.isRunning { - self.audioEngine.stop() - } - if self.playerNode.isPlaying { - self.playerNode.stop() - } - if self.isPlayerNodeAttached { - self.audioEngine.disconnectNodeOutput(self.playerNode) - self.audioEngine.detach(self.playerNode) - self.isPlayerNodeAttached = false + self.tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: false) { + continuation.resume() } - self.isCapturing = false - continuation.resume() } } } + private func checkAndResetStoppedEngine() { + audioLifecycleQueue.async { [weak self] in + guard let self else { return } + if self.isCapturing, !self.audioEngine.isRunning { + NSLog("[Audio] Audio engine stopped while backgrounded, attempting reset") + self.attemptAudioReset() + } + } + } + + private func syncOnAudioLifecycleQueue(_ work: () throws -> T) rethrows -> T { + if DispatchQueue.getSpecific(key: audioLifecycleQueueKey) != nil { + return try work() + } + return try audioLifecycleQueue.sync(execute: work) + } + private func removeObservers() { if let observer = interruptionObserver { NotificationCenter.default.removeObserver(observer) diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift index c65d3038..1864a0af 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift @@ -144,6 +144,7 @@ class GeminiSessionViewModel: ObservableObject { audioManager.onAudioCaptured = { [weak self] data in guard let self else { return } Task { @MainActor in + guard self.isGeminiActive, !self.isStoppingSession else { return } let speakerOnPhone = self.streamingMode == .iPhone || SettingsManager.shared.speakerOutputEnabled if speakerOnPhone && self.geminiService.isModelSpeaking { return } self.onInputAudioChunk?(data) @@ -152,12 +153,20 @@ class GeminiSessionViewModel: ObservableObject { } geminiService.onAudioReceived = { [weak self] data in - self?.onOutputAudioChunk?(data) - self?.audioManager.playAudio(data: data) + guard let self else { return } + Task { @MainActor in + guard self.isGeminiActive, !self.isStoppingSession else { return } + self.onOutputAudioChunk?(data) + self.audioManager.playAudio(data: data) + } } geminiService.onInterrupted = { [weak self] in - self?.audioManager.stopPlayback() + guard let self else { return } + Task { @MainActor in + guard self.isGeminiActive, !self.isStoppingSession else { return } + self.audioManager.stopPlayback() + } } geminiService.onTurnComplete = { [weak self] in @@ -217,10 +226,12 @@ class GeminiSessionViewModel: ObservableObject { let wasActive = isGeminiActive isGeminiActive = false isAudioReady = false + isModelSpeaking = false isStoppingSession = true + clearGeminiCallbacks() + audioManager.stopPlayback() await audioManager.stopCapture() await Task.yield() - clearGeminiCallbacks() await geminiService.disconnectAndWaitForClose(timeout: 1.0) stateObservation?.cancel() stateObservation = nil @@ -359,20 +370,20 @@ class GeminiSessionViewModel: ObservableObject { private func resetToIdle(message: String?) async { isStoppingSession = true + isGeminiActive = false + isAudioReady = false + isModelSpeaking = false stateObservation?.cancel() stateObservation = nil + clearGeminiCallbacks() + audioManager.stopPlayback() // This is the Gemini-to-WebRTC hardware barrier: the engine graph and // accumulator finish before the socket is allowed to close. await audioManager.stopCapture() await Task.yield() - audioManager.stopPlayback() - clearGeminiCallbacks() await geminiService.disconnectAndWaitForClose(timeout: 1.0) - isGeminiActive = false - isAudioReady = false connectionState = .disconnected - isModelSpeaking = false userTranscript = "" aiTranscript = "" currentSessionInstruction = nil diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 19312ce5..44fafb14 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -1567,6 +1567,7 @@ actor WorkerAdminLiveSessionCoordinator { typealias Sleeper = @Sendable (UInt64) async -> Void typealias FileLoader = @Sendable (URL) async -> Data? typealias HeartbeatResponseHandler = @Sendable (WorkerLiveHeartbeatResponse) async -> Void + typealias HeartbeatFailureHandler = @Sendable (_ sessionID: String, _ message: String) async -> Void private let api: WorkerAdminAPI private let telemetry: WorkerTelemetry? @@ -1574,6 +1575,7 @@ actor WorkerAdminLiveSessionCoordinator { private let sleeper: Sleeper private let fileLoader: FileLoader private let onHeartbeatResponse: HeartbeatResponseHandler? + private let onHeartbeatFailure: HeartbeatFailureHandler? private var sessionID: String? private var roomCode: String? @@ -1591,6 +1593,7 @@ actor WorkerAdminLiveSessionCoordinator { heartbeatIntervalNanoseconds: UInt64 = 7_000_000_000, telemetry: WorkerTelemetry? = nil, onHeartbeatResponse: HeartbeatResponseHandler? = nil, + onHeartbeatFailure: HeartbeatFailureHandler? = nil, sleeper: @escaping Sleeper = { nanoseconds in guard nanoseconds > 0 else { return } try? await Task.sleep(nanoseconds: nanoseconds) @@ -1608,6 +1611,7 @@ actor WorkerAdminLiveSessionCoordinator { self.sleeper = sleeper self.fileLoader = fileLoader self.onHeartbeatResponse = onHeartbeatResponse + self.onHeartbeatFailure = onHeartbeatFailure } func start( @@ -1847,6 +1851,7 @@ actor WorkerAdminLiveSessionCoordinator { telemetry: telemetry ) } catch { + let message = error.localizedDescription WorkerLiveLogger.log( "heartbeat_result", sessionID: sessionID, @@ -1854,9 +1859,10 @@ actor WorkerAdminLiveSessionCoordinator { bucket: lastFrameBucket, path: lastFramePath, uploadState: "active", - error: error.localizedDescription, + error: message, telemetry: telemetry ) + await onHeartbeatFailure?(sessionID, message) } } @@ -2761,6 +2767,7 @@ class StreamSessionViewModel: ObservableObject { private var didAttemptPendingRecordingRecovery = false private var shouldResumeAiSupportAfterBackOffice = false private var isLiveRoomHandoffInProgress = false + private var lastWorkerLiveHeartbeatFailureWarningAt: Date = .distantPast private var isDemoWorkerMode: Bool { let configuredCode = GeminiConfig.workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) @@ -3967,6 +3974,11 @@ class StreamSessionViewModel: ObservableObject { Task { @MainActor [weak self] in await self?.handleWorkerLiveHeartbeatResponse(response) } + }, + onHeartbeatFailure: { [weak self] sessionID, message in + Task { @MainActor [weak self] in + await self?.handleWorkerLiveHeartbeatFailure(sessionID: sessionID, message: message) + } } ) geminiLiveSpotter.configure(api: opsAPIClient) @@ -3983,6 +3995,7 @@ class StreamSessionViewModel: ObservableObject { lastGlassesAnalysisFrameQueuedAt = 0 hasLoggedRoomCreatedForSession = false hasLoggedRoomJoinedForSession = false + lastWorkerLiveHeartbeatFailureWarningAt = .distantPast if streamingMode == .iPhone { sopVideoRecorder = nil conversationAudioRecorder = ConversationAudioRecorder(sessionID: sessionId) @@ -5750,9 +5763,38 @@ class StreamSessionViewModel: ObservableObject { await workerAdminSync?.enqueueFrameUpload(data: jpegData) } + private func handleWorkerLiveHeartbeatFailure(sessionID: String, message: String) async { + guard sessionID == currentSopSessionId else { return } + + let now = Date() + guard now.timeIntervalSince(lastWorkerLiveHeartbeatFailureWarningAt) >= 5 else { return } + lastWorkerLiveHeartbeatFailureWarningAt = now + + setOperationsSyncWarning( + phase: "live_heartbeat", + message: "Live heartbeat failed; keeping active audio and handoff routes unchanged. \(message)" + ) + await WorkerTelemetry.shared.record( + "worker_live_heartbeat_failed_no_audio_teardown", + source: "ios_app", + stage: "non_authoritative_error", + sessionID: sessionID, + payload: [ + "error": message, + "gemini_active": geminiAssistant.isGeminiActive, + "gemini_audio_ready": geminiAssistant.isAudioReady, + "webrtc_active": webrtcViewModel.isActive, + "has_active_help_escalation": hasActiveHelpEscalation, + "support_mode_trusted": false + ] + ) + } + private func handleWorkerLiveHeartbeatResponse(_ response: WorkerLiveHeartbeatResponse) async { guard response.sessionID == currentSopSessionId else { return } + // Only successful heartbeat responses are authoritative enough to mutate + // handoff state. HTTP failures are handled above and never release audio. let humanConnected = response.shouldOpenLiveRoom || (response.supportMode == "back_office" && response.humanSupportStatus == "connected") From 6b9f42216312bea97906511fb6543599d31f6b8b Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 11 Jun 2026 14:22:42 -0500 Subject: [PATCH 09/11] Upgrade live room when support connects --- .../ViewModels/StreamSessionViewModel.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 44fafb14..1bf9a7c3 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -5805,8 +5805,24 @@ class StreamSessionViewModel: ObservableObject { if humanConnected { hasActiveHelpEscalation = true - if !webrtcViewModel.isActive { - helpStatusMessage = "Back office answered. Opening live video and audio..." + if !webrtcViewModel.isActive || !webrtcViewModel.isSupportMode { + let wasObservationRoom = webrtcViewModel.isActive && !webrtcViewModel.isSupportMode + helpStatusMessage = wasObservationRoom + ? "Back office answered. Upgrading live room to video and audio..." + : "Back office answered. Opening live video and audio..." + if wasObservationRoom { + await WorkerTelemetry.shared.record( + "worker_live_support_upgrade_from_observation", + source: "ios_app", + stage: "handoff", + sessionID: response.sessionID, + payload: [ + "previous_room_code_present": !webrtcViewModel.roomCode.isEmpty, + "support_mode": response.supportMode, + "human_support_status": response.humanSupportStatus + ] + ) + } await ensureLiveRoomSession() } else if !webrtcViewModel.roomCode.isEmpty { await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) From b338de25853c53d6258f9faac06d6490602cf410 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 11 Jun 2026 15:24:15 -0500 Subject: [PATCH 10/11] feat(audio): implement lease-gated handoff barrier, protocol keepalives, and background passthrough video muxing --- .../CameraAccess/Gemini/AudioManager.swift | 24 +- .../Gemini/GeminiLiveService.swift | 157 +++- .../Gemini/GeminiSessionViewModel.swift | 200 ++++- .../CameraAccess/Gemini/SopRelayClient.swift | 2 +- .../ViewModels/StreamSessionViewModel.swift | 775 +++++++++++++++--- .../CameraAccessTests/CameraAccessTests.swift | 133 ++- 6 files changed, 1095 insertions(+), 196 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift index 9bcf8480..8f426c51 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift @@ -561,26 +561,24 @@ final class AudioManager: @unchecked Sendable { func stopCapture() async { // AVAudioEngine graph teardown runs on a serial lifecycle queue so callers // can await the barrier without blocking the MainActor on audio hardware. - await withCheckedContinuation { (continuation: CheckedContinuation) in + let lease = await withCheckedContinuation { (continuation: CheckedContinuation) in audioLifecycleQueue.async { [weak self] in guard let self else { - continuation.resume() + continuation.resume(returning: nil) return } + let lease = self.audioRouteLease + self.audioRouteLease = nil self.tearDownEngineGraphOnAudioLifecycleQueue(flushPendingAudio: true) { self.removeObservers() - continuation.resume() + continuation.resume(returning: lease) } } } - let lease = audioRouteLease - audioRouteLease = nil if let lease { - await WorkerAudioRouteCoordinator.shared.release(lease: lease) { [weak self] in - await self?.waitForAudioGraphClean() - } + await WorkerAudioRouteCoordinator.shared.release(lease: lease) } } @@ -743,18 +741,18 @@ final class AudioManager: @unchecked Sendable { audioGraphGeneration &+= 1 isCapturing = false + let inputNode = audioEngine.inputNode audioEngine.stop() if isInputTapInstalled { - audioEngine.inputNode.removeTap(onBus: 0) + inputNode.removeTap(onBus: 0) isInputTapInstalled = false } - if playerNode.isPlaying { - playerNode.stop() - } - if isPlayerNodeAttached { + if playerNode.isPlaying { + playerNode.stop() + } audioEngine.disconnectNodeOutput(playerNode) audioEngine.detach(playerNode) isPlayerNodeAttached = false diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift index 6ee41683..6f7d0f9d 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift @@ -10,6 +10,32 @@ enum GeminiConnectionState: Equatable { case error(String) } +enum GeminiRecoverableDisconnectReason: Equatable { + case socketClosed(String) + case socketError(String) + case receiveError(String) + case sendError(String) + case pingError(String) + case goAway(seconds: Int) + + var message: String { + switch self { + case .socketClosed(let message): + return message + case .socketError(let message): + return message + case .receiveError(let message): + return message + case .sendError(let message): + return message + case .pingError(let message): + return message + case .goAway(let seconds): + return "Server closing (time left: \(seconds)s)" + } + } +} + @MainActor class GeminiLiveService: ObservableObject { @Published var connectionState: GeminiConnectionState = .disconnected @@ -23,6 +49,7 @@ class GeminiLiveService: ObservableObject { var onOutputTranscription: ((String) -> Void)? var onSocketOpened: (() -> Void)? var onSocketClosed: ((String?) -> Void)? + var onRecoverableDisconnect: ((GeminiRecoverableDisconnectReason) -> Void)? // Latency tracking private var lastUserSpeechEnd: Date? @@ -30,6 +57,7 @@ class GeminiLiveService: ObservableObject { private var webSocketTask: URLSessionWebSocketTask? private var receiveTask: Task? + private var pingTask: Task? private var connectContinuation: CheckedContinuation? private var closeWaitContinuation: CheckedContinuation? private let delegate = WebSocketDelegate() @@ -40,6 +68,10 @@ class GeminiLiveService: ObservableObject { private var setupModel: String = GeminiConfig.model private var videoFrameSendCount: Int64 = 0 private var videoFrameStatsWindowStart = CACurrentMediaTime() + private var connectionGeneration = 0 + private var isClosingIntentionally = false + private var didNotifyRecoverableDisconnect = false + private let keepaliveIntervalNanoseconds: UInt64 = 15_000_000_000 var lastVideoFrameBase64: String? { latestVideoFrameBase64 } @@ -61,6 +93,11 @@ class GeminiLiveService: ObservableObject { setupSystemInstruction = resolvedSystemInstruction(systemInstruction) setupModel = credential.model connectionState = .connecting + connectionGeneration += 1 + let generation = connectionGeneration + isClosingIntentionally = false + didNotifyRecoverableDisconnect = false + stopKeepalive() let result = await withCheckedContinuation { (continuation: CheckedContinuation) in self.connectContinuation = continuation @@ -68,6 +105,7 @@ class GeminiLiveService: ObservableObject { self.delegate.onOpen = { [weak self] protocol_ in guard let self else { return } Task { @MainActor in + guard self.connectionGeneration == generation else { return } Task { await WorkerTelemetry.shared.record( "gemini_socket_open", @@ -78,6 +116,7 @@ class GeminiLiveService: ObservableObject { } self.onSocketOpened?() self.connectionState = .settingUp + self.startKeepalive(generation: generation) self.sendSetupMessage() self.startReceiving() } @@ -87,6 +126,7 @@ class GeminiLiveService: ObservableObject { guard let self else { return } let reasonStr = reason.flatMap { String(data: $0, encoding: .utf8) } ?? "no reason" Task { @MainActor in + guard self.connectionGeneration == generation else { return } Task { await WorkerTelemetry.shared.record( "gemini_socket_closed", @@ -98,12 +138,16 @@ class GeminiLiveService: ObservableObject { ] ) } - self.resolveConnect(success: false) - self.connectionState = .disconnected - self.isModelSpeaking = false - self.resolveCloseWait() - self.onSocketClosed?("Connection closed (code \(code.rawValue): \(reasonStr))") - self.onDisconnected?("Connection closed (code \(code.rawValue): \(reasonStr))") + let message = "Connection closed (code \(code.rawValue): \(reasonStr))" + if self.isClosingIntentionally || code == .normalClosure { + self.stopKeepalive() + self.resolveConnect(success: false) + self.connectionState = .disconnected + self.isModelSpeaking = false + self.resolveCloseWait() + return + } + self.notifyRecoverableDisconnect(.socketClosed(message), state: .disconnected) } } @@ -111,6 +155,7 @@ class GeminiLiveService: ObservableObject { guard let self else { return } let msg = error?.localizedDescription ?? "Unknown error" Task { @MainActor in + guard self.connectionGeneration == generation else { return } Task { await WorkerTelemetry.shared.record( "gemini_socket_error", @@ -119,12 +164,15 @@ class GeminiLiveService: ObservableObject { payload: ["error": msg] ) } - self.resolveConnect(success: false) - self.connectionState = .error(msg) - self.isModelSpeaking = false - self.resolveCloseWait() - self.onSocketClosed?(msg) - self.onDisconnected?(msg) + guard !self.isClosingIntentionally else { + self.stopKeepalive() + self.resolveConnect(success: false) + self.connectionState = .disconnected + self.isModelSpeaking = false + self.resolveCloseWait() + return + } + self.notifyRecoverableDisconnect(.socketError(msg), state: .error(msg)) } } @@ -135,6 +183,7 @@ class GeminiLiveService: ObservableObject { Task { try? await Task.sleep(nanoseconds: 15_000_000_000) await MainActor.run { + guard self.connectionGeneration == generation else { return } self.resolveConnect(success: false) if self.connectionState == .connecting || self.connectionState == .settingUp { self.connectionState = .error("Connection timed out") @@ -147,6 +196,9 @@ class GeminiLiveService: ObservableObject { } func disconnect() { + isClosingIntentionally = true + connectionGeneration += 1 + stopKeepalive() receiveTask?.cancel() receiveTask = nil webSocketTask?.cancel(with: .normalClosure, reason: nil) @@ -156,6 +208,7 @@ class GeminiLiveService: ObservableObject { delegate.onError = nil onSocketOpened = nil onSocketClosed = nil + onRecoverableDisconnect = nil connectionState = .disconnected isModelSpeaking = false resolveConnect(success: false) @@ -163,6 +216,8 @@ class GeminiLiveService: ObservableObject { } func disconnectAndWaitForClose(timeout: TimeInterval = 1.0) async { + isClosingIntentionally = true + stopKeepalive() receiveTask?.cancel() receiveTask = nil @@ -193,6 +248,7 @@ class GeminiLiveService: ObservableObject { delegate.onError = nil onSocketOpened = nil onSocketClosed = nil + onRecoverableDisconnect = nil connectionState = .disconnected isModelSpeaking = false resolveConnect(success: false) @@ -315,6 +371,58 @@ class GeminiLiveService: ObservableObject { cont.resume() } + private func startKeepalive(generation: Int) { + stopKeepalive() + let interval = keepaliveIntervalNanoseconds + pingTask = Task { [weak self] in + while !Task.isCancelled { + try? await Task.sleep(nanoseconds: interval) + guard !Task.isCancelled else { break } + await MainActor.run { + self?.sendKeepalivePingIfCurrent(generation: generation) + } + } + } + } + + private func stopKeepalive() { + pingTask?.cancel() + pingTask = nil + } + + private func sendKeepalivePingIfCurrent(generation: Int) { + guard connectionGeneration == generation, + !isClosingIntentionally, + let task = webSocketTask else { + return + } + + task.sendPing { [weak self] error in + guard let error else { return } + Task { @MainActor in + guard let self, self.connectionGeneration == generation else { return } + self.notifyRecoverableDisconnect( + .pingError(error.localizedDescription), + state: .disconnected + ) + } + } + } + + private func notifyRecoverableDisconnect( + _ reason: GeminiRecoverableDisconnectReason, + state: GeminiConnectionState + ) { + guard !isClosingIntentionally, !didNotifyRecoverableDisconnect else { return } + didNotifyRecoverableDisconnect = true + stopKeepalive() + resolveConnect(success: false) + connectionState = state + isModelSpeaking = false + resolveCloseWait() + onRecoverableDisconnect?(reason) + } + private func resolvedSystemInstruction(_ override: String?) -> String { let candidate = override?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !candidate.isEmpty { @@ -371,12 +479,10 @@ class GeminiLiveService: ObservableObject { guard let self, let error else { return } Task { @MainActor in NSLog("[Gemini] WebSocket send failed: %@", error.localizedDescription) - self.resolveConnect(success: false) - self.connectionState = .error("WebSocket send failed: \(error.localizedDescription)") - self.isModelSpeaking = false - self.resolveCloseWait() - self.onSocketClosed?(error.localizedDescription) - self.onDisconnected?(error.localizedDescription) + self.notifyRecoverableDisconnect( + .sendError(error.localizedDescription), + state: .error("WebSocket send failed: \(error.localizedDescription)") + ) } } } @@ -402,11 +508,10 @@ class GeminiLiveService: ObservableObject { if !Task.isCancelled { let reason = error.localizedDescription await MainActor.run { - self.resolveConnect(success: false) - self.connectionState = .disconnected - self.isModelSpeaking = false - self.resolveCloseWait() - self.onDisconnected?(reason) + self.notifyRecoverableDisconnect( + .receiveError(reason), + state: .disconnected + ) } } break @@ -455,11 +560,7 @@ class GeminiLiveService: ObservableObject { if let goAway = json["goAway"] as? [String: Any] { let timeLeft = goAway["timeLeft"] as? [String: Any] let seconds = timeLeft?["seconds"] as? Int ?? 0 - connectionState = .disconnected - isModelSpeaking = false - resolveCloseWait() - onSocketClosed?("Server closing (time left: \(seconds)s)") - onDisconnected?("Server closing (time left: \(seconds)s)") + notifyRecoverableDisconnect(.goAway(seconds: seconds), state: .disconnected) return } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift index 1864a0af..9b4caca0 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift @@ -19,6 +19,12 @@ class GeminiSessionViewModel: ObservableObject { let provider: String? } + private enum GeminiSessionIntent { + case idle + case active + case humanSupport + } + private let geminiService = GeminiLiveService() private let audioManager = AudioManager() private var lastVideoFrameTime: Date = .distantPast @@ -29,6 +35,11 @@ class GeminiSessionViewModel: ObservableObject { private var currentSessionInstruction: String? private var lastDiagnosticsID: String? private var isStoppingSession = false + private var sessionIntent: GeminiSessionIntent = .idle + private var sessionGeneration = 0 + private var reconnectTask: Task? + private var autoReconnectAttempts = 0 + private let maxAutoReconnectAttempts = 3 var streamingMode: StreamingMode = .glasses var onInputCommand: ((String) -> Void)? @@ -46,12 +57,18 @@ class GeminiSessionViewModel: ObservableObject { return } + sessionGeneration += 1 + sessionIntent = .active + autoReconnectAttempts = 0 + reconnectTask?.cancel() + reconnectTask = nil errorMessage = nil userTranscript = "" aiTranscript = "" isStoppingSession = false guard let liveConfig = await resolveLiveSessionConfig(fallbackInstruction: systemInstruction) else { + sessionIntent = .idle errorMessage = "Gemini Live token unavailable. Check Admin AI Settings and the worker backend connection." return } @@ -66,6 +83,7 @@ class GeminiSessionViewModel: ObservableObject { do { try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) } catch { + sessionIntent = .idle await resetToIdle(message: "Audio setup failed: \(error.localizedDescription)") return } @@ -80,6 +98,7 @@ class GeminiSessionViewModel: ObservableObject { fallback: "Failed to connect to Gemini", diagnosticsID: liveConfig.diagnosticsID ) + sessionIntent = .idle await resetToIdle(message: message) await recordTelemetry( "gemini_live_connect_failed", @@ -95,6 +114,7 @@ class GeminiSessionViewModel: ObservableObject { do { try audioManager.startCapture() } catch { + sessionIntent = .idle await resetToIdle(message: "Mic capture failed: \(error.localizedDescription)") return } @@ -113,12 +133,28 @@ class GeminiSessionViewModel: ObservableObject { } func stopSession() async { + sessionIntent = .idle + sessionGeneration += 1 + reconnectTask?.cancel() + reconnectTask = nil + await resetToIdle(message: nil) + } + + func stopSessionForHumanSupportHandoff() async { + sessionIntent = .humanSupport + sessionGeneration += 1 + reconnectTask?.cancel() + reconnectTask = nil await resetToIdle(message: nil) } func refreshSessionInstruction(_ systemInstruction: String?) async { guard isGeminiActive else { return } guard let liveConfig = await resolveLiveSessionConfig(fallbackInstruction: systemInstruction) else { + sessionIntent = .idle + sessionGeneration += 1 + reconnectTask?.cancel() + reconnectTask = nil await resetToIdle(message: "Gemini Live token refresh failed. Check Admin AI Settings and try again.") return } @@ -192,19 +228,24 @@ class GeminiSessionViewModel: ObservableObject { } } + geminiService.onRecoverableDisconnect = { [weak self] reason in + guard let self else { return } + Task { @MainActor in + await self.handleRecoverableDisconnect(reason) + } + } + geminiService.onDisconnected = { [weak self] reason in guard let self else { return } Task { @MainActor in - guard self.isGeminiActive, !self.isStoppingSession else { return } - await self.resetToIdle(message: "Gemini connection lost: \(reason ?? "Unknown error")") + await self.handleFatalDisconnect("Gemini connection lost: \(reason ?? "Unknown error")") } } geminiService.onSocketClosed = { [weak self] reason in guard let self else { return } Task { @MainActor in - guard self.isGeminiActive, !self.isStoppingSession else { return } - await self.resetToIdle(message: "Gemini socket closed: \(reason ?? "Unknown error")") + await self.handleFatalDisconnect("Gemini socket closed: \(reason ?? "Unknown error")") } } } @@ -222,7 +263,108 @@ class GeminiSessionViewModel: ObservableObject { } } - private func reconnectTransport(with liveConfig: LiveSessionConfig) async { + private func handleFatalDisconnect(_ message: String) async { + guard isGeminiActive, !isStoppingSession else { return } + sessionIntent = .idle + sessionGeneration += 1 + reconnectTask?.cancel() + reconnectTask = nil + await resetToIdle(message: message) + } + + private func handleRecoverableDisconnect(_ reason: GeminiRecoverableDisconnectReason) async { + guard isGeminiActive, !isStoppingSession, sessionIntent == .active else { return } + isAudioReady = false + isModelSpeaking = false + + let generation = sessionGeneration + guard reconnectTask == nil else { return } + reconnectTask = Task { @MainActor [weak self] in + await self?.runAutoReconnect(reason: reason, generation: generation) + } + } + + private func shouldContinueAutoReconnect(generation: Int) -> Bool { + sessionIntent == .active && sessionGeneration == generation + } + + private func shouldUseReconnectGeneration(_ generation: Int?) -> Bool { + guard let generation else { return true } + return shouldContinueAutoReconnect(generation: generation) + } + + private func runAutoReconnect( + reason: GeminiRecoverableDisconnectReason, + generation: Int + ) async { + defer { + if sessionGeneration == generation { + reconnectTask = nil + } + } + + while autoReconnectAttempts < maxAutoReconnectAttempts { + guard shouldContinueAutoReconnect(generation: generation), !Task.isCancelled else { return } + autoReconnectAttempts += 1 + let attempt = autoReconnectAttempts + let delayNanoseconds = UInt64(Double(attempt) * 750_000_000) + + try? await Task.sleep(nanoseconds: delayNanoseconds) + guard shouldContinueAutoReconnect(generation: generation), !Task.isCancelled else { return } + + await recordTelemetry( + "gemini_live_auto_reconnect_attempt", + stage: "retrying", + payload: [ + "attempt": attempt, + "max_attempts": maxAutoReconnectAttempts, + "reason": reason.message + ] + ) + + let fallbackInstruction = currentSessionInstruction + guard let liveConfig = await resolveLiveSessionConfig(fallbackInstruction: fallbackInstruction) else { + continue + } + guard shouldContinueAutoReconnect(generation: generation), !Task.isCancelled else { return } + + let didReconnect = await reconnectTransport( + with: liveConfig, + preserveTranscripts: true, + resetOnFailure: false, + requiredGeneration: generation + ) + guard shouldContinueAutoReconnect(generation: generation), !Task.isCancelled else { return } + + if didReconnect { + autoReconnectAttempts = 0 + await recordTelemetry( + "gemini_live_auto_reconnect_succeeded", + stage: "ready", + payload: [ + "attempt": attempt, + "reason": reason.message, + "diagnostics_id": liveConfig.diagnosticsID ?? NSNull() + ] + ) + return + } + } + + guard shouldContinueAutoReconnect(generation: generation) else { return } + reconnectTask = nil + sessionIntent = .idle + sessionGeneration += 1 + await resetToIdle(message: "Gemini connection lost: \(reason.message)") + } + + @discardableResult + private func reconnectTransport( + with liveConfig: LiveSessionConfig, + preserveTranscripts: Bool = false, + resetOnFailure: Bool = true, + requiredGeneration: Int? = nil + ) async -> Bool { let wasActive = isGeminiActive isGeminiActive = false isAudioReady = false @@ -237,11 +379,14 @@ class GeminiSessionViewModel: ObservableObject { stateObservation = nil isStoppingSession = false - guard wasActive else { return } + guard wasActive || shouldUseReconnectGeneration(requiredGeneration) else { return false } + guard shouldUseReconnectGeneration(requiredGeneration) else { return false } errorMessage = nil - userTranscript = "" - aiTranscript = "" + if !preserveTranscripts { + userTranscript = "" + aiTranscript = "" + } currentLiveCredential = liveConfig.credential currentSessionInstruction = liveConfig.systemInstruction lastDiagnosticsID = liveConfig.diagnosticsID @@ -252,31 +397,54 @@ class GeminiSessionViewModel: ObservableObject { do { try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) } catch { - await resetToIdle(message: "Audio setup failed: \(error.localizedDescription)") - return + if resetOnFailure { + await resetToIdle(message: "Audio setup failed: \(error.localizedDescription)") + } else { + errorMessage = "Audio setup failed: \(error.localizedDescription)" + } + return false } + guard shouldUseReconnectGeneration(requiredGeneration) else { return false } let setupOk = await geminiService.connect( systemInstruction: liveConfig.systemInstruction, credential: liveConfig.credential ) guard setupOk else { - await resetToIdle(message: liveConnectionError( + let message = liveConnectionError( fallback: "Failed to reconnect to Gemini", diagnosticsID: liveConfig.diagnosticsID - )) - return + ) + if resetOnFailure { + await resetToIdle(message: message) + } else { + errorMessage = message + await geminiService.disconnectAndWaitForClose(timeout: 1.0) + } + return false + } + guard shouldUseReconnectGeneration(requiredGeneration) else { + await geminiService.disconnectAndWaitForClose(timeout: 1.0) + return false } do { try audioManager.startCapture() } catch { - await resetToIdle(message: "Mic capture failed: \(error.localizedDescription)") - return + if resetOnFailure { + await resetToIdle(message: "Mic capture failed: \(error.localizedDescription)") + } else { + errorMessage = "Mic capture failed: \(error.localizedDescription)" + } + return false + } + guard shouldUseReconnectGeneration(requiredGeneration) else { + return false } isGeminiActive = true isAudioReady = true + return true } private func resolveLiveSessionConfig(fallbackInstruction: String?) async -> LiveSessionConfig? { @@ -373,6 +541,7 @@ class GeminiSessionViewModel: ObservableObject { isGeminiActive = false isAudioReady = false isModelSpeaking = false + autoReconnectAttempts = 0 stateObservation?.cancel() stateObservation = nil clearGeminiCallbacks() @@ -395,6 +564,7 @@ class GeminiSessionViewModel: ObservableObject { private func clearGeminiCallbacks() { geminiService.onDisconnected = nil geminiService.onSocketClosed = nil + geminiService.onRecoverableDisconnect = nil geminiService.onSocketOpened = nil geminiService.onAudioReceived = nil geminiService.onInterrupted = nil diff --git a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift index 32fec78b..2ed9827e 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift @@ -1435,7 +1435,7 @@ struct WorkerLiveHeartbeatResponse: Decodable, Equatable { } } -struct WorkerMediaUploadTarget: Decodable, Equatable { +struct WorkerMediaUploadTarget: Decodable, Equatable, Sendable { let assetID: String let bucket: String let path: String diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 1bf9a7c3..c7dcf0b6 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -23,6 +23,7 @@ import AVFoundation import Combine import Speech import SwiftUI +import UIKit import VideoToolbox enum StreamingStatus { @@ -760,7 +761,7 @@ private enum ConversationAudioMuxer { try? FileManager.default.removeItem(at: outputURL) guard let exporter = AVAssetExportSession( asset: composition, - presetName: AVAssetExportPresetHighestQuality + presetName: AVAssetExportPresetPassthrough ) else { continuation.resume(returning: nil) return @@ -775,6 +776,65 @@ private enum ConversationAudioMuxer { } } +private enum SopMediaBackgroundTask { + static func begin(name: String) async -> UIBackgroundTaskIdentifier { + await MainActor.run { + UIApplication.shared.beginBackgroundTask(withName: name) { + NSLog("[SOPRecorder] Background media finalize task expired") + } + } + } + + static func end(_ identifier: UIBackgroundTaskIdentifier) async { + guard identifier != .invalid else { return } + await MainActor.run { + UIApplication.shared.endBackgroundTask(identifier) + } + } +} + +private enum SopSessionMediaFinalizer { + static func finishRecording( + wasIPhoneRecording: Bool, + iPhoneCameraManager: IPhoneCameraManager?, + conversationAudioRecorder: ConversationAudioRecorder?, + sopVideoRecorder: SopVideoRecorder? + ) async -> URL? { + if wasIPhoneRecording { + let phoneVideoURL = await iPhoneCameraManager?.stopRecording() + let conversationAudioURL = await conversationAudioRecorder?.finishAudioFile() + iPhoneCameraManager?.stop() + + guard let phoneVideoURL else { + return nil + } + + guard let conversationAudioURL else { + return phoneVideoURL + } + + let mixedURL = phoneVideoURL + .deletingLastPathComponent() + .appendingPathComponent(phoneVideoURL.deletingPathExtension().lastPathComponent + "_mixed") + .appendingPathExtension("mp4") + if let muxedURL = await ConversationAudioMuxer.mux( + videoURL: phoneVideoURL, + audioURL: conversationAudioURL, + outputURL: mixedURL + ) { + try? FileManager.default.removeItem(at: phoneVideoURL) + try? FileManager.default.removeItem(at: conversationAudioURL) + return muxedURL + } + + NSLog("[SOPRecorder] iPhone mixed audio mux failed; returning phone recording") + return phoneVideoURL + } + + return await sopVideoRecorder?.finishRecording() + } +} + private final class SopVideoRecorder: @unchecked Sendable { private enum AudioTrackKind: String { case input = "worker_input" @@ -1549,7 +1609,7 @@ private enum WorkerLiveLogger { } } -struct WorkerMediaUploadResult: Equatable { +struct WorkerMediaUploadResult: Equatable, Sendable { let assetType: String let assetID: String? let bucket: String? @@ -1561,6 +1621,15 @@ struct WorkerMediaUploadResult: Equatable { var succeeded: Bool { uploadState == "uploaded" } + + var isPending: Bool { + uploadState == "pending" + } +} + +struct WorkerPreparedMediaUpload: Equatable, Sendable { + let target: WorkerMediaUploadTarget + let result: WorkerMediaUploadResult } actor WorkerAdminLiveSessionCoordinator { @@ -1704,6 +1773,90 @@ actor WorkerAdminLiveSessionCoordinator { from fileURL: URL?, source: String = "session-recording" ) async -> WorkerMediaUploadResult { + guard let preparedUpload = await prepareVideoRecordingUpload(source: source) else { + return videoUploadFailureResult(errorMessage: "Recording upload target could not be prepared.") + } + + return await uploadPreparedVideoRecording(from: fileURL, preparedUpload: preparedUpload) + } + + func prepareVideoRecordingUpload( + source: String = "session-recording" + ) async -> WorkerPreparedMediaUpload? { + guard let sessionID else { + return nil + } + + let targetStartedAt = CACurrentMediaTime() + + do { + let target = try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetType: "video", + byteSize: 0, + uploadState: "pending" + ) { + try await api.requestWorkerMediaUploadTarget( + sessionID: sessionID, + assetType: "video", + filename: "recording.mp4", + contentType: "video/mp4", + byteSize: 0, + source: source + ) + } + + WorkerLiveLogger.log( + "video_upload_target", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: "video", + bucket: target.bucket, + path: target.path, + byteSize: 0, + uploadState: "pending", + durationMs: (CACurrentMediaTime() - targetStartedAt) * 1000, + telemetry: telemetry + ) + + return WorkerPreparedMediaUpload( + target: target, + result: WorkerMediaUploadResult( + assetType: "video", + assetID: target.assetID, + bucket: target.bucket, + path: target.path, + byteSize: 0, + uploadState: "pending", + errorMessage: nil + ) + ) + } catch { + WorkerLiveLogger.log( + "video_upload_target_failure", + sessionID: sessionID, + roomCode: roomCode, + assetType: "video", + byteSize: 0, + uploadState: "failed", + error: error.localizedDescription, + durationMs: (CACurrentMediaTime() - targetStartedAt) * 1000, + telemetry: telemetry + ) + return nil + } + } + + func uploadPreparedVideoRecording( + from fileURL: URL?, + preparedUpload: WorkerPreparedMediaUpload + ) async -> WorkerMediaUploadResult { + guard let sessionID else { + return videoUploadFailureResult(errorMessage: "Recording upload session is missing.") + } + let byteSize: Int let data: Data? let missingDataError: String @@ -1728,50 +1881,54 @@ actor WorkerAdminLiveSessionCoordinator { "recording_missing", sessionID: sessionID, roomCode: roomCode, + assetID: preparedUpload.target.assetID, assetType: "video", + bucket: preparedUpload.target.bucket, + path: preparedUpload.target.path, byteSize: byteSize, uploadState: "failed", error: missingDataError, telemetry: telemetry ) - return WorkerMediaUploadResult( + + return await finalizeFailure( + logPrefix: "video", + sessionID: sessionID, assetType: "video", - assetID: nil, - bucket: nil, - path: nil, + target: preparedUpload.target, byteSize: byteSize, - uploadState: "failed", errorMessage: missingDataError ) } - return await uploadAsset( + return await uploadPreparedAsset( assetType: "video", - filename: "recording.mp4", contentType: "video/mp4", + target: preparedUpload.target, data: data, byteSize: byteSize, - missingDataError: missingDataError, - source: source + missingDataError: missingDataError ) } func completeSession( - videoFileURL: URL?, - videoSource: String = "session-recording", + pendingVideoUpload: WorkerPreparedMediaUpload?, onBeforeMarkEnded: () async -> Void ) async -> WorkerMediaUploadResult { + let result = pendingVideoUpload?.result + ?? videoUploadFailureResult(errorMessage: "Recording upload target could not be prepared.") + queuedFrameData = nil frameUploadTask?.cancel() frameUploadTask = nil + heartbeatTask?.cancel() + heartbeatTask = nil - let result = await uploadVideoRecording(from: videoFileURL, source: videoSource) - await sendHeartbeat() await onBeforeMarkEnded() await telemetry?.record( "session_end_requested", source: "ios_app", - stage: result.succeeded ? "uploaded" : "failed", + stage: result.isPending ? "pending" : result.succeeded ? "uploaded" : "failed", sessionID: sessionID, payload: [ "video_upload_state": result.uploadState, @@ -1779,13 +1936,14 @@ actor WorkerAdminLiveSessionCoordinator { "error": result.errorMessage ?? NSNull() ] ) - await telemetry?.flushAndStop() - - heartbeatTask?.cancel() - heartbeatTask = nil + await telemetry?.flush() return result } + func flushTelemetryAndStop() async { + await telemetry?.flushAndStop() + } + func stop() async { queuedFrameData = nil frameUploadTask?.cancel() @@ -1898,10 +2056,10 @@ actor WorkerAdminLiveSessionCoordinator { } } - private func uploadAsset( - assetType: String, - filename: String, - contentType: String, + private func uploadAsset( + assetType: String, + filename: String, + contentType: String, data: Data?, byteSize: Int, missingDataError: String, @@ -2132,10 +2290,203 @@ actor WorkerAdminLiveSessionCoordinator { byteSize: byteSize, uploadState: "failed", errorMessage: error.localizedDescription + ) + } + } + + private func uploadPreparedAsset( + assetType: String, + contentType: String, + target: WorkerMediaUploadTarget, + data: Data?, + byteSize: Int, + missingDataError: String + ) async -> WorkerMediaUploadResult { + guard let sessionID else { + return WorkerMediaUploadResult( + assetType: assetType, + assetID: target.assetID, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + errorMessage: "Session ID missing." + ) + } + + let logPrefix = assetType == "frame" ? "frame" : "video" + let assetUploadStartedAt = CACurrentMediaTime() + + guard let data, !data.isEmpty else { + WorkerLiveLogger.log( + "\(logPrefix)_upload_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: missingDataError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: missingDataError + ) + } + + do { + let binaryUploadStartedAt = CACurrentMediaTime() + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "pending" + ) { + try await api.uploadBinary(to: target, data: data, contentType: contentType) + } + + WorkerLiveLogger.log( + "\(logPrefix)_upload_success", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "pending", + durationMs: (CACurrentMediaTime() - binaryUploadStartedAt) * 1000, + metricValue: Double(byteSize), + metricUnit: "bytes", + telemetry: telemetry + ) + + do { + let finalizeStartedAt = CACurrentMediaTime() + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded" + ) { + try await api.finalizeWorkerMediaUpload( + WorkerMediaFinalizeRequest( + assetID: target.assetID, + sessionID: sessionID, + bucket: target.bucket, + path: target.path, + status: "uploaded", + byteSize: byteSize, + error: nil + ) + ) + } + + WorkerLiveLogger.log( + "\(logPrefix)_finalize_success", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded", + durationMs: (CACurrentMediaTime() - finalizeStartedAt) * 1000, + metricValue: Double(byteSize), + metricUnit: "bytes", + telemetry: telemetry + ) + + return WorkerMediaUploadResult( + assetType: assetType, + assetID: target.assetID, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded", + errorMessage: nil + ) + } catch { + let finalizeError = "Finalize uploaded failed: \(error.localizedDescription)" + WorkerLiveLogger.log( + "\(logPrefix)_finalize_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded", + error: finalizeError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: finalizeError + ) + } + } catch { + let uploadError = error.localizedDescription + WorkerLiveLogger.log( + "\(logPrefix)_upload_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: uploadError, + durationMs: (CACurrentMediaTime() - assetUploadStartedAt) * 1000, + telemetry: telemetry + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: uploadError ) } } + private func videoUploadFailureResult(errorMessage: String) -> WorkerMediaUploadResult { + WorkerMediaUploadResult( + assetType: "video", + assetID: nil, + bucket: nil, + path: nil, + byteSize: 0, + uploadState: "failed", + errorMessage: errorMessage + ) + } + private func finalizeFailure( logPrefix: String, sessionID: String, @@ -2325,6 +2676,18 @@ private struct PendingWorkerRecording: Codable, Equatable { let filePath: String } +private enum PendingWorkerRecordingStore { + static func remember(defaultsKey: String, sessionID: String, fileURL: URL) { + let pending = PendingWorkerRecording(sessionID: sessionID, filePath: fileURL.path) + guard let encoded = try? JSONEncoder().encode(pending) else { return } + UserDefaults.standard.set(encoded, forKey: defaultsKey) + } + + static func clear(defaultsKey: String) { + UserDefaults.standard.removeObject(forKey: defaultsKey) + } +} + private struct IPhoneAnalysisFrameEnvelope: @unchecked Sendable { let image: UIImage let shouldRecordAudit: Bool @@ -2762,6 +3125,15 @@ class StreamSessionViewModel: ObservableObject { private var locallyCompletedSopsByPackageKey: [String: Set] = [:] private var lastLivePreviewSyncAt: Date = .distantPast private var hasActiveHelpEscalation = false + private struct LiveRoomSyncSnapshot: Equatable { + let roomCode: String + let helpRequested: Bool + let backOfficeConnected: Bool + } + private var lastLiveRoomSyncSnapshot: LiveRoomSyncSnapshot? + private var lastLiveRoomSyncAt: Date = .distantPast + private let liveRoomRedundantSyncThrottleInterval: TimeInterval = 5 + private var hasReceivedBackOfficeConnectedHandshake = false private var hasLoggedRoomCreatedForSession = false private var hasLoggedRoomJoinedForSession = false private var didAttemptPendingRecordingRecovery = false @@ -3202,6 +3574,7 @@ class StreamSessionViewModel: ObservableObject { } else { await workerAdminSync?.stop() workerAdminSync = nil + resetLiveRoomSyncSnapshot() } if webrtcViewModel.isActive { @@ -3992,6 +4365,7 @@ class StreamSessionViewModel: ObservableObject { isStepValidationRunning = false proofImagesByTargetID = [:] lastLivePreviewSyncAt = .distantPast + resetLiveRoomSyncSnapshot() lastGlassesAnalysisFrameQueuedAt = 0 hasLoggedRoomCreatedForSession = false hasLoggedRoomJoinedForSession = false @@ -4102,6 +4476,7 @@ class StreamSessionViewModel: ObservableObject { helpStatusMessage = "Supervisor room closed." hasActiveHelpEscalation = false shouldResumeAiSupportAfterBackOffice = false + resetLiveRoomSyncSnapshot() Task { @MainActor in await workerAdminSync?.updateHelpRequested(false) await patchActiveExecutionSession( @@ -4169,6 +4544,76 @@ class StreamSessionViewModel: ObservableObject { } } + private func launchBackgroundMediaFinalization( + sessionID: String, + workerAdminSync: WorkerAdminLiveSessionCoordinator?, + preparedUpload: WorkerPreparedMediaUpload?, + wasIPhoneRecording: Bool, + iPhoneCameraManager: IPhoneCameraManager?, + conversationAudioRecorder: ConversationAudioRecorder?, + sopVideoRecorder: SopVideoRecorder? + ) { + let pendingDefaultsKey = pendingRecordingDefaultsKey + + Task.detached(priority: .utility) { + let backgroundTaskID = await SopMediaBackgroundTask.begin(name: "SOP media finalize") + let recordedVideoURL = await SopSessionMediaFinalizer.finishRecording( + wasIPhoneRecording: wasIPhoneRecording, + iPhoneCameraManager: iPhoneCameraManager, + conversationAudioRecorder: conversationAudioRecorder, + sopVideoRecorder: sopVideoRecorder + ) + + let result: WorkerMediaUploadResult + if let workerAdminSync, let preparedUpload { + result = await workerAdminSync.uploadPreparedVideoRecording( + from: recordedVideoURL, + preparedUpload: preparedUpload + ) + } else { + result = WorkerMediaUploadResult( + assetType: "video", + assetID: preparedUpload?.result.assetID, + bucket: preparedUpload?.result.bucket, + path: preparedUpload?.result.path, + byteSize: 0, + uploadState: "failed", + errorMessage: workerAdminSync == nil + ? "Worker admin sync was unavailable during background media finalize." + : "Recording upload target could not be prepared." + ) + } + + if result.succeeded { + PendingWorkerRecordingStore.clear(defaultsKey: pendingDefaultsKey) + if let recordedVideoURL { + try? FileManager.default.removeItem(at: recordedVideoURL) + } + } else if let recordedVideoURL { + PendingWorkerRecordingStore.remember( + defaultsKey: pendingDefaultsKey, + sessionID: sessionID, + fileURL: recordedVideoURL + ) + } + + WorkerLiveLogger.log( + "session_media_background_finalize_completed", + sessionID: sessionID, + assetID: result.assetID, + assetType: result.assetType, + bucket: result.bucket, + path: result.path, + byteSize: result.byteSize, + uploadState: result.uploadState, + error: result.errorMessage + ) + + await workerAdminSync?.flushTelemetryAndStop() + await SopMediaBackgroundTask.end(backgroundTaskID) + } + } + func endAndShip(status: SopTerminationStatus, cancelCountdownTask: Bool = true) async { guard !isFinalizingAndShipping, isSopAuditRunning, currentSopSessionId != nil else { return } isFinalizingAndShipping = true @@ -4177,6 +4622,22 @@ class StreamSessionViewModel: ObservableObject { let syncedToBackend = activeExecutionSession != nil let completedSOP = selectedSOP let roomCodeAtEnd = webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode + let workerAdminSyncAtEnd = workerAdminSync + let wasIPhoneRecording = streamingMode == .iPhone + let recordingSource = wasIPhoneRecording ? "phone-recording" : "stream-capture" + let iPhoneCameraForFinalization = wasIPhoneRecording ? iPhoneCameraManager : nil + let conversationAudioRecorderForFinalization = conversationAudioRecorder + let sopVideoRecorderForFinalization = sopVideoRecorder + let proofImages = proofImagesByTargetID + let checklistPayload: [[String: Any]] = checklistItems.map { + [ + "name": $0.name, + "checked": $0.isChecked, + "source": $0.completionSource.rawValue + ] + } + let completedCount = checklistItems.filter(\.isChecked).count + let finalStepIndex = nextIncompleteStepIndex() WorkerLiveLogger.log( "session_end_requested", @@ -4187,72 +4648,34 @@ class StreamSessionViewModel: ObservableObject { isSopAuditRunning = false isDossierUploading = true - updateDossierPipelineStatus("Finalizing session media...", kind: .active) + updateDossierPipelineStatus("Registering replay upload...", kind: .active) if cancelCountdownTask { sopCountdownTask?.cancel() } sopCountdownTask = nil - if geminiAssistant.isGeminiActive { - await geminiAssistant.stopSession() - try? await Task.sleep(nanoseconds: 150_000_000) - } - - let wasIPhoneRecording = streamingMode == .iPhone - var recordedVideoURL: URL? if wasIPhoneRecording { - let phoneVideoURL = await iPhoneCameraManager?.stopRecording() - let conversationAudioURL = await conversationAudioRecorder?.finishAudioFile() - if let phoneVideoURL, let conversationAudioURL { - let mixedURL = phoneVideoURL - .deletingLastPathComponent() - .appendingPathComponent(phoneVideoURL.deletingPathExtension().lastPathComponent + "_mixed") - .appendingPathExtension("mp4") - if let muxedURL = await ConversationAudioMuxer.mux( - videoURL: phoneVideoURL, - audioURL: conversationAudioURL, - outputURL: mixedURL - ) { - recordedVideoURL = muxedURL - try? FileManager.default.removeItem(at: phoneVideoURL) - try? FileManager.default.removeItem(at: conversationAudioURL) - } else { - NSLog("[SOPRecorder] iPhone mixed audio mux failed; returning phone recording") - recordedVideoURL = phoneVideoURL - } - } else { - recordedVideoURL = phoneVideoURL - } - stopIPhoneSession() - } else { - await streamSession.stop() - if let videoRecorder = sopVideoRecorder { - recordedVideoURL = await videoRecorder.finishRecording() - } + resetIPhoneAnalysisLane() + iPhoneCameraManager = nil + iPhonePreviewSession = nil + currentVideoFrame = nil + hasReceivedFirstFrame = false + streamingStatus = .stopped + streamingMode = .glasses + geminiAssistant.streamingMode = .glasses } - - let proofImages = proofImagesByTargetID sopVideoRecorder = nil conversationAudioRecorder = nil proofImagesByTargetID = [:] - let checklistPayload: [[String: Any]] = checklistItems.map { - [ - "name": $0.name, - "checked": $0.isChecked, - "source": $0.completionSource.rawValue - ] - } - let completedCount = checklistItems.filter(\.isChecked).count - let finalStepIndex = nextIncompleteStepIndex() - await workerAdminSync?.updateCurrentStepIndex(finalStepIndex) - await workerAdminSync?.updateHelpRequested(false, sendImmediateHeartbeat: false) + await workerAdminSyncAtEnd?.updateCurrentStepIndex(finalStepIndex) + await workerAdminSyncAtEnd?.updateHelpRequested(false, sendImmediateHeartbeat: false) + let preparedVideoUpload = await workerAdminSyncAtEnd?.prepareVideoRecordingUpload(source: recordingSource) let videoUploadResult: WorkerMediaUploadResult - if let workerAdminSync { - videoUploadResult = await workerAdminSync.completeSession( - videoFileURL: recordedVideoURL, - videoSource: wasIPhoneRecording ? "phone-recording" : "stream-capture" + if let workerAdminSyncAtEnd { + videoUploadResult = await workerAdminSyncAtEnd.completeSession( + pendingVideoUpload: preparedVideoUpload ) { [weak self] in guard let self else { return } @@ -4270,13 +4693,13 @@ class StreamSessionViewModel: ObservableObject { await self.patchActiveExecutionSession( ExecutionSessionPatch( status: status == .allItemsChecked ? "completed" : "ended", - currentSopID: self.selectedSOP?.remoteID, + currentSopID: completedSOP?.remoteID, currentStepIndex: finalStepIndex, helpRequested: false, endedAt: ISO8601DateFormatter().string(from: Date()) ) ) - } else if recordedVideoURL == nil { + } else { self.updateDossierPipelineStatus("Execution ended locally. No backend session was active.", kind: .info) } } @@ -4303,19 +4726,35 @@ class StreamSessionViewModel: ObservableObject { ) await patchActiveExecutionSession( - ExecutionSessionPatch( - status: status == .allItemsChecked ? "completed" : "ended", - currentSopID: selectedSOP?.remoteID, - currentStepIndex: finalStepIndex, - helpRequested: false, - endedAt: ISO8601DateFormatter().string(from: Date()) + ExecutionSessionPatch( + status: status == .allItemsChecked ? "completed" : "ended", + currentSopID: completedSOP?.remoteID, + currentStepIndex: finalStepIndex, + helpRequested: false, + endedAt: ISO8601DateFormatter().string(from: Date()) + ) ) - ) - } else if recordedVideoURL == nil { + } else { updateDossierPipelineStatus("Execution ended locally. No backend session was active.", kind: .info) } } + if geminiAssistant.isGeminiActive { + await geminiAssistant.stopSession() + try? await Task.sleep(nanoseconds: 150_000_000) + } + + didAttemptPendingRecordingRecovery = false + launchBackgroundMediaFinalization( + sessionID: sessionID ?? UUID().uuidString, + workerAdminSync: workerAdminSyncAtEnd, + preparedUpload: preparedVideoUpload, + wasIPhoneRecording: wasIPhoneRecording, + iPhoneCameraManager: iPhoneCameraForFinalization, + conversationAudioRecorder: conversationAudioRecorderForFinalization, + sopVideoRecorder: sopVideoRecorderForFinalization + ) + if let activeExecutionSession { if let remoteSOPID = completedSOP?.remoteID { await createExecutionMemoryLink( @@ -4338,28 +4777,17 @@ class StreamSessionViewModel: ObservableObject { markPendingTaskComplete(completedSOP) } - if videoUploadResult.succeeded { - clearPendingRecording() - } else if let recordedVideoURL, let sessionID { - rememberPendingRecording(sessionID: sessionID, fileURL: recordedVideoURL) - didAttemptPendingRecordingRecovery = false - } - - if let recordedVideoURL, videoUploadResult.succeeded { - try? FileManager.default.removeItem(at: recordedVideoURL) - } - - if !videoUploadResult.succeeded { + if videoUploadResult.uploadState == "failed" { let errorMessage = videoUploadResult.errorMessage ?? "Video finalize failed." let recordingLabel = wasIPhoneRecording ? "Phone recording" : "Session recording" setCriticalOperationsSyncIssue( - phase: "media_finalize", - message: "\(recordingLabel) finalize failed: \(errorMessage)" + phase: "media_reservation", + message: "\(recordingLabel) upload could not be reserved: \(errorMessage)" ) - updateDossierPipelineStatus("\(recordingLabel) finalize failed.", kind: .error) + updateDossierPipelineStatus("\(recordingLabel) upload reservation failed.", kind: .error) } else { updateDossierPipelineStatus( - wasIPhoneRecording ? "Phone recording finalized." : "Session recording finalized.", + wasIPhoneRecording ? "Phone recording upload pending." : "Session recording upload pending.", kind: .success ) } @@ -4370,7 +4798,9 @@ class StreamSessionViewModel: ObservableObject { ShippedSessionRecord( timestamp: Date(), sopName: completedSOP?.name ?? "Unknown SOP", - status: videoUploadResult.succeeded ? "Replay ready" : "Finalize failed" + status: videoUploadResult.isPending + ? "Upload pending" + : videoUploadResult.succeeded ? "Replay ready" : "Finalize failed" ) ) @@ -4392,12 +4822,11 @@ class StreamSessionViewModel: ObservableObject { if webrtcViewModel.isActive { webrtcViewModel.stopSession() } - await geminiAssistant.stopSession() - await workerAdminSync?.stop() workerAdminSync = nil hasActiveHelpEscalation = false shouldResumeAiSupportAfterBackOffice = false + resetLiveRoomSyncSnapshot() activeExecutionSession = nil currentSopSessionId = nil activeCaptureSOP = nil @@ -4407,13 +4836,15 @@ class StreamSessionViewModel: ObservableObject { if canCloseCurrentPackage { packageClosureStatusMessage = "All required SOPs are complete. Close the package from NOW." } - sopAuditStatusMessage = videoUploadResult.succeeded + sopAuditStatusMessage = videoUploadResult.uploadState != "failed" ? (syncedToBackend ? "Execution synced" : "Execution uploaded") - : "Execution ended with media finalize issues" + : "Execution ended with media reservation issues" isSpotterInferenceInFlight = false shouldDismissCapture = true showShipSuccessToast = true - await startHomeCameraPreviewIfNeeded() + if !wasIPhoneRecording { + await startHomeCameraPreviewIfNeeded() + } successToastTask?.cancel() successToastTask = Task { @MainActor [weak self] in try? await Task.sleep(nanoseconds: 2_000_000_000) @@ -4654,13 +5085,15 @@ class StreamSessionViewModel: ObservableObject { } private func rememberPendingRecording(sessionID: String, fileURL: URL) { - let pending = PendingWorkerRecording(sessionID: sessionID, filePath: fileURL.path) - guard let encoded = try? JSONEncoder().encode(pending) else { return } - UserDefaults.standard.set(encoded, forKey: pendingRecordingDefaultsKey) + PendingWorkerRecordingStore.remember( + defaultsKey: pendingRecordingDefaultsKey, + sessionID: sessionID, + fileURL: fileURL + ) } private func clearPendingRecording() { - UserDefaults.standard.removeObject(forKey: pendingRecordingDefaultsKey) + PendingWorkerRecordingStore.clear(defaultsKey: pendingRecordingDefaultsKey) } private func loadPendingRecording() -> PendingWorkerRecording? { @@ -5785,11 +6218,30 @@ class StreamSessionViewModel: ObservableObject { "gemini_audio_ready": geminiAssistant.isAudioReady, "webrtc_active": webrtcViewModel.isActive, "has_active_help_escalation": hasActiveHelpEscalation, + "audio_route_lease_protected": true, + "av_audio_engine_state_protected": true, "support_mode_trusted": false ] ) } + private func isAuthoritativeHumanSupportEnd(_ response: WorkerLiveHeartbeatResponse) -> Bool { + guard response.supportMode == "ai", response.humanSupportStatus == "ended" else { + return false + } + + return hasActiveHelpEscalation || + shouldResumeAiSupportAfterBackOffice || + (webrtcViewModel.isActive && webrtcViewModel.isSupportMode) || + response.supportUpdatedAt != nil + } + + private func resetLiveRoomSyncSnapshot() { + lastLiveRoomSyncSnapshot = nil + lastLiveRoomSyncAt = .distantPast + hasReceivedBackOfficeConnectedHandshake = false + } + private func handleWorkerLiveHeartbeatResponse(_ response: WorkerLiveHeartbeatResponse) async { guard response.sessionID == currentSopSessionId else { return } @@ -5798,13 +6250,15 @@ class StreamSessionViewModel: ObservableObject { let humanConnected = response.shouldOpenLiveRoom || (response.supportMode == "back_office" && response.humanSupportStatus == "connected") - let humanEnded = + let reportedHumanEnded = response.supportMode == "ai" && response.humanSupportStatus == "ended" + let humanEnded = isAuthoritativeHumanSupportEnd(response) let humanRinging = response.supportMode == "handoff_requested" || response.humanSupportStatus == "ringing" if humanConnected { hasActiveHelpEscalation = true + hasReceivedBackOfficeConnectedHandshake = true if !webrtcViewModel.isActive || !webrtcViewModel.isSupportMode { let wasObservationRoom = webrtcViewModel.isActive && !webrtcViewModel.isSupportMode helpStatusMessage = wasObservationRoom @@ -5830,11 +6284,32 @@ class StreamSessionViewModel: ObservableObject { return } + if reportedHumanEnded && !humanEnded { + await WorkerTelemetry.shared.record( + "worker_live_support_end_ignored_non_authoritative", + source: "ios_app", + stage: "non_authoritative_heartbeat", + sessionID: response.sessionID, + payload: [ + "support_mode": response.supportMode, + "human_support_status": response.humanSupportStatus, + "webrtc_active": webrtcViewModel.isActive, + "webrtc_support_mode": webrtcViewModel.isSupportMode, + "has_active_help_escalation": hasActiveHelpEscalation, + "should_resume_ai": shouldResumeAiSupportAfterBackOffice, + "support_updated_at_present": response.supportUpdatedAt != nil, + "audio_route_lease_protected": true + ] + ) + return + } + if humanEnded { - if webrtcViewModel.isActive { + if webrtcViewModel.isActive && webrtcViewModel.isSupportMode { webrtcViewModel.stopSession() } hasActiveHelpEscalation = false + resetLiveRoomSyncSnapshot() await workerAdminSync?.updateHelpRequested(false, sendImmediateHeartbeat: false) helpStatusMessage = "Back office ended. AI support is available again." await ensureObservationLiveRoomSession() @@ -5866,7 +6341,7 @@ class StreamSessionViewModel: ObservableObject { let notes = helpRequestNotes.trimmingCharacters(in: .whitespacesAndNewlines) shouldResumeAiSupportAfterBackOffice = geminiAssistant.isGeminiActive if geminiAssistant.isGeminiActive { - await geminiAssistant.stopSession() + await geminiAssistant.stopSessionForHumanSupportHandoff() aiGuideStatusMessage = "AI guide paused while back office support is requested." sopAuditStatusMessage = aiGuideStatusMessage } @@ -5898,7 +6373,6 @@ class StreamSessionViewModel: ObservableObject { helpRequested: true ) ) - await workerAdminSync?.updateHelpRequested(true) helpStatusMessage = "Calling back office. Live video and audio will open after they answer." } catch { hasActiveHelpEscalation = false @@ -5912,20 +6386,65 @@ class StreamSessionViewModel: ObservableObject { } private func syncLiveRoomState(roomCode: String) async { - guard !roomCode.isEmpty else { return } - await workerAdminSync?.updateRoomCode(roomCode) - guard activeExecutionSession != nil else { - helpStatusMessage = liveRoomStatusMessage(localOnly: true, helpRequested: hasActiveHelpEscalation, roomCode: roomCode) + let normalizedRoomCode = roomCode.trimmingCharacters(in: .whitespacesAndNewlines) + guard !normalizedRoomCode.isEmpty else { return } + + let backOfficeConnected = + hasReceivedBackOfficeConnectedHandshake || + (hasActiveHelpEscalation && + webrtcViewModel.isActive && + webrtcViewModel.isSupportMode && + webrtcViewModel.connectionState == .connected) + let nextSnapshot = LiveRoomSyncSnapshot( + roomCode: normalizedRoomCode, + helpRequested: hasActiveHelpEscalation, + backOfficeConnected: backOfficeConnected + ) + let previousSnapshot = lastLiveRoomSyncSnapshot + let now = Date() + let isFirstRoomCodePublish = previousSnapshot?.roomCode == nil + let didChangeRoomCode = previousSnapshot?.roomCode != nil && + previousSnapshot?.roomCode != normalizedRoomCode + let didInitiallyRequestHelp = hasActiveHelpEscalation && + previousSnapshot?.helpRequested != true + let didInitiallyConnectBackOffice = backOfficeConnected && + previousSnapshot?.backOfficeConnected != true + let shouldSendImmediateHeartbeat = + isFirstRoomCodePublish || + didChangeRoomCode || + didInitiallyRequestHelp || + didInitiallyConnectBackOffice + let isRedundantSync = previousSnapshot == nextSnapshot + let didClearThrottle = + now.timeIntervalSince(lastLiveRoomSyncAt) >= liveRoomRedundantSyncThrottleInterval + + let localOnly = activeExecutionSession == nil + helpStatusMessage = liveRoomStatusMessage( + localOnly: localOnly, + helpRequested: hasActiveHelpEscalation, + roomCode: normalizedRoomCode + ) + + guard shouldSendImmediateHeartbeat || !isRedundantSync || didClearThrottle else { return } - helpStatusMessage = liveRoomStatusMessage(localOnly: false, helpRequested: hasActiveHelpEscalation, roomCode: roomCode) - await patchActiveExecutionSession( - ExecutionSessionPatch( - helpRequested: hasActiveHelpEscalation, - webrtcRoomCode: roomCode - ) + await workerAdminSync?.updateRoomCode( + normalizedRoomCode, + sendImmediateHeartbeat: shouldSendImmediateHeartbeat ) + + if !localOnly { + await patchActiveExecutionSession( + ExecutionSessionPatch( + helpRequested: hasActiveHelpEscalation, + webrtcRoomCode: normalizedRoomCode + ) + ) + } + + lastLiveRoomSyncSnapshot = nextSnapshot + lastLiveRoomSyncAt = now } private func closeCurrentPackageFlow() async { @@ -6025,7 +6544,7 @@ class StreamSessionViewModel: ObservableObject { shouldResumeAiSupportAfterBackOffice = true aiGuideStatusMessage = "AI guide paused while back office support connects." sopAuditStatusMessage = aiGuideStatusMessage - await geminiAssistant.stopSession() + await geminiAssistant.stopSessionForHumanSupportHandoff() } let hasRoomCode = !webrtcViewModel.roomCode.isEmpty diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index e67423f8..ab190be9 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -491,12 +491,22 @@ private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { matched: true, confidence: 0.93, reason: "Clear visual evidence.", - evidenceTimestamp: request.capturedAt, - threshold: 0.88, - model: "gemini-3.5-flash", - autoComplete: true - ) - : spotterResponses.removeFirst() + evidenceTimestamp: request.capturedAt, + threshold: 0.88, + model: "gemini-3.5-flash", + autoComplete: true, + modelAutoComplete: nil, + evidenceWindowSatisfied: nil, + activeDurationSatisfied: nil, + elapsedActiveMs: nil, + minActiveSeconds: nil, + stableObservations: nil, + stableObservationsRequired: nil, + advancedToStepIndex: nil, + completedSop: nil, + packageProgressWarning: nil + ) + : spotterResponses.removeFirst() return (queuedError, response) } @@ -674,7 +684,7 @@ final class WorkerAdminLiveSessionCoordinatorTests: XCTestCase { XCTAssertTrue(snapshot.uploadCalls.isEmpty) } - func testCompleteSessionUploadsVideoBeforeEndCallback() async throws { + func testPrepareVideoRecordingUploadReturnsPendingWithoutUploading() async throws { let api = WorkerAdminAPIMock() api.uploadTargetResponses = [ WorkerMediaUploadTarget( @@ -685,8 +695,36 @@ final class WorkerAdminLiveSessionCoordinatorTests: XCTestCase { ) ] - let fileURL = try makeTempFile(data: Data([0x01, 0x02, 0x03]), suffix: "ordering") - defer { try? FileManager.default.removeItem(at: fileURL) } + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-4", + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + let preparedResult = await coordinator.prepareVideoRecordingUpload(source: "stream-capture") + let prepared = try XCTUnwrap(preparedResult) + let snapshot = api.snapshot() + + XCTAssertEqual(prepared.result.uploadState, "pending") + XCTAssertEqual(prepared.result.assetID, "video-asset-2") + XCTAssertEqual(prepared.result.byteSize, 0) + XCTAssertEqual(snapshot.uploadTargetRequests.last?.byteSize, 0) + XCTAssertEqual(snapshot.uploadTargetRequests.last?.source, "stream-capture") + XCTAssertTrue(snapshot.uploadCalls.isEmpty) + XCTAssertTrue(snapshot.finalizeRequests.isEmpty) + } + + func testCompleteSessionEndsBeforePreparedVideoUpload() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-2", + bucket: "execution-videos", + path: "sessions/session-4/recording.mp4", + uploadURL: "https://upload.example/video-2" + ) + ] let callOrder = CallOrderRecorder() api.onFinalizeAttempt = { finalize in @@ -702,12 +740,85 @@ final class WorkerAdminLiveSessionCoordinatorTests: XCTestCase { ) await coordinator.start(sessionID: "session-4", currentStepIndex: 0, helpRequested: false) - let result = await coordinator.completeSession(videoFileURL: fileURL) { + let preparedResult = await coordinator.prepareVideoRecordingUpload(source: "stream-capture") + let prepared = try XCTUnwrap(preparedResult) + let result = await coordinator.completeSession(pendingVideoUpload: prepared) { callOrder.append("end") } + let completionSnapshot = api.snapshot() + + XCTAssertEqual(result.uploadState, "pending") + XCTAssertEqual(callOrder.values, ["end"]) + XCTAssertTrue(completionSnapshot.uploadCalls.isEmpty) + XCTAssertTrue(completionSnapshot.finalizeRequests.isEmpty) + } + + func testPreparedVideoUploadReusesReservedAssetID() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-2", + bucket: "execution-videos", + path: "sessions/session-4/recording.mp4", + uploadURL: "https://upload.example/video-2" + ) + ] + + let fileURL = try makeTempFile(data: Data([0x01, 0x02, 0x03]), suffix: "prepared-reuse") + defer { try? FileManager.default.removeItem(at: fileURL) } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-4", + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + let preparedResult = await coordinator.prepareVideoRecordingUpload(source: "stream-capture") + let prepared = try XCTUnwrap(preparedResult) + let result = await coordinator.uploadPreparedVideoRecording( + from: fileURL, + preparedUpload: prepared + ) + let snapshot = api.snapshot() XCTAssertTrue(result.succeeded) - XCTAssertEqual(callOrder.values, ["finalize", "end"]) + XCTAssertEqual(snapshot.uploadCalls.last?.assetID, "video-asset-2") + XCTAssertEqual(snapshot.finalizeRequests.last?.assetID, "video-asset-2") + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "uploaded") + } + + func testPreparedVideoUploadFinalizesFailedWhenRecordingIsMissing() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-missing", + bucket: "execution-videos", + path: "sessions/session-missing/recording.mp4", + uploadURL: "https://upload.example/video-missing" + ) + ] + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-missing", + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + let preparedResult = await coordinator.prepareVideoRecordingUpload(source: "phone-recording") + let prepared = try XCTUnwrap(preparedResult) + let result = await coordinator.uploadPreparedVideoRecording( + from: nil, + preparedUpload: prepared + ) + let snapshot = api.snapshot() + + XCTAssertEqual(result.uploadState, "failed") + XCTAssertEqual(result.assetID, "video-asset-missing") + XCTAssertTrue(snapshot.uploadCalls.isEmpty) + XCTAssertEqual(snapshot.finalizeRequests.last?.assetID, "video-asset-missing") + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "failed") } func testVideoFinalizeRetriesTransientErrorsThenSucceeds() async throws { From ac87c7edba60ad36e9db85199f32050abf87b1d5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 11 Jun 2026 15:52:32 -0500 Subject: [PATCH 11/11] Fix Bluetooth audio routing and sample timing --- .../CameraAccess/Gemini/AudioManager.swift | 3 ++- .../ViewModels/StreamSessionViewModel.swift | 18 +++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift index 8f426c51..c16e2487 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/AudioManager.swift @@ -113,7 +113,7 @@ final class WorkerAudioRouteCoordinator: @unchecked Sendable { try session.setActive(true, options: .notifyOthersOnDeactivation) var preferredInputName: String? - if mode == .glasses, !forceSpeaker, let input = preferredBluetoothHandsFreeInput(session) { + if let input = preferredBluetoothHandsFreeInput(session) { try session.setPreferredInput(input) preferredInputName = "\(input.portType.rawValue):\(input.portName)" try session.setActive(true, options: .notifyOthersOnDeactivation) @@ -666,6 +666,7 @@ final class AudioManager: @unchecked Sendable { switch reason { case .newDeviceAvailable: NSLog("[Audio] New audio device available") + attemptAudioReset() case .oldDeviceUnavailable: NSLog("[Audio] Audio device removed") attemptAudioReset() diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index c7dcf0b6..fab84e6f 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -582,7 +582,7 @@ private final class ConversationAudioRecorder: @unchecked Sendable { let framesPerChunk = Int(GeminiConfig.outputAudioSampleRate) let bytesPerChunk = framesPerChunk * bytesPerFrame var byteOffset = 0 - var frameOffset = 0 + var accumulatedSampleCount = 0 var appendFailed = false while byteOffset < data.count, !appendFailed { @@ -594,7 +594,7 @@ private final class ConversationAudioRecorder: @unchecked Sendable { guard alignedByteCount > 0 else { break } let chunkData = data.subdata(in: byteOffset..<(byteOffset + alignedByteCount)) let presentationTime = CMTime( - value: CMTimeValue(frameOffset), + value: CMTimeValue(accumulatedSampleCount), timescale: CMTimeScale(GeminiConfig.outputAudioSampleRate) ) guard let sampleBuffer = makeAudioSampleBuffer( @@ -609,7 +609,7 @@ private final class ConversationAudioRecorder: @unchecked Sendable { } appendFailed = !audioInput.append(sampleBuffer) byteOffset += alignedByteCount - frameOffset += alignedByteCount / bytesPerFrame + accumulatedSampleCount += alignedByteCount / bytesPerFrame } audioInput.markAsFinished() @@ -682,11 +682,9 @@ private final class ConversationAudioRecorder: @unchecked Sendable { } guard status == noErr else { return nil } + let sampleDuration = CMTime(value: 1, timescale: CMTimeScale(sampleRate)) var timing = CMSampleTimingInfo( - duration: CMTime( - value: CMTimeValue(sampleCount), - timescale: CMTimeScale(sampleRate.rounded()) - ), + duration: sampleDuration, presentationTimeStamp: presentationTime, decodeTimeStamp: .invalid ) @@ -1320,11 +1318,9 @@ private final class SopVideoRecorder: @unchecked Sendable { } guard status == noErr else { return nil } + let sampleDuration = CMTime(value: 1, timescale: CMTimeScale(sampleRate)) var timing = CMSampleTimingInfo( - duration: CMTime( - value: CMTimeValue(sampleCount), - timescale: CMTimeScale(sampleRate.rounded()) - ), + duration: sampleDuration, presentationTimeStamp: presentationTime, decodeTimeStamp: .invalid )

biP2uK%%{r~PZ?U9`sTbb0OvuY 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 p;#zafoaeup&&1!L&mRc<{{i{OHW$ORZDE9 literal 0 HcmV?d00001 diff --git a/samples/CameraAccess/CameraAccess/CameraAccessApp.swift b/samples/CameraAccess/CameraAccess/CameraAccessApp.swift index 1fedafda..1267d8c3 100644 --- a/samples/CameraAccess/CameraAccess/CameraAccessApp.swift +++ b/samples/CameraAccess/CameraAccess/CameraAccessApp.swift @@ -19,13 +19,13 @@ import Foundation import MWDATCore import SwiftUI -#if canImport(MWDATMockDevice) +#if DEBUG && canImport(MWDATMockDevice) import MWDATMockDevice #endif @main struct CameraAccessApp: App { - #if canImport(MWDATMockDevice) + #if DEBUG && canImport(MWDATMockDevice) // Debug menu for simulating device connections during development @StateObject private var debugMenuViewModel = DebugMenuViewModel(mockDeviceKit: MockDeviceKit.shared) #endif @@ -58,7 +58,7 @@ struct CameraAccessApp: App { } message: { Text(wearablesViewModel.errorMessage) } - #if canImport(MWDATMockDevice) + #if DEBUG && canImport(MWDATMockDevice) .sheet(isPresented: $debugMenuViewModel.showDebugMenu) { MockDeviceKitView(viewModel: debugMenuViewModel.mockDeviceKitViewModel) } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift index 5c124f66..560d9758 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift @@ -2,7 +2,8 @@ import Foundation enum GeminiConfig { static let websocketBaseURL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent" - static let model = "models/gemini-2.5-flash-native-audio-preview-12-2025" + // Use a model that exists for this API key and supports native audio interactions. + static let model = "models/gemini-2.5-flash-native-audio-latest" static let inputAudioSampleRate: Double = 16000 static let outputAudioSampleRate: Double = 24000 @@ -15,37 +16,31 @@ enum GeminiConfig { static var systemInstruction: String { SettingsManager.shared.geminiSystemPrompt } static let defaultSystemInstruction = """ - You are an AI assistant for someone wearing Meta Ray-Ban smart glasses. You can see through their camera and have a voice conversation. Keep responses concise and natural. + You are a live frontline worker copilot for SOP execution sessions. - CRITICAL: You have NO memory, NO storage, and NO ability to take actions on your own. You cannot remember things, keep lists, set reminders, search the web, send messages, or do anything persistent. You are ONLY a voice interface. + Your job is to converse naturally with the worker, guide the current task step by step, and use connected tools when they help move the job forward. - You have exactly ONE tool: execute. This connects you to a powerful personal assistant that can do anything -- send messages, search the web, manage lists, set reminders, create notes, research topics, control smart home devices, interact with apps, and much more. - - ALWAYS use execute when the user asks you to: - - Send a message to someone (any platform: WhatsApp, Telegram, iMessage, Slack, etc.) - - Search or look up anything (web, local info, facts, news) - - Add, create, or modify anything (shopping lists, reminders, notes, todos, events) - - Research, analyze, or draft anything - - Control or interact with apps, devices, or services - - Remember or store any information for later - - Be detailed in your task description. Include all relevant context: names, content, platforms, quantities, etc. The assistant works better with complete information. - - NEVER pretend to do these things yourself. - - IMPORTANT: Before calling execute, ALWAYS speak a brief acknowledgment first. For example: - - "Sure, let me add that to your shopping list." then call execute. - - "Got it, searching for that now." then call execute. - - "On it, sending that message." then call execute. - Never call execute silently -- the user needs verbal confirmation that you heard them and are working on it. The tool may take several seconds to complete, so the acknowledgment lets them know something is happening. - - For messages, confirm recipient and content before delegating unless clearly urgent. + Rules: + - Ground answers in the live camera feed, the current SOP context, and the worker's request. + - If visual evidence is insufficient, say what you need to see next. + - Keep spoken responses short, clear, and useful for hands-free work. + - Offer direct next actions instead of long explanations. + - Use available tools for task execution, logging, and memory when appropriate. + - Never pretend you verified something you could not actually observe or infer. """ // User-configurable values (Settings screen overrides, falling back to Secrets.swift) + static var deviceID: String { SettingsManager.shared.deviceID } + static var workerLoginCode: String { SettingsManager.shared.workerLoginCode } + static var workerEmail: String { SettingsManager.shared.workerEmail } static var apiKey: String { SettingsManager.shared.geminiAPIKey } + static var opsBaseURL: String { SettingsManager.shared.opsBaseURL } + static var adminBaseURL: String { SettingsManager.shared.adminBaseURL } + static var signalBaseURL: String { SettingsManager.shared.signalBaseURL } static var openClawHost: String { SettingsManager.shared.openClawHost } static var openClawPort: Int { SettingsManager.shared.openClawPort } + static var openClawTailscaleIP: String { SettingsManager.shared.openClawTailscaleIP } + static var openClawBearerToken: String { SettingsManager.shared.openClawBearerToken } static var openClawHookToken: String { SettingsManager.shared.openClawHookToken } static var openClawGatewayToken: String { SettingsManager.shared.openClawGatewayToken } @@ -58,6 +53,16 @@ enum GeminiConfig { return apiKey != "YOUR_GEMINI_API_KEY" && !apiKey.isEmpty } + static var isOpsConfigured: Bool { + let trimmed = opsBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + return !trimmed.isEmpty && !trimmed.contains("YOUR_") + } + + static var isAdminConfigured: Bool { + let trimmed = adminBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + return !trimmed.isEmpty && !trimmed.contains("YOUR_") + } + static var isOpenClawConfigured: Bool { return openClawGatewayToken != "YOUR_OPENCLAW_GATEWAY_TOKEN" && !openClawGatewayToken.isEmpty diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift index 248f2f02..0c7bb2da 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveService.swift @@ -1,4 +1,5 @@ import Foundation +import QuartzCore import UIKit enum GeminiConnectionState: Equatable { @@ -22,6 +23,8 @@ class GeminiLiveService: ObservableObject { var onOutputTranscription: ((String) -> Void)? var onToolCall: ((GeminiToolCall) -> Void)? var onToolCallCancellation: ((GeminiToolCallCancellation) -> Void)? + var onSocketOpened: (() -> Void)? + var onSocketClosed: ((String?) -> Void)? // Latency tracking private var lastUserSpeechEnd: Date? @@ -33,6 +36,12 @@ class GeminiLiveService: ObservableObject { private let delegate = WebSocketDelegate() private var urlSession: URLSession! private let sendQueue = DispatchQueue(label: "gemini.send", qos: .userInitiated) + private var latestVideoFrameBase64: String? + private var setupSystemInstruction: String = GeminiConfig.systemInstruction + private var videoFrameSendCount: Int64 = 0 + private var videoFrameStatsWindowStart = CACurrentMediaTime() + + var lastVideoFrameBase64: String? { latestVideoFrameBase64 } init() { let config = URLSessionConfiguration.default @@ -40,12 +49,13 @@ class GeminiLiveService: ObservableObject { self.urlSession = URLSession(configuration: config, delegate: delegate, delegateQueue: nil) } - func connect() async -> Bool { + func connect(systemInstruction: String? = nil) async -> Bool { guard let url = GeminiConfig.websocketURL() else { connectionState = .error("No API key configured") return false } + setupSystemInstruction = resolvedSystemInstruction(systemInstruction) connectionState = .connecting let result = await withCheckedContinuation { (continuation: CheckedContinuation) in @@ -54,6 +64,7 @@ class GeminiLiveService: ObservableObject { self.delegate.onOpen = { [weak self] protocol_ in guard let self else { return } Task { @MainActor in + self.onSocketOpened?() self.connectionState = .settingUp self.sendSetupMessage() self.startReceiving() @@ -67,6 +78,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .disconnected self.isModelSpeaking = false + self.onSocketClosed?("Connection closed (code \(code.rawValue): \(reasonStr))") self.onDisconnected?("Connection closed (code \(code.rawValue): \(reasonStr))") } } @@ -78,6 +90,7 @@ class GeminiLiveService: ObservableObject { self.resolveConnect(success: false) self.connectionState = .error(msg) self.isModelSpeaking = false + self.onSocketClosed?(msg) self.onDisconnected?(msg) } } @@ -110,6 +123,8 @@ class GeminiLiveService: ObservableObject { delegate.onError = nil onToolCall = nil onToolCallCancellation = nil + onSocketOpened = nil + onSocketClosed = nil connectionState = .disconnected isModelSpeaking = false resolveConnect(success: false) @@ -127,14 +142,20 @@ class GeminiLiveService: ObservableObject { ] ] ] - self?.sendJSON(json) + Task { @MainActor [weak self] in + self?.sendJSON(json) + } } } func sendVideoFrame(image: UIImage) { guard connectionState == .ready else { return } + let frameStartedAt = CACurrentMediaTime() sendQueue.async { [weak self] in + guard let self else { return } + let encodeStartedAt = CACurrentMediaTime() guard let jpegData = image.jpegData(compressionQuality: GeminiConfig.videoJPEGQuality) else { return } + let encodeDurationMs = (CACurrentMediaTime() - encodeStartedAt) * 1000 let base64 = jpegData.base64EncodedString() let json: [String: Any] = [ "realtimeInput": [ @@ -144,13 +165,24 @@ class GeminiLiveService: ObservableObject { ] ] ] - self?.sendJSON(json) + self.videoFrameSendCount += 1 + self.logVideoSendStatsIfNeeded( + payloadBytes: jpegData.count, + encodeDurationMs: encodeDurationMs, + totalDurationMs: (CACurrentMediaTime() - frameStartedAt) * 1000 + ) + Task { @MainActor [weak self] in + self?.latestVideoFrameBase64 = base64 + self?.sendJSON(json) + } } } func sendToolResponse(_ response: [String: Any]) { sendQueue.async { [weak self] in - self?.sendJSON(response) + Task { @MainActor [weak self] in + self?.sendJSON(response) + } } } @@ -168,6 +200,27 @@ class GeminiLiveService: ObservableObject { } } + private func logVideoSendStatsIfNeeded( + payloadBytes: Int, + encodeDurationMs: Double, + totalDurationMs: Double + ) { + guard videoFrameSendCount == 1 || videoFrameSendCount % 10 == 0 else { return } + let now = CACurrentMediaTime() + let elapsed = max(now - videoFrameStatsWindowStart, 0.001) + let fps = Double(videoFrameSendCount) / elapsed + NSLog( + "[Gemini] Vision lane frames=%lld rate=%.2ffps encode=%.1fms total=%.1fms payload=%dB", + videoFrameSendCount, + fps, + encodeDurationMs, + totalDurationMs, + payloadBytes + ) + videoFrameStatsWindowStart = now + videoFrameSendCount = 0 + } + // MARK: - Private private func resolveConnect(success: Bool) { @@ -177,6 +230,20 @@ class GeminiLiveService: ObservableObject { } } + private func resolvedSystemInstruction(_ override: String?) -> String { + let candidate = override?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + if !candidate.isEmpty { + return candidate + } + + let configured = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) + if !configured.isEmpty { + return configured + } + + return GeminiConfig.defaultSystemInstruction + } + private func sendSetupMessage() { let setup: [String: Any] = [ "setup": [ @@ -189,7 +256,7 @@ class GeminiLiveService: ObservableObject { ], "systemInstruction": [ "parts": [ - ["text": GeminiConfig.systemInstruction] + ["text": setupSystemInstruction] ] ], "tools": [ @@ -225,7 +292,17 @@ class GeminiLiveService: ObservableObject { let string = String(data: data, encoding: .utf8) else { return } - webSocketTask?.send(.string(string)) { _ in } + webSocketTask?.send(.string(string)) { [weak self] error in + guard let self, let error else { return } + Task { @MainActor in + NSLog("[Gemini] WebSocket send failed: %@", error.localizedDescription) + self.resolveConnect(success: false) + self.connectionState = .error("WebSocket send failed: \(error.localizedDescription)") + self.isModelSpeaking = false + self.onSocketClosed?(error.localizedDescription) + self.onDisconnected?(error.localizedDescription) + } + } } private func startReceiving() { @@ -267,6 +344,20 @@ class GeminiLiveService: ObservableObject { return } + // Server-provided error payload + if let errorObj = json["error"] as? [String: Any] { + let status = errorObj["status"] as? String ?? "UNKNOWN" + let message = errorObj["message"] as? String ?? "Unknown Gemini server error" + let full = "Gemini setup error [\(status)]: \(message)" + NSLog("[Gemini] %@", full) + connectionState = .error(full) + isModelSpeaking = false + resolveConnect(success: false) + onSocketClosed?(full) + onDisconnected?(full) + return + } + // Setup complete if json["setupComplete"] != nil { connectionState = .ready @@ -280,6 +371,7 @@ class GeminiLiveService: ObservableObject { let seconds = timeLeft?["seconds"] as? Int ?? 0 connectionState = .disconnected isModelSpeaking = false + onSocketClosed?("Server closing (time left: \(seconds)s)") onDisconnected?("Server closing (time left: \(seconds)s)") return } diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift new file mode 100644 index 00000000..8e92b463 --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiLiveSpotter.swift @@ -0,0 +1,142 @@ +import Foundation +import UIKit + +final class GeminiLiveSpotter { + private let session: URLSession = { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + return URLSession(configuration: config) + }() + + struct SpotterRequestItem: Hashable { + let id: String + let name: String + let aiPrompt: String + let expectedObjects: [String] + } + + func detectVisibleItemIDs( + image: UIImage, + items: [SpotterRequestItem] + ) async throws -> [String] { + guard !items.isEmpty else { return [] } + + guard GeminiConfig.isConfigured else { + return [] + } + + guard let jpegData = image.jpegData(compressionQuality: 0.55) else { + return [] + } + + let base64 = jpegData.base64EncodedString() + let itemHints = items.map { + [ + "id": $0.id, + "name": $0.name, + "ai_prompt": $0.aiPrompt, + "expected_objects": $0.expectedObjects + ] as [String: Any] + } + let prompt = """ + You are the live SOP brain for an operations execution app. + Each candidate item has an AI prompt describing what visual completion looks like. + Expected objects run in parallel with the prompt, but the prompt is the main rule. + + Candidate steps: \(itemHints) + + Look at the image and decide which candidate steps are clearly complete right now. + Only mark a step complete if the image strongly satisfies that step's ai_prompt. + If evidence is ambiguous, do not include it. + + Reply ONLY with a valid JSON array of the item IDs you clearly see as complete. + Example: [\"wallet\", \"thermos\"] + """ + + let payload: [String: Any] = [ + "contents": [ + [ + "parts": [ + ["text": prompt], + [ + "inline_data": [ + "mime_type": "image/jpeg", + "data": base64 + ] + ] + ] + ] + ], + "generationConfig": [ + "temperature": 0.1, + "maxOutputTokens": 64, + "responseMimeType": "application/json" + ] + ] + + let model = "models/gemini-2.5-flash-lite" + let endpoint = "https://generativelanguage.googleapis.com/v1beta/\(model):generateContent?key=\(GeminiConfig.apiKey)" + guard let url = URL(string: endpoint) else { return [] } + + let data = try JSONSerialization.data(withJSONObject: payload) + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = data + + let (responseData, response) = try await session.data(for: request) + guard let http = response as? HTTPURLResponse, + (200...299).contains(http.statusCode) else { + return [] + } + + return parseVisibleIDs(from: responseData, allowedIDs: Set(items.map(\.id))) + } + + private func parseVisibleIDs(from data: Data, allowedIDs: Set) -> [String] { + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let candidates = json["candidates"] as? [[String: Any]], + let first = candidates.first, + let content = first["content"] as? [String: Any], + let parts = content["parts"] as? [[String: Any]], + let text = parts.compactMap({ $0["text"] as? String }).joined(separator: "\n").nonEmpty else { + return [] + } + + // Try direct JSON array first. + if let ids = parseJSONArrayIDs(from: text) { + return ids.filter { allowedIDs.contains($0) } + } + + // If model wrapped in markdown fences. + let cleaned = text + .replacingOccurrences(of: "```json", with: "") + .replacingOccurrences(of: "```", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + + if let ids = parseJSONArrayIDs(from: cleaned) { + return ids.filter { allowedIDs.contains($0) } + } + + return [] + } + + private func parseJSONArrayIDs(from raw: String) -> [String]? { + guard let rawData = raw.data(using: .utf8), + let array = try? JSONSerialization.jsonObject(with: rawData) as? [String] else { + return nil + } + + return array.map { + $0 + .lowercased() + .trimmingCharacters(in: .whitespacesAndNewlines) + } + } +} + +private extension String { + var nonEmpty: String? { + isEmpty ? nil : self + } +} diff --git a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift index e7d9d902..77884a33 100644 --- a/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/Gemini/GeminiSessionViewModel.swift @@ -11,32 +11,279 @@ class GeminiSessionViewModel: ObservableObject { @Published var aiTranscript: String = "" @Published var toolCallStatus: ToolCallStatus = .idle @Published var openClawConnectionState: OpenClawConnectionState = .notConfigured + private let geminiService = GeminiLiveService() + private let sopRelayClient = SopRelayClient() private let openClawBridge = OpenClawBridge() private var toolCallRouter: ToolCallRouter? private let audioManager = AudioManager() private let eventClient = OpenClawEventClient() private var lastVideoFrameTime: Date = .distantPast private var stateObservation: Task? + private var heartbeatTask: Task? + private var heartbeatTimeoutTask: Task? + private var currentSopSessionId: String? + private var isSopSessionTerminated: Bool = true + private var isFinalizingSession: Bool = false + private var pendingSystemInstruction: String? + private var currentSessionInstruction: String? var streamingMode: StreamingMode = .glasses - func startSession() async { - guard !isGeminiActive else { return } + func startSession(systemInstruction: String? = nil) async { + pendingSystemInstruction = normalizedSystemInstruction(systemInstruction) + guard !isGeminiActive else { + await refreshSessionInstruction(systemInstruction) + return + } + errorMessage = nil + userTranscript = "" + aiTranscript = "" guard GeminiConfig.isConfigured else { - errorMessage = "Gemini API key not configured. Open GeminiConfig.swift and replace YOUR_GEMINI_API_KEY with your key from https://aistudio.google.com/apikey" + errorMessage = "Gemini API key not configured. Open Settings and add your key from https://aistudio.google.com/apikey" return } isGeminiActive = true + configureRealtimeCallbacks() + + await openClawBridge.checkConnection() + openClawBridge.resetSession() + toolCallRouter = ToolCallRouter(bridge: openClawBridge) + startStateObservation() + + do { + try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) + } catch { + resetToIdle(receiptMessage: "Audio setup failed: \(error.localizedDescription)") + return + } + + let resolvedInstruction = resolvedSystemInstruction() + let setupOk = await geminiService.connect(systemInstruction: resolvedInstruction) + + if !setupOk { + let message: String + if case .error(let err) = geminiService.connectionState { + message = err + } else { + message = "Failed to connect to Gemini" + } + resetToIdle(receiptMessage: message) + return + } + + do { + try audioManager.startCapture() + } catch { + resetToIdle(receiptMessage: "Mic capture failed: \(error.localizedDescription)") + return + } + + connectEventStreamIfNeeded() + currentSessionInstruction = resolvedInstruction + } + + func stopSession() { + finalizeSessionWithReceipt(status: "terminated") + } + + func refreshSessionInstruction(_ systemInstruction: String?) async { + pendingSystemInstruction = normalizedSystemInstruction(systemInstruction) + guard isGeminiActive else { return } + + let resolvedInstruction = resolvedSystemInstruction() + guard resolvedInstruction != currentSessionInstruction else { return } + + await reconnectTransport(with: resolvedInstruction) + } + + func sendVideoFrameIfThrottled(image: UIImage) { + guard SettingsManager.shared.videoStreamingEnabled else { return } + guard isGeminiActive, connectionState == .ready else { return } + let now = Date() + guard now.timeIntervalSince(lastVideoFrameTime) >= GeminiConfig.videoFrameInterval else { return } + lastVideoFrameTime = now + geminiService.sendVideoFrame(image: image) + } + + private func handleSopLogToolCall(_ call: GeminiFunctionCall) { + let stepName = (call.args["step_name"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard !stepName.isEmpty else { + geminiService.sendToolResponse(buildToolResponse( + callId: call.id, + name: call.name, + result: .failure("Missing required argument: step_name") + )) + return + } + + let sessionId: String + if let existing = currentSopSessionId { + sessionId = existing + } else { + let created = UUID().uuidString + currentSopSessionId = created + isSopSessionTerminated = false + sessionId = created + } + + let imageBase64 = (call.args["frame_data"] as? String)?.isEmpty == false + ? (call.args["frame_data"] as? String ?? "") + : ((call.args["image_base64"] as? String)?.isEmpty == false + ? (call.args["image_base64"] as? String ?? "") + : (geminiService.lastVideoFrameBase64 ?? "")) + + sopRelayClient.postSopLog( + tailscaleIP: GeminiConfig.openClawTailscaleIP, + sessionID: sessionId, + stepName: stepName, + timestampISO8601: ISO8601DateFormatter().string(from: Date()), + imageBase64: imageBase64 + ) + + geminiService.sendToolResponse(buildToolResponse( + callId: call.id, + name: call.name, + result: .success("SOP step forwarded") + )) + } + + private func connectEventStreamIfNeeded() { + eventClient.disconnect() + guard SettingsManager.shared.proactiveNotificationsEnabled else { return } + + eventClient.onNotification = { [weak self] text in + guard let self else { return } + Task { @MainActor in + guard self.isGeminiActive, self.connectionState == .ready else { return } + self.geminiService.sendTextMessage(text) + } + } + eventClient.connect() + } + + private func startSopHeartbeatSession() { + if currentSopSessionId != nil && !isSopSessionTerminated { return } + + let sessionId = UUID().uuidString + currentSopSessionId = sessionId + isSopSessionTerminated = false + + heartbeatTask?.cancel() + heartbeatTimeoutTask?.cancel() + + heartbeatTask = Task { [weak self] in + guard let self else { return } + + self.sopRelayClient.postHeartbeat( + tailscaleIP: GeminiConfig.openClawTailscaleIP, + sessionID: sessionId, + status: "active" + ) + + while !Task.isCancelled && !self.isSopSessionTerminated { + self.sopRelayClient.postHeartbeat( + tailscaleIP: GeminiConfig.openClawTailscaleIP, + sessionID: sessionId, + status: "active" + ) + try? await Task.sleep(nanoseconds: 3_000_000_000) + } + } + + heartbeatTimeoutTask = Task { [weak self] in + try? await Task.sleep(nanoseconds: 60_000_000_000) + await MainActor.run { + self?.finalizeSessionWithReceipt(status: "terminated") + } + } + } + + private func finalizeSessionWithReceipt(status: String) { + if isFinalizingSession { return } + + guard let sessionId = currentSopSessionId, !isSopSessionTerminated else { + resetToIdle(receiptMessage: nil) + return + } + + isFinalizingSession = true + isSopSessionTerminated = true + heartbeatTask?.cancel() + heartbeatTask = nil + heartbeatTimeoutTask?.cancel() + heartbeatTimeoutTask = nil + + Task { [weak self] in + guard let self else { return } + + let receiptMessage = await self.sopRelayClient.postHeartbeatForReceipt( + tailscaleIP: GeminiConfig.openClawTailscaleIP, + sessionID: sessionId, + status: status + ) - // Wire audio callbacks + await MainActor.run { + self.resetToIdle(receiptMessage: receiptMessage) + } + } + } + + private func resetToIdle(receiptMessage: String?) { + eventClient.disconnect() + geminiService.onDisconnected = nil + geminiService.onSocketClosed = nil + geminiService.onSocketOpened = nil + + toolCallRouter?.cancelAll() + toolCallRouter = nil + audioManager.stopCapture() + geminiService.disconnect() + stateObservation?.cancel() + stateObservation = nil + heartbeatTask?.cancel() + heartbeatTask = nil + heartbeatTimeoutTask?.cancel() + heartbeatTimeoutTask = nil + + isGeminiActive = false + connectionState = .disconnected + isModelSpeaking = false + userTranscript = "" + aiTranscript = "" + toolCallStatus = .idle + errorMessage = normalizedReceiptMessage(receiptMessage) + currentSessionInstruction = nil + + currentSopSessionId = nil + isSopSessionTerminated = true + isFinalizingSession = false + } + + private func normalizedSystemInstruction(_ instruction: String?) -> String? { + let trimmed = instruction?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return trimmed.isEmpty ? nil : trimmed + } + + private func resolvedSystemInstruction() -> String { + if let pendingSystemInstruction { + return pendingSystemInstruction + } + + let configured = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) + if !configured.isEmpty { + return configured + } + + return GeminiConfig.defaultSystemInstruction + } + + private func configureRealtimeCallbacks() { audioManager.onAudioCaptured = { [weak self] data in guard let self else { return } Task { @MainActor in - // Mute mic while model speaks when speaker is on the phone - // (loudspeaker + co-located mic overwhelms iOS echo cancellation) let speakerOnPhone = self.streamingMode == .iPhone || SettingsManager.shared.speakerOutputEnabled if speakerOnPhone && self.geminiService.isModelSpeaking { return } self.geminiService.sendAudio(data: data) @@ -54,7 +301,6 @@ class GeminiSessionViewModel: ObservableObject { geminiService.onTurnComplete = { [weak self] in guard let self else { return } Task { @MainActor in - // Clear user transcript when AI finishes responding self.userTranscript = "" } } @@ -74,27 +320,38 @@ class GeminiSessionViewModel: ObservableObject { } } - // Handle unexpected disconnection geminiService.onDisconnected = { [weak self] reason in guard let self else { return } Task { @MainActor in - guard self.isGeminiActive else { return } - self.stopSession() - self.errorMessage = "Connection lost: \(reason ?? "Unknown error")" + guard self.isGeminiActive, !self.isFinalizingSession else { return } + self.resetToIdle(receiptMessage: "Connection lost: \(reason ?? "Unknown error")") } } - // Check OpenClaw connectivity and start fresh session - await openClawBridge.checkConnection() - openClawBridge.resetSession() + geminiService.onSocketOpened = { [weak self] in + guard let self else { return } + Task { @MainActor in + self.startSopHeartbeatSession() + } + } - // Wire tool call handling - toolCallRouter = ToolCallRouter(bridge: openClawBridge) + geminiService.onSocketClosed = { [weak self] _ in + guard let self else { return } + Task { @MainActor in + guard self.isGeminiActive, !self.isFinalizingSession else { return } + self.finalizeSessionWithReceipt(status: "terminated") + } + } geminiService.onToolCall = { [weak self] toolCall in guard let self else { return } Task { @MainActor in for call in toolCall.functionCalls { + if call.name == "log_sop_step" { + self.handleSopLogToolCall(call) + continue + } + self.toolCallRouter?.handleToolCall(call) { [weak self] response in self?.geminiService.sendToolResponse(response) } @@ -108,12 +365,14 @@ class GeminiSessionViewModel: ObservableObject { self.toolCallRouter?.cancelToolCalls(ids: cancellation.ids) } } + } - // Observe service state + private func startStateObservation() { + stateObservation?.cancel() stateObservation = Task { [weak self] in guard let self else { return } while !Task.isCancelled { - try? await Task.sleep(nanoseconds: 100_000_000) // 100ms + try? await Task.sleep(nanoseconds: 100_000_000) guard !Task.isCancelled else { break } self.connectionState = self.geminiService.connectionState self.isModelSpeaking = self.geminiService.isModelSpeaking @@ -121,84 +380,82 @@ class GeminiSessionViewModel: ObservableObject { self.openClawConnectionState = self.openClawBridge.connectionState } } + } + + private func reconnectTransport(with systemInstruction: String) async { + eventClient.disconnect() + audioManager.stopCapture() + geminiService.onDisconnected = nil + geminiService.onSocketClosed = nil + geminiService.onSocketOpened = nil + geminiService.disconnect() + stateObservation?.cancel() + stateObservation = nil + errorMessage = nil + userTranscript = "" + aiTranscript = "" + + await openClawBridge.checkConnection() + if toolCallRouter == nil { + toolCallRouter = ToolCallRouter(bridge: openClawBridge) + } + configureRealtimeCallbacks() + startStateObservation() - // Setup audio do { try audioManager.setupAudioSession(useIPhoneMode: streamingMode == .iPhone) } catch { - errorMessage = "Audio setup failed: \(error.localizedDescription)" - isGeminiActive = false + resetToIdle(receiptMessage: "Audio setup failed: \(error.localizedDescription)") return } - // Connect to Gemini and wait for setupComplete - let setupOk = await geminiService.connect() - + let setupOk = await geminiService.connect(systemInstruction: systemInstruction) if !setupOk { - let msg: String + let message: String if case .error(let err) = geminiService.connectionState { - msg = err + message = err } else { - msg = "Failed to connect to Gemini" - } - errorMessage = msg - geminiService.disconnect() - stateObservation?.cancel() - stateObservation = nil - isGeminiActive = false - connectionState = .disconnected + message = "Failed to reconnect to Gemini" + } + resetToIdle(receiptMessage: message) return } - // Start mic capture do { try audioManager.startCapture() } catch { - errorMessage = "Mic capture failed: \(error.localizedDescription)" - geminiService.disconnect() - stateObservation?.cancel() - stateObservation = nil - isGeminiActive = false - connectionState = .disconnected + resetToIdle(receiptMessage: "Mic capture failed: \(error.localizedDescription)") return } - // Connect to OpenClaw event stream for proactive notifications - if SettingsManager.shared.proactiveNotificationsEnabled { - eventClient.onNotification = { [weak self] text in - guard let self else { return } - Task { @MainActor in - guard self.isGeminiActive, self.connectionState == .ready else { return } - self.geminiService.sendTextMessage(text) - } - } - eventClient.connect() - } + connectEventStreamIfNeeded() + currentSessionInstruction = systemInstruction } - func stopSession() { - eventClient.disconnect() - toolCallRouter?.cancelAll() - toolCallRouter = nil - audioManager.stopCapture() - geminiService.disconnect() - stateObservation?.cancel() - stateObservation = nil - isGeminiActive = false - connectionState = .disconnected - isModelSpeaking = false - userTranscript = "" - aiTranscript = "" - toolCallStatus = .idle + private func normalizedReceiptMessage(_ receiptMessage: String?) -> String? { + let trimmed = receiptMessage?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard !trimmed.isEmpty else { return nil } + if trimmed.localizedCaseInsensitiveContains("legacy sop relay disabled") { + return nil + } + return trimmed } - func sendVideoFrameIfThrottled(image: UIImage) { - guard SettingsManager.shared.videoStreamingEnabled else { return } - guard isGeminiActive, connectionState == .ready else { return } - let now = Date() - guard now.timeIntervalSince(lastVideoFrameTime) >= GeminiConfig.videoFrameInterval else { return } - lastVideoFrameTime = now - geminiService.sendVideoFrame(image: image) + private func buildToolResponse( + callId: String, + name: String, + result: ToolResult + ) -> [String: Any] { + return [ + "toolResponse": [ + "functionResponses": [ + [ + "id": callId, + "name": name, + "response": result.responseValue + ] + ] + ] + ] } - } diff --git a/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift new file mode 100644 index 00000000..f95fd216 --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Gemini/SopRelayClient.swift @@ -0,0 +1,1637 @@ +import Foundation + +private extension KeyedDecodingContainer { + func decodeFirstString(forKeys keys: [K]) throws -> String? { + for key in keys { + if let stringValue = try decodeIfPresent(String.self, forKey: key), + !stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return stringValue + } + } + return nil + } + + func decodeLossyInt(forKeys keys: [K]) throws -> Int? { + for key in keys { + if let intValue = try? decodeIfPresent(Int.self, forKey: key) { + return intValue + } + if let stringValue = try? decodeIfPresent(String.self, forKey: key), + !stringValue.isEmpty { + if let intValue = Int(stringValue) { + return intValue + } + if let doubleValue = Double(stringValue) { + return Int(doubleValue.rounded(.down)) + } + } + if let doubleValue = try? decodeIfPresent(Double.self, forKey: key) { + return Int(doubleValue.rounded(.down)) + } + } + return nil + } + + func decodeLossyBool(forKeys keys: [K]) throws -> Bool? { + for key in keys { + if let boolValue = try? decodeIfPresent(Bool.self, forKey: key) { + return boolValue + } + if let intValue = try? decodeIfPresent(Int.self, forKey: key) { + return intValue != 0 + } + if let stringValue = try? decodeIfPresent(String.self, forKey: key), + !stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + switch stringValue.lowercased() { + case "true", "1", "yes", "active": + return true + case "false", "0", "no", "inactive": + return false + default: + continue + } + } + } + return nil + } +} + +private func canonicalLucasSopTitle(for sopID: String) -> String? { + switch sopID { + case "22222222-2222-2222-2222-222222222222": + return "Cold Chain Verification SOP" + case "a1000001-0000-0000-0000-000000000001": + return "Burger Assembly" + case "a1000002-0000-0000-0000-000000000002": + return "Fries Assembly" + case "a1000003-0000-0000-0000-000000000003": + return "Drink Prep" + default: + return nil + } +} + +private func canonicalLucasPackageTitle(for packageID: String?) -> String? { + guard let packageID else { return nil } + switch packageID { + case "33333333-3333-3333-3333-333333333333": + return "Inbound Cold Chain Audit" + case "b2000001-0000-0000-0000-000000000001": + return "QSR Value Meal Order" + default: + return nil + } +} + +private func canonicalLucasSopSortOrder(for sopID: String) -> Int? { + switch sopID { + case "22222222-2222-2222-2222-222222222222": + return 1 + case "a1000001-0000-0000-0000-000000000001": + return 2 + case "a1000002-0000-0000-0000-000000000002": + return 3 + case "a1000003-0000-0000-0000-000000000003": + return 4 + default: + return nil + } +} + +struct BackendWorker: Identifiable, Decodable, Equatable { + let id: String + let loginCode: String? + let email: String? + let displayName: String + let role: String? + let status: String? + + private enum CodingKeys: String, CodingKey { + case id + case loginCode = "login_code" + case loginCodeCamel = "loginCode" + case email + case displayName = "display_name" + case displayNameCamel = "displayName" + case name + case role + case status + case active + } + + init( + id: String, + loginCode: String?, + email: String? = nil, + displayName: String, + role: String?, + status: String? + ) { + self.id = id + self.loginCode = loginCode + self.email = email + self.displayName = displayName + self.role = role + self.status = status + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + loginCode = try container.decodeFirstString(forKeys: [.loginCode, .loginCodeCamel]) + email = try container.decodeIfPresent(String.self, forKey: .email) + displayName = + try container.decodeFirstString(forKeys: [.displayName, .displayNameCamel, .name]) + ?? "Unassigned Worker" + role = try container.decodeIfPresent(String.self, forKey: .role) + status = + try container.decodeIfPresent(String.self, forKey: .status) + ?? ((try container.decodeLossyBool(forKeys: [.active]) ?? false) ? "active" : nil) + } +} + +struct BackendDevice: Identifiable, Decodable, Equatable { + let id: String + let workerID: String? + let platform: String? + let deviceLabel: String? + + private enum CodingKeys: String, CodingKey { + case id + case workerID = "worker_id" + case workerIDCamel = "workerId" + case platform + case deviceLabel = "device_label" + case deviceLabelCamel = "deviceLabel" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + workerID = try container.decodeFirstString(forKeys: [.workerID, .workerIDCamel]) + platform = try container.decodeIfPresent(String.self, forKey: .platform) + deviceLabel = try container.decodeFirstString(forKeys: [.deviceLabel, .deviceLabelCamel]) + } +} + +struct BackendPackage: Identifiable, Decodable, Equatable { + let id: String + let title: String + let description: String? + let outcome: String? + let version: Int? + let status: String? + + private enum CodingKeys: String, CodingKey { + case id + case title + case name + case description + case outcome + case version + case status + } + + init( + id: String, + title: String, + description: String?, + outcome: String?, + version: Int?, + status: String? + ) { + self.id = id + self.title = title + self.description = description + self.outcome = outcome + self.version = version + self.status = status + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + title = + try container.decodeIfPresent(String.self, forKey: .title) + ?? container.decodeIfPresent(String.self, forKey: .name) + ?? "Untitled Package" + description = try container.decodeIfPresent(String.self, forKey: .description) + outcome = try container.decodeIfPresent(String.self, forKey: .outcome) + version = try container.decodeLossyInt(forKeys: [.version]) + status = try container.decodeIfPresent(String.self, forKey: .status) + } +} + +struct BackendAssignedPackage: Identifiable, Decodable, Equatable { + let id: String + let title: String + let description: String? + let outcome: String? + let version: Int? + let shiftName: String? + let active: Bool? + let packageRunID: String? + let packageRunStatus: String? + let packageRunStartedAt: String? + let packageRunCompletedAt: String? + + private enum CodingKeys: String, CodingKey { + case id + case packageID = "package_id" + case packageIDCamel = "packageId" + case title + case packageTitle = "package_title" + case packageTitleCamel = "packageTitle" + case description + case packageDescription = "package_description" + case packageDescriptionCamel = "packageDescription" + case outcome + case packageOutcome = "package_outcome" + case packageOutcomeCamel = "packageOutcome" + case version + case packageVersion = "package_version" + case packageVersionCamel = "packageVersion" + case shiftName = "shift_name" + case shiftNameCamel = "shiftName" + case active + case packageRunID = "package_run_id" + case packageRunIDCamel = "packageRunId" + case packageRunStatus = "package_run_status" + case packageRunStatusCamel = "packageRunStatus" + case packageRunStartedAt = "package_run_started_at" + case packageRunStartedAtCamel = "packageRunStartedAt" + case packageRunCompletedAt = "package_run_completed_at" + case packageRunCompletedAtCamel = "packageRunCompletedAt" + } + + init( + id: String, + title: String, + description: String?, + outcome: String?, + version: Int?, + shiftName: String?, + active: Bool?, + packageRunID: String?, + packageRunStatus: String?, + packageRunStartedAt: String?, + packageRunCompletedAt: String? + ) { + self.id = id + self.title = title + self.description = description + self.outcome = outcome + self.version = version + self.shiftName = shiftName + self.active = active + self.packageRunID = packageRunID + self.packageRunStatus = packageRunStatus + self.packageRunStartedAt = packageRunStartedAt + self.packageRunCompletedAt = packageRunCompletedAt + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let rawID = + try container.decodeFirstString(forKeys: [.id, .packageID, .packageIDCamel]) + ?? UUID().uuidString + let rawTitle = + try container.decodeFirstString(forKeys: [.title, .packageTitle, .packageTitleCamel]) + ?? canonicalLucasPackageTitle(for: rawID) + ?? "Untitled Package" + + self.init( + id: rawID, + title: rawTitle, + description: try container.decodeFirstString(forKeys: [.description, .packageDescription, .packageDescriptionCamel]), + outcome: try container.decodeFirstString(forKeys: [.outcome, .packageOutcome, .packageOutcomeCamel]), + version: try container.decodeLossyInt(forKeys: [.version, .packageVersion, .packageVersionCamel]), + shiftName: try container.decodeFirstString(forKeys: [.shiftName, .shiftNameCamel]), + active: try container.decodeLossyBool(forKeys: [.active]), + packageRunID: try container.decodeFirstString(forKeys: [.packageRunID, .packageRunIDCamel]), + packageRunStatus: try container.decodeFirstString(forKeys: [.packageRunStatus, .packageRunStatusCamel]), + packageRunStartedAt: try container.decodeFirstString(forKeys: [.packageRunStartedAt, .packageRunStartedAtCamel]), + packageRunCompletedAt: try container.decodeFirstString(forKeys: [.packageRunCompletedAt, .packageRunCompletedAtCamel]) + ) + } +} + +struct BackendShift: Identifiable, Decodable, Equatable { + let id: String + let packageID: String? + let shiftName: String? + let startsAt: String? + let endsAt: String? + let active: Bool? + let package: BackendPackage? + + private enum CodingKeys: String, CodingKey { + case id + case packageID = "package_id" + case packageIDCamel = "packageId" + case shiftName = "shift_name" + case shiftNameCamel = "shiftName" + case startsAt = "starts_at" + case startsAtCamel = "startsAt" + case endsAt = "ends_at" + case endsAtCamel = "endsAt" + case active + case package + } + + init( + id: String, + packageID: String?, + shiftName: String?, + startsAt: String?, + endsAt: String?, + active: Bool?, + package: BackendPackage? + ) { + self.id = id + self.packageID = packageID + self.shiftName = shiftName + self.startsAt = startsAt + self.endsAt = endsAt + self.active = active + self.package = package + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + packageID = try container.decodeFirstString(forKeys: [.packageID, .packageIDCamel]) + shiftName = try container.decodeFirstString(forKeys: [.shiftName, .shiftNameCamel]) + startsAt = try container.decodeFirstString(forKeys: [.startsAt, .startsAtCamel]) + endsAt = try container.decodeFirstString(forKeys: [.endsAt, .endsAtCamel]) + active = try container.decodeLossyBool(forKeys: [.active]) + package = try container.decodeIfPresent(BackendPackage.self, forKey: .package) + } +} + +struct WorkerQueueStep: Identifiable, Decodable, Equatable, Hashable { + let id: String + let order: Int + let title: String + let description: String + let duration: String + let validation: String + let critical: Bool + let aiPrompt: String + let expectedObjects: [String] + let allowManualComplete: Bool + + private enum CodingKeys: String, CodingKey { + case id + case order + case name + case title + case label + case step + case item + case description + case duration + case validation + case critical + case aiPrompt = "ai_prompt" + case aiPromptCamel = "aiPrompt" + case expectedObjects = "expected_objects" + case expectedObjectsCamel = "expectedObjects" + case allowManualComplete = "allow_manual_complete" + case allowManualCompleteCamel = "allowManualComplete" + } + + init( + id: String, + order: Int, + title: String, + description: String = "", + duration: String = "30s", + validation: String = "visual", + critical: Bool = false, + aiPrompt: String? = nil, + expectedObjects: [String] = [], + allowManualComplete: Bool = true + ) { + self.id = id + self.order = order + self.title = title + self.description = description + self.duration = duration + self.validation = validation + self.critical = critical + self.aiPrompt = aiPrompt ?? "Look at the image and confirm whether \"\(title)\" has been completed." + self.expectedObjects = expectedObjects + self.allowManualComplete = allowManualComplete + } + + init(from decoder: Decoder) throws { + if let single = try? decoder.singleValueContainer(), + let raw = try? single.decode(String.self) { + self.init( + id: raw.lowercased().replacingOccurrences(of: "[^a-z0-9]+", with: "_", options: .regularExpression), + order: 0, + title: raw + ) + return + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + let name = try container.decodeIfPresent(String.self, forKey: .name) + let fallbackTitle = try container.decodeIfPresent(String.self, forKey: .title) + let label = try container.decodeIfPresent(String.self, forKey: .label) + let step = try container.decodeIfPresent(String.self, forKey: .step) + let item = try container.decodeIfPresent(String.self, forKey: .item) + let resolvedTitle = name ?? fallbackTitle ?? label ?? step ?? item ?? "Untitled Step" + let resolvedID = + try container.decodeIfPresent(String.self, forKey: .id) + ?? resolvedTitle.lowercased().replacingOccurrences(of: "[^a-z0-9]+", with: "_", options: .regularExpression) + let expectedObjects = + try container.decodeIfPresent([String].self, forKey: .expectedObjects) + ?? container.decodeIfPresent([String].self, forKey: .expectedObjectsCamel) + ?? [] + self.init( + id: resolvedID, + order: try container.decodeIfPresent(Int.self, forKey: .order) ?? 0, + title: resolvedTitle, + description: try container.decodeIfPresent(String.self, forKey: .description) ?? "", + duration: try container.decodeIfPresent(String.self, forKey: .duration) ?? "30s", + validation: try container.decodeIfPresent(String.self, forKey: .validation) ?? "visual", + critical: try container.decodeIfPresent(Bool.self, forKey: .critical) ?? false, + aiPrompt: + try container.decodeIfPresent(String.self, forKey: .aiPrompt) + ?? container.decodeIfPresent(String.self, forKey: .aiPromptCamel), + expectedObjects: expectedObjects.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }, + allowManualComplete: + try container.decodeIfPresent(Bool.self, forKey: .allowManualComplete) + ?? container.decodeIfPresent(Bool.self, forKey: .allowManualCompleteCamel) + ?? true + ) + } +} + +struct WorkerQueueItem: Identifiable, Decodable, Equatable { + let shiftAssignmentID: String? + let workerID: String? + let workerName: String? + let packageID: String? + let packageTitle: String? + let packageRunID: String? + let packageVersion: Int? + let sopID: String + let sopTitle: String + let sopVersion: Int? + let steps: [WorkerQueueStep] + let shiftName: String? + let sourceType: String + let sortOrder: Int + let required: Bool + let active: Bool? + let startsAt: String? + let endsAt: String? + + var id: String { "\(packageRunID ?? packageID ?? sourceType):\(sopID)" } + var stepTitles: [String] { steps.map(\.title) } + + private enum CodingKeys: String, CodingKey { + case shiftAssignmentID = "shift_assignment_id" + case shiftAssignmentIDCamel = "shiftAssignmentId" + case workerID = "worker_id" + case workerIDCamel = "workerId" + case workerName = "worker_name" + case workerNameCamel = "workerName" + case packageID = "package_id" + case packageIDCamel = "packageId" + case packageTitle = "package_title" + case packageTitleCamel = "packageTitle" + case packageRunID = "package_run_id" + case packageRunIDCamel = "packageRunId" + case packageVersion = "package_version" + case packageVersionCamel = "packageVersion" + case sopID = "sop_id" + case sopIDCamel = "sopId" + case sopTitle = "sop_title" + case sopTitleCamel = "sopTitle" + case sopVersion = "sop_version" + case sopVersionCamel = "sopVersion" + case steps + case shiftName = "shift_name" + case shiftNameCamel = "shiftName" + case sourceType = "source_type" + case sourceTypeCamel = "sourceType" + case sortOrder = "sort_order" + case sortOrderCamel = "sortOrder" + case required + case active + case startsAt = "starts_at" + case startsAtCamel = "startsAt" + case scheduledFor = "scheduledFor" + case endsAt = "ends_at" + case endsAtCamel = "endsAt" + case completedAtCamel = "completedAt" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + guard let decodedSopID = try container.decodeFirstString(forKeys: [.sopID, .sopIDCamel]) else { + throw DecodingError.keyNotFound( + CodingKeys.sopID, + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Missing sop_id/sopId in worker queue item." + ) + ) + } + shiftAssignmentID = try container.decodeFirstString(forKeys: [.shiftAssignmentID, .shiftAssignmentIDCamel]) + workerID = try container.decodeFirstString(forKeys: [.workerID, .workerIDCamel]) + workerName = try container.decodeFirstString(forKeys: [.workerName, .workerNameCamel]) + packageID = try container.decodeFirstString(forKeys: [.packageID, .packageIDCamel]) + packageTitle = + try container.decodeFirstString(forKeys: [.packageTitle, .packageTitleCamel]) + ?? canonicalLucasPackageTitle(for: packageID) + packageRunID = try container.decodeFirstString(forKeys: [.packageRunID, .packageRunIDCamel]) + packageVersion = try container.decodeLossyInt(forKeys: [.packageVersion, .packageVersionCamel]) + sopID = decodedSopID + sopTitle = + try container.decodeFirstString(forKeys: [.sopTitle, .sopTitleCamel]) + ?? canonicalLucasSopTitle(for: decodedSopID) + ?? "Assigned SOP" + sopVersion = try container.decodeLossyInt(forKeys: [.sopVersion, .sopVersionCamel]) + shiftName = + try container.decodeFirstString(forKeys: [.shiftName, .shiftNameCamel]) + ?? "Morning" + sourceType = + try container.decodeFirstString(forKeys: [.sourceType, .sourceTypeCamel]) + ?? (packageID == nil ? "standalone" : "package") + sortOrder = + try container.decodeLossyInt(forKeys: [.sortOrder, .sortOrderCamel]) + ?? canonicalLucasSopSortOrder(for: decodedSopID) + ?? 0 + required = try container.decodeLossyBool(forKeys: [.required]) ?? true + active = try container.decodeLossyBool(forKeys: [.active]) + startsAt = try container.decodeFirstString(forKeys: [.startsAt, .startsAtCamel, .scheduledFor]) + endsAt = try container.decodeFirstString(forKeys: [.endsAt, .endsAtCamel, .completedAtCamel]) + + if let direct = try container.decodeIfPresent([String].self, forKey: .steps) { + steps = direct.enumerated().map { index, title in + WorkerQueueStep( + id: "\(decodedSopID)-\(index + 1)", + order: index + 1, + title: title + ) + } + } else if let richSteps = try container.decodeIfPresent([WorkerQueueStep].self, forKey: .steps) { + steps = richSteps.enumerated().map { index, step in + WorkerQueueStep( + id: step.id, + order: step.order == 0 ? index + 1 : step.order, + title: step.title, + description: step.description, + duration: step.duration, + validation: step.validation, + critical: step.critical, + aiPrompt: step.aiPrompt, + expectedObjects: step.expectedObjects, + allowManualComplete: step.allowManualComplete + ) + } + } else { + steps = [] + } + } +} + +struct BootstrapPayload: Decodable, Equatable { + let worker: BackendWorker + let device: BackendDevice? + let shift: BackendShift? + let queue: [WorkerQueueItem] + let assignedPackages: [BackendAssignedPackage] + let workerSessionToken: String? + let workerSessionExpiresAt: String? + + private enum CodingKeys: String, CodingKey { + case worker + case device + case shift + case queue + case assignedPackages = "assigned_packages" + case assignedPackagesCamel = "assignedPackages" + case packages + case workerSessionToken = "worker_session_token" + case workerSessionTokenCamel = "workerSessionToken" + case workerToken = "worker_token" + case sessionToken = "session_token" + case sessionTokenCamel = "sessionToken" + case token + case workerSessionExpiresAt = "worker_session_expires_at" + case workerSessionExpiresAtCamel = "workerSessionExpiresAt" + case sessionExpiresAt = "session_expires_at" + case sessionExpiresAtCamel = "sessionExpiresAt" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + worker = try container.decode(BackendWorker.self, forKey: .worker) + device = try container.decodeIfPresent(BackendDevice.self, forKey: .device) + shift = try container.decodeIfPresent(BackendShift.self, forKey: .shift) + queue = try container.decodeIfPresent([WorkerQueueItem].self, forKey: .queue) ?? [] + let directAssignedPackages = + try container.decodeIfPresent([BackendAssignedPackage].self, forKey: .assignedPackages) + let camelAssignedPackages = + try container.decodeIfPresent([BackendAssignedPackage].self, forKey: .assignedPackagesCamel) + let packageList = + try container.decodeIfPresent([BackendAssignedPackage].self, forKey: .packages) + assignedPackages = + directAssignedPackages + ?? camelAssignedPackages + ?? packageList + ?? BootstrapPayload.deriveAssignedPackages(shift: shift, queue: queue) + + let tokenFromWorkerSession = + try container.decodeIfPresent(String.self, forKey: .workerSessionToken) + let tokenFromWorkerSessionCamel = + try container.decodeIfPresent(String.self, forKey: .workerSessionTokenCamel) + let tokenFromWorkerToken = + try container.decodeIfPresent(String.self, forKey: .workerToken) + let tokenFromSession = + try container.decodeIfPresent(String.self, forKey: .sessionToken) + let tokenFromSessionCamel = + try container.decodeIfPresent(String.self, forKey: .sessionTokenCamel) + let tokenFromGeneric = + try container.decodeIfPresent(String.self, forKey: .token) + workerSessionToken = + tokenFromWorkerSession + ?? tokenFromWorkerSessionCamel + ?? tokenFromWorkerToken + ?? tokenFromSession + ?? tokenFromSessionCamel + ?? tokenFromGeneric + + let expiresFromWorkerSession = + try container.decodeIfPresent(String.self, forKey: .workerSessionExpiresAt) + let expiresFromSession = + try container.decodeIfPresent(String.self, forKey: .sessionExpiresAt) + let expiresFromWorkerSessionCamel = + try container.decodeIfPresent(String.self, forKey: .workerSessionExpiresAtCamel) + let expiresFromSessionCamel = + try container.decodeIfPresent(String.self, forKey: .sessionExpiresAtCamel) + workerSessionExpiresAt = + expiresFromWorkerSession + ?? expiresFromSession + ?? expiresFromWorkerSessionCamel + ?? expiresFromSessionCamel + } + + private static func deriveAssignedPackages( + shift: BackendShift?, + queue: [WorkerQueueItem] + ) -> [BackendAssignedPackage] { + var resolved: [BackendAssignedPackage] = [] + var seen = Set() + + if let package = shift?.package { + let candidate = BackendAssignedPackage( + id: package.id, + title: package.title, + description: package.description, + outcome: package.outcome, + version: package.version, + shiftName: shift?.shiftName, + active: shift?.active, + packageRunID: queue.first(where: { $0.packageID == package.id })?.packageRunID, + packageRunStatus: nil, + packageRunStartedAt: nil, + packageRunCompletedAt: nil + ) + resolved.append(candidate) + seen.insert(candidate.id) + } + + for item in queue where item.packageID != nil { + guard let packageID = item.packageID, !seen.contains(packageID) else { continue } + resolved.append( + BackendAssignedPackage( + id: packageID, + title: item.packageTitle ?? "Assigned Package", + description: nil, + outcome: nil, + version: item.packageVersion, + shiftName: item.shiftName ?? shift?.shiftName, + active: item.active, + packageRunID: item.packageRunID, + packageRunStatus: nil, + packageRunStartedAt: item.startsAt, + packageRunCompletedAt: item.endsAt + ) + ) + seen.insert(packageID) + } + + return resolved + } +} + +struct BackendExecutionSession: Identifiable, Decodable, Equatable { + let id: String + let workerID: String? + let deviceID: String? + let packageID: String? + let packageRunID: String? + let currentSopID: String? + let sopVersion: Int? + let packageVersion: Int? + let currentStepIndex: Int + let status: String + let helpRequested: Bool + let webrtcRoomCode: String? + let lastFrameBucket: String? + let lastFramePath: String? + let startedAt: String? + let endedAt: String? + let updatedAt: String? + let packageProgressWarning: String? + + private enum CodingKeys: String, CodingKey { + case id + case workerID = "worker_id" + case workerIDCamel = "workerId" + case deviceID = "device_id" + case deviceIDCamel = "deviceId" + case packageID = "package_id" + case packageIDCamel = "packageId" + case packageRunID = "package_run_id" + case packageRunIDCamel = "packageRunId" + case currentSopID = "current_sop_id" + case currentSopIDCamel = "currentSopId" + case sopVersion = "sop_version" + case sopVersionCamel = "sopVersion" + case packageVersion = "package_version" + case packageVersionCamel = "packageVersion" + case currentStepIndex = "current_step_index" + case currentStepIndexCamel = "currentStepIndex" + case status + case helpRequested = "help_requested" + case helpRequestedCamel = "helpRequested" + case webrtcRoomCode = "webrtc_room_code" + case webrtcRoomCodeCamel = "webrtcRoomCode" + case lastFrameBucket = "last_frame_bucket" + case lastFrameBucketCamel = "lastFrameBucket" + case lastFramePath = "last_frame_path" + case lastFramePathCamel = "lastFramePath" + case startedAt = "started_at" + case startedAtCamel = "startedAt" + case endedAt = "ended_at" + case endedAtCamel = "endedAt" + case updatedAt = "updated_at" + case updatedAtCamel = "updatedAt" + case packageProgressWarning = "package_progress_warning" + case packageProgressWarningCamel = "packageProgressWarning" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: .id) + workerID = try container.decodeFirstString(forKeys: [.workerID, .workerIDCamel]) + deviceID = try container.decodeFirstString(forKeys: [.deviceID, .deviceIDCamel]) + packageID = try container.decodeFirstString(forKeys: [.packageID, .packageIDCamel]) + packageRunID = try container.decodeFirstString(forKeys: [.packageRunID, .packageRunIDCamel]) + currentSopID = try container.decodeFirstString(forKeys: [.currentSopID, .currentSopIDCamel]) + sopVersion = try container.decodeLossyInt(forKeys: [.sopVersion, .sopVersionCamel]) + packageVersion = try container.decodeLossyInt(forKeys: [.packageVersion, .packageVersionCamel]) + currentStepIndex = try container.decodeLossyInt(forKeys: [.currentStepIndex, .currentStepIndexCamel]) ?? 0 + status = try container.decodeIfPresent(String.self, forKey: .status) ?? "active" + helpRequested = try container.decodeLossyBool(forKeys: [.helpRequested, .helpRequestedCamel]) ?? false + webrtcRoomCode = try container.decodeFirstString(forKeys: [.webrtcRoomCode, .webrtcRoomCodeCamel]) + lastFrameBucket = try container.decodeFirstString(forKeys: [.lastFrameBucket, .lastFrameBucketCamel]) + lastFramePath = try container.decodeFirstString(forKeys: [.lastFramePath, .lastFramePathCamel]) + startedAt = try container.decodeFirstString(forKeys: [.startedAt, .startedAtCamel]) + endedAt = try container.decodeFirstString(forKeys: [.endedAt, .endedAtCamel]) + updatedAt = try container.decodeFirstString(forKeys: [.updatedAt, .updatedAtCamel]) + packageProgressWarning = try container.decodeFirstString( + forKeys: [.packageProgressWarning, .packageProgressWarningCamel] + ) + } +} + +struct BackendExecutionEvent: Identifiable, Decodable, Equatable { + let id: Int + let sessionID: String? + let eventType: String? + + private enum CodingKeys: String, CodingKey { + case id + case sessionID = "session_id" + case eventType = "event_type" + } +} + +struct BackendIntervention: Identifiable, Decodable, Equatable { + let id: String + let sessionID: String? + let status: String? + let notes: String? + + private enum CodingKeys: String, CodingKey { + case id + case sessionID = "session_id" + case status + case notes + } +} + +struct BackendMediaAsset: Identifiable, Decodable, Equatable { + let id: String + let sessionID: String? + let bucket: String? + let path: String? + + private enum CodingKeys: String, CodingKey { + case id + case sessionID = "session_id" + case bucket + case path + } +} + +struct BackendMediaUploadTarget: Decodable, Equatable { + let assetID: String? + let uploadURL: String + let method: String + let headers: [String: String] + + private enum CodingKeys: String, CodingKey { + case assetID = "asset_id" + case assetIDCamel = "assetId" + case uploadURL = "upload_url" + case uploadURLCamel = "uploadUrl" + case method + case headers + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + assetID = try container.decodeFirstString(forKeys: [.assetID, .assetIDCamel]) + uploadURL = try container.decodeFirstString(forKeys: [.uploadURL, .uploadURLCamel]) ?? "" + method = try container.decodeIfPresent(String.self, forKey: .method) ?? "PUT" + headers = try container.decodeIfPresent([String: String].self, forKey: .headers) ?? [:] + } +} + +protocol WorkerAdminAPI: AnyObject { + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws + func requestWorkerMediaUploadTarget( + sessionID: String, + assetType: String, + filename: String, + contentType: String, + byteSize: Int, + source: String? + ) async throws -> WorkerMediaUploadTarget + func finalizeWorkerMediaUpload(_ finalize: WorkerMediaFinalizeRequest) async throws + func uploadBinary( + to target: WorkerMediaUploadTarget, + data: Data, + contentType: String + ) async throws +} + +struct WorkerLiveHeartbeatRequest: Equatable { + let sessionID: String + let webrtcRoomCode: String? + let currentStepIndex: Int + let helpRequested: Bool + let status: String + let lastFrameBucket: String? + let lastFramePath: String? + + var payload: [String: Any] { + var payload: [String: Any] = [ + "sessionId": sessionID, + "currentStepIndex": currentStepIndex, + "helpRequested": helpRequested, + "status": status + ] + if let webrtcRoomCode { + payload["webrtcRoomCode"] = webrtcRoomCode + } + if let lastFrameBucket { + payload["lastFrameBucket"] = lastFrameBucket + } + if let lastFramePath { + payload["lastFramePath"] = lastFramePath + } + return payload + } +} + +struct WorkerMediaUploadTarget: Decodable, Equatable { + let assetID: String + let bucket: String + let path: String + let uploadURL: String + let method: String + let headers: [String: String] + + init( + assetID: String, + bucket: String, + path: String, + uploadURL: String, + method: String = "PUT", + headers: [String: String] = [:] + ) { + self.assetID = assetID + self.bucket = bucket + self.path = path + self.uploadURL = uploadURL + self.method = method + self.headers = headers + } + + private enum CodingKeys: String, CodingKey { + case assetID = "asset_id" + case assetIDCamel = "assetId" + case bucket + case path + case uploadURL = "upload_url" + case uploadURLCamel = "uploadUrl" + case method + case headers + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + guard let assetID = try container.decodeFirstString(forKeys: [.assetID, .assetIDCamel]), + let bucket = try container.decodeFirstString(forKeys: [.bucket]), + let path = try container.decodeFirstString(forKeys: [.path]), + let uploadURL = try container.decodeFirstString(forKeys: [.uploadURL, .uploadURLCamel]) + else { + throw DecodingError.dataCorrupted( + .init(codingPath: decoder.codingPath, debugDescription: "Missing worker media upload target fields") + ) + } + self.assetID = assetID + self.bucket = bucket + self.path = path + self.uploadURL = uploadURL + self.method = try container.decodeIfPresent(String.self, forKey: .method) ?? "PUT" + self.headers = try container.decodeIfPresent([String: String].self, forKey: .headers) ?? [:] + } +} + +struct WorkerMediaFinalizeRequest: Equatable { + let assetID: String + let sessionID: String + let bucket: String + let path: String + let status: String + let byteSize: Int + let error: String? + + var payload: [String: Any] { + var payload: [String: Any] = [ + "assetId": assetID, + "sessionId": sessionID, + "bucket": bucket, + "path": path, + "status": status, + "byteSize": byteSize + ] + if let error { + payload["error"] = error + } + return payload + } +} + +struct BackendMemoryLink: Identifiable, Decodable, Equatable { + let id: String +} + +struct BackendPackageExecutionRun: Identifiable, Decodable, Equatable { + let id: String + let packageID: String? + let status: String? + let completedAt: String? + + private enum CodingKeys: String, CodingKey { + case id + case packageID = "package_id" + case status + case completedAt = "completed_at" + } +} + +private struct HealthResponse: Decodable { + let status: String + let service: String +} + +struct ExecutionSessionPatch { + var status: String? + var currentSopID: String? + var currentStepIndex: Int? + var helpRequested: Bool? + var webrtcRoomCode: String? + var lastFrameBucket: String? + var lastFramePath: String? + var endedAt: String? + + var payload: [String: Any] { + var payload: [String: Any] = [:] + if let status { payload["status"] = status } + if let currentSopID { payload["current_sop_id"] = currentSopID } + if let currentStepIndex { payload["current_step_index"] = currentStepIndex } + if let helpRequested { payload["help_requested"] = helpRequested } + if let webrtcRoomCode = webrtcRoomCode?.trimmingCharacters(in: .whitespacesAndNewlines), + !webrtcRoomCode.isEmpty { + payload["webrtc_room_code"] = webrtcRoomCode + } + if let lastFrameBucket = lastFrameBucket?.trimmingCharacters(in: .whitespacesAndNewlines), + !lastFrameBucket.isEmpty { + payload["last_frame_bucket"] = lastFrameBucket + } + if let lastFramePath = lastFramePath?.trimmingCharacters(in: .whitespacesAndNewlines), + !lastFramePath.isEmpty { + payload["last_frame_path"] = lastFramePath + } + if let endedAt { payload["ended_at"] = endedAt } + return payload + } +} + +enum OpsAPIError: LocalizedError { + case notConfigured + case invalidURL(String) + case invalidResponse + case missingWorkerSession + case missingWorkerBearerToken + case server(statusCode: Int, message: String) + + var errorDescription: String? { + switch self { + case .notConfigured: + return "Ops API base URL is not configured." + case .invalidURL(let path): + return "Invalid URL for path \(path)." + case .invalidResponse: + return "The ops-api returned an invalid response." + case .missingWorkerSession: + return "Worker session is missing. Re-bootstrap before writing execution state." + case .missingWorkerBearerToken: + return "Worker bearer token is missing. Re-bootstrap or configure a worker bearer token in Settings." + case .server(let statusCode, let message): + return "ops-api returned HTTP \(statusCode): \(message)" + } + } +} + +enum AdminIngestError: LocalizedError { + case notConfigured + case invalidURL(String) + case invalidResponse + case missingWorkerBearerToken + case server(statusCode: Int, url: String, message: String) + + var errorDescription: String? { + switch self { + case .notConfigured: + return "Admin ingest base URL is not configured." + case .invalidURL(let path): + return "Invalid admin ingest URL for path \(path)." + case .invalidResponse: + return "The admin ingest service returned an invalid response." + case .missingWorkerBearerToken: + return "Worker bearer token is missing. Re-bootstrap or configure a worker bearer token in Settings." + case .server(let statusCode, let url, let message): + return "Admin ingest returned HTTP \(statusCode) from \(url): \(message)" + } + } +} + +final class OpsAPIClient: WorkerAdminAPI { + private let session: URLSession + private let decoder: JSONDecoder + private var workerSessionToken: String? + + init(session: URLSession = { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 15 + return URLSession(configuration: config) + }()) { + self.session = session + self.decoder = JSONDecoder() + } + + var isConfigured: Bool { + GeminiConfig.isOpsConfigured + } + + var currentWorkerBearerToken: String? { + let liveToken = workerSessionToken?.trimmingCharacters(in: .whitespacesAndNewlines) + if let liveToken, !liveToken.isEmpty { + return liveToken + } + + let configuredToken = GeminiConfig.openClawBearerToken.trimmingCharacters(in: .whitespacesAndNewlines) + return configuredToken.isEmpty ? nil : configuredToken + } + + func health() async throws -> String { + let response: HealthResponse = try await performRequest(path: "/health", method: "GET") + return "\(response.status):\(response.service)" + } + + func bootstrap( + loginCode: String?, + email: String?, + platform: String, + label: String + ) async throws -> BootstrapPayload { + var payload: [String: Any] = [ + "platform": platform, + "label": label, + "device_label": label + ] + if let loginCode, !loginCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + payload["login_code"] = loginCode + } + if let email, !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + payload["email"] = email + } + let response: BootstrapPayload = try await performRequest( + path: "/v1/bootstrap", + method: "POST", + requiresWorkerAuth: false, + payload: payload + ) + workerSessionToken = response.workerSessionToken?.trimmingCharacters(in: .whitespacesAndNewlines) + return response + } + + func createExecutionSession( + workerID: String, + deviceID: String?, + shiftID: String?, + packageID: String?, + packageRunID: String?, + currentSopID: String?, + sopVersion: Int?, + packageVersion: Int?, + status: String = "active" + ) async throws -> BackendExecutionSession { + var payload: [String: Any] = [ + "worker_id": workerID, + "status": status + ] + if let deviceID { payload["device_id"] = deviceID } + if let shiftID { payload["shift_id"] = shiftID } + if let packageID { payload["package_id"] = packageID } + if let packageRunID { payload["package_run_id"] = packageRunID } + if let currentSopID { payload["current_sop_id"] = currentSopID } + if let sopVersion { payload["sop_version"] = sopVersion } + if let packageVersion { payload["package_version"] = packageVersion } + return try await performRequest( + path: "/v1/execution-sessions", + method: "POST", + requiresWorkerAuth: true, + payload: payload + ) + } + + func updateExecutionSession( + id: String, + patch: ExecutionSessionPatch + ) async throws -> BackendExecutionSession { + try await performRequest( + path: "/v1/execution-sessions/\(id)", + method: "PATCH", + requiresWorkerAuth: true, + payload: patch.payload + ) + } + + func postExecutionEvent( + sessionID: String, + eventType: String, + payload: [String: Any] + ) async throws -> BackendExecutionEvent { + try await performRequest( + path: "/v1/execution-sessions/\(sessionID)/events", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "event_type": eventType, + "payload": payload + ] + ) + } + + func createIntervention( + sessionID: String, + type: String, + notes: String? + ) async throws -> BackendIntervention { + var payload: [String: Any] = [ + "session_id": sessionID, + "type": type + ] + if let notes, !notes.isEmpty { + payload["notes"] = notes + } + return try await performRequest( + path: "/v1/interventions", + method: "POST", + requiresWorkerAuth: true, + payload: payload + ) + } + + func registerMediaAsset( + sessionID: String, + bucket: String, + path: String, + assetType: String, + metadata: [String: Any] + ) async throws -> BackendMediaAsset { + try await performRequest( + path: "/v1/media-assets", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "session_id": sessionID, + "bucket": bucket, + "path": path, + "asset_type": assetType, + "metadata": metadata + ] + ) + } + + func createMemoryLink( + sourceID: String, + sourceType: String, + targetID: String, + targetType: String, + linkType: String, + metadata: [String: Any] + ) async throws -> BackendMemoryLink { + try await performRequest( + path: "/v1/memory-links", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "source_id": sourceID, + "source_type": sourceType, + "target_id": targetID, + "target_type": targetType, + "link_type": linkType, + "metadata": metadata + ] + ) + } + + func requestMediaUploadTarget( + assetID: String, + contentType: String, + byteCount: Int + ) async throws -> BackendMediaUploadTarget { + try await performRequest( + path: "/v1/media-assets/\(assetID)/upload-target", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "content_type": contentType, + "byte_count": byteCount + ] + ) + } + + func finalizeMediaAssetUpload( + assetID: String, + uploadState: String = "uploaded", + byteCount: Int, + contentType: String + ) async throws -> BackendMediaAsset { + try await performRequest( + path: "/v1/media-assets/\(assetID)/finalize", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "upload_state": uploadState, + "byte_count": byteCount, + "content_type": contentType + ] + ) + } + + func uploadBinary( + to target: BackendMediaUploadTarget, + data: Data, + contentType: String + ) async throws { + guard let url = URL(string: target.uploadURL), !target.uploadURL.isEmpty else { + throw OpsAPIError.invalidURL(target.uploadURL) + } + + var request = URLRequest(url: url) + request.httpMethod = target.method.isEmpty ? "PUT" : target.method + request.setValue(contentType, forHTTPHeaderField: "Content-Type") + for (name, value) in target.headers { + request.setValue(value, forHTTPHeaderField: name) + } + + let (_, response) = try await session.upload(for: request, from: data) + guard let httpResponse = response as? HTTPURLResponse else { + throw OpsAPIError.invalidResponse + } + guard (200...299).contains(httpResponse.statusCode) else { + throw OpsAPIError.server(statusCode: httpResponse.statusCode, message: "Media upload failed") + } + } + + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws { + _ = try await performWorkerRequest( + path: "/api/worker/live/heartbeat", + method: "POST", + payload: heartbeat.payload + ) + } + + func requestWorkerMediaUploadTarget( + sessionID: String, + assetType: String, + filename: String, + contentType: String, + byteSize: Int, + source: String? = nil + ) async throws -> WorkerMediaUploadTarget { + var payload: [String: Any] = [ + "sessionId": sessionID, + "assetType": assetType, + "filename": filename, + "contentType": contentType, + "byteSize": byteSize + ] + if let source, !source.isEmpty { + payload["source"] = source + } + let data = try await performWorkerRequest( + path: "/api/worker/media/upload-target", + method: "POST", + payload: payload + ) + + do { + return try decoder.decode(WorkerMediaUploadTarget.self, from: data) + } catch { + let body = String(data: data, encoding: .utf8) ?? "" + NSLog("[admin-ingest] Failed decoding /api/worker/media/upload-target -> %@", body) + throw error + } + } + + func finalizeWorkerMediaUpload(_ finalize: WorkerMediaFinalizeRequest) async throws { + _ = try await performWorkerRequest( + path: "/api/worker/media/finalize", + method: "POST", + payload: finalize.payload + ) + } + + func uploadBinary( + to target: WorkerMediaUploadTarget, + data: Data, + contentType: String + ) async throws { + guard let url = URL(string: target.uploadURL), !target.uploadURL.isEmpty else { + throw OpsAPIError.invalidURL(target.uploadURL) + } + + var request = URLRequest(url: url) + request.httpMethod = target.method.isEmpty ? "PUT" : target.method + request.setValue(contentType, forHTTPHeaderField: "Content-Type") + for (name, value) in target.headers { + request.setValue(value, forHTTPHeaderField: name) + } + + let (_, response) = try await session.upload(for: request, from: data) + guard let httpResponse = response as? HTTPURLResponse else { + throw OpsAPIError.invalidResponse + } + guard (200...299).contains(httpResponse.statusCode) else { + throw OpsAPIError.server(statusCode: httpResponse.statusCode, message: "Media upload failed") + } + } + + func closePackageRun( + packageRunID: String, + workerID: String + ) async throws -> BackendPackageExecutionRun { + try await performRequest( + path: "/v1/package-runs/\(packageRunID)/close", + method: "POST", + requiresWorkerAuth: true, + payload: [ + "worker_id": workerID + ] + ) + } + + private func performRequest( + path: String, + method: String, + requiresWorkerAuth: Bool = false, + payload: [String: Any]? = nil + ) async throws -> Response { + guard isConfigured else { + throw OpsAPIError.notConfigured + } + + guard let url = makeURL(path: path, baseURLString: GeminiConfig.opsBaseURL) else { + throw OpsAPIError.invalidURL(path) + } + + var request = URLRequest(url: url) + request.httpMethod = method + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("application/json", forHTTPHeaderField: "Accept") + + if requiresWorkerAuth { + guard let token = workerSessionToken?.trimmingCharacters(in: .whitespacesAndNewlines), + !token.isEmpty + else { + throw OpsAPIError.missingWorkerSession + } + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } + + if let payload { + request.httpBody = try JSONSerialization.data(withJSONObject: payload, options: []) + } + + let (data, response) = try await session.data(for: request) + guard let httpResponse = response as? HTTPURLResponse else { + throw OpsAPIError.invalidResponse + } + + guard (200...299).contains(httpResponse.statusCode) else { + let message = String(data: data, encoding: .utf8) ?? "Unknown error" + throw OpsAPIError.server(statusCode: httpResponse.statusCode, message: message) + } + + do { + return try decoder.decode(Response.self, from: data) + } catch { + let body = String(data: data, encoding: .utf8) ?? "" + NSLog("[ops-api] Failed decoding %@ -> %@", path, body) + throw error + } + } + + private func makeURL(path: String, baseURLString: String) -> URL? { + let trimmed = baseURLString.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + + let normalizedBase: String + if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") { + normalizedBase = trimmed + } else { + normalizedBase = "https://\(trimmed)" + } + + guard var components = URLComponents(string: normalizedBase) else { + return nil + } + let cleanedPath = components.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + components.path = "/" + [cleanedPath, path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))] + .filter { !$0.isEmpty } + .joined(separator: "/") + return components.url + } + + private func performWorkerRequest( + path: String, + method: String, + payload: [String: Any]? = nil + ) async throws -> Data { + guard GeminiConfig.isAdminConfigured else { + throw AdminIngestError.notConfigured + } + + guard let url = makeURL(path: path, baseURLString: GeminiConfig.adminBaseURL) else { + throw AdminIngestError.invalidURL(path) + } + + guard let token = currentWorkerBearerToken else { + throw AdminIngestError.missingWorkerBearerToken + } + + var request = URLRequest(url: url) + request.httpMethod = method + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("application/json", forHTTPHeaderField: "Accept") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + if let payload { + request.httpBody = try JSONSerialization.data(withJSONObject: payload, options: []) + } + + let (data, response) = try await session.data(for: request) + guard let httpResponse = response as? HTTPURLResponse else { + throw AdminIngestError.invalidResponse + } + + guard (200...299).contains(httpResponse.statusCode) else { + let message = summarizeResponseBody(data) + NSLog( + "[admin-ingest] %@ %@ -> %d %@", + method, + url.absoluteString, + httpResponse.statusCode, + message + ) + throw AdminIngestError.server( + statusCode: httpResponse.statusCode, + url: url.absoluteString, + message: message + ) + } + + return data + } + + private func summarizeResponseBody(_ data: Data) -> String { + let raw = String(data: data, encoding: .utf8) ?? "Unknown error" + let compact = raw.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) + .trimmingCharacters(in: .whitespacesAndNewlines) + guard compact.count > 240 else { return compact } + let endIndex = compact.index(compact.startIndex, offsetBy: 240) + return "\(compact[.. String? { + "Legacy SOP relay disabled while ops-api migration is active." + } + + func postHeartbeatForReceiptWithStatus( + tailscaleIP: String, + sessionID: String, + status: String + ) async -> (statusCode: Int?, message: String?) { + (200, "Legacy SOP relay disabled while ops-api migration is active.") + } + + func postFinalPayloadForReceiptWithStatus( + tailscaleIP: String, + payload: [String: Any] + ) async -> (statusCode: Int?, message: String?) { + (200, "Legacy SOP relay disabled while ops-api migration is active.") + } + + func postSopVideoForReceiptWithStatus( + tailscaleIP: String, + sessionID: String, + videoFileURL: URL + ) async -> (statusCode: Int?, message: String?) { + (200, "Video upload deferred until media upload flow is implemented.") + } + + func postSopDossierForReceiptWithStatus( + tailscaleIP: String, + sessionID: String, + sopName: String, + metadataJSONString: String, + videoFileURL: URL, + proofImagesByTargetID: [String: Data] + ) async -> (statusCode: Int?, message: String?) { + (200, "Dossier upload deferred until media upload flow is implemented.") + } +} diff --git a/samples/CameraAccess/CameraAccess/Info.plist b/samples/CameraAccess/CameraAccess/Info.plist index 12cc4016..cbda4159 100644 --- a/samples/CameraAccess/CameraAccess/Info.plist +++ b/samples/CameraAccess/CameraAccess/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - VisionClaw + Embarcadero CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - VisionClaw + Embarcadero CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString @@ -33,49 +33,44 @@ CFBundleVersion $(CURRENT_PROJECT_VERSION) - - MWDAT AppLinkURLScheme - cameraaccess:// - MetaAppID - - $(META_APP_ID) - ClientToken $(CLIENT_TOKEN) - + MetaAppID + $(META_APP_ID) TeamID $(DEVELOPMENT_TEAM) - UIBackgroundModes - - audio - bluetooth-peripheral - external-accessory - + NSAppTransportSecurity + + NSAllowsLocalNetworking + + NSExceptionDomains + + 100.64.30.99 + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + + NSBluetoothAlwaysUsageDescription Needed to connect to Meta AI Glasses NSBluetoothPeripheralUsageDescription To listen to audio from Meta AI Glasses - UISupportedExternalAccessoryProtocols - - com.meta.ar.wearable - - NSCameraUsageDescription This app uses the camera for iPhone testing mode, allowing you to test the AI assistant pipeline without glasses. NSMicrophoneUsageDescription This app uses the microphone to have voice conversations with the AI assistant while streaming from your glasses. NSPhotoLibraryAddUsageDescription This app needs access to save photos captured from your glasses. - NSAppTransportSecurity - - NSAllowsLocalNetworking - - + NSSpeechRecognitionUsageDescription + This app uses speech recognition so operators can check SOP items hands-free while capturing. UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -83,14 +78,22 @@ UIApplicationSupportsIndirectInputEvents - + UIBackgroundModes + + audio + bluetooth-peripheral + external-accessory + UILaunchScreen UIRequiredDeviceCapabilities armv7 - + UISupportedExternalAccessoryProtocols + + com.meta.ar.wearable + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift b/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift index 1f48ac6f..104447ab 100644 --- a/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift +++ b/samples/CameraAccess/CameraAccess/OpenClaw/OpenClawBridge.swift @@ -99,7 +99,7 @@ class OpenClawBridge: ObservableObject { "stream": false ] - NSLog("[OpenClaw] Sending %d messages in conversation", conversationHistory.count) + NSLog("[OpenClaw] Sending conversation with %d messages", conversationHistory.count) do { request.httpBody = try JSONSerialization.data(withJSONObject: body) @@ -108,8 +108,7 @@ class OpenClawBridge: ObservableObject { guard let statusCode = httpResponse?.statusCode, (200...299).contains(statusCode) else { let code = httpResponse?.statusCode ?? 0 - let bodyStr = String(data: data, encoding: .utf8) ?? "no body" - NSLog("[OpenClaw] Chat failed: HTTP %d - %@", code, String(bodyStr.prefix(200))) + NSLog("[OpenClaw] Chat failed with HTTP %d", code) lastToolCallStatus = .failed(toolName, "HTTP \(code)") return .failure("Agent returned HTTP \(code)") } @@ -121,14 +120,14 @@ class OpenClawBridge: ObservableObject { let content = message["content"] as? String { // Append assistant response to history for continuity conversationHistory.append(["role": "assistant", "content": content]) - NSLog("[OpenClaw] Agent result: %@", String(content.prefix(200))) + NSLog("[OpenClaw] Agent response received") lastToolCallStatus = .completed(toolName) return .success(content) } let raw = String(data: data, encoding: .utf8) ?? "OK" conversationHistory.append(["role": "assistant", "content": raw]) - NSLog("[OpenClaw] Agent raw: %@", String(raw.prefix(200))) + NSLog("[OpenClaw] Agent raw response received") lastToolCallStatus = .completed(toolName) return .success(raw) } catch { diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift index c7222a28..b3604022 100644 --- a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift +++ b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallModels.swift @@ -85,7 +85,7 @@ enum ToolCallStatus: Equatable { enum ToolDeclarations { static func allDeclarations() -> [[String: Any]] { - return [execute] + return [execute, logSopStep] } static let execute: [String: Any] = [ @@ -103,4 +103,40 @@ enum ToolDeclarations { ] as [String: Any], "behavior": "BLOCKING" ] + + static let logSopStep: [String: Any] = [ + "name": "log_sop_step", + "description": "Log an SOP step to the external SOP processor.", + "parameters": [ + "type": "object", + "properties": [ + "step_number": [ + "type": "integer", + "description": "Current SOP step number (1-based)" + ], + "step_name": [ + "type": "string", + "description": "SOP step label to record" + ], + "action": [ + "type": "string", + "description": "Step action state: started, completed, failed, or skipped" + ], + "total_steps": [ + "type": "integer", + "description": "Total number of SOP steps" + ], + "frame_data": [ + "type": "string", + "description": "Optional current frame JPEG as raw base64 bytes (no data URI)." + ], + "notes": [ + "type": "string", + "description": "Optional operator/model notes for this step." + ] + ], + "required": ["step_name"] + ] as [String: Any], + "behavior": "NON_BLOCKING" + ] } diff --git a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift index a20babf4..acf5a52b 100644 --- a/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift +++ b/samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift @@ -29,7 +29,7 @@ class ToolCallRouter { consecutiveFailures, callId) let errorResult: ToolResult = .failure( "Tool execution is temporarily unavailable after \(consecutiveFailures) consecutive failures. " + - "Please tell the user you cannot complete this action right now and suggest they check their OpenClaw gateway connection." + "Please tell the user you cannot complete this action right now and suggest they check their Video AI Analyst gateway connection." ) let response = buildToolResponse(callId: callId, name: callName, result: errorResult) sendResponse(response) diff --git a/samples/CameraAccess/CameraAccess/Secrets.swift.example b/samples/CameraAccess/CameraAccess/Secrets.swift.example index af66099a..5e80369b 100644 --- a/samples/CameraAccess/CameraAccess/Secrets.swift.example +++ b/samples/CameraAccess/CameraAccess/Secrets.swift.example @@ -4,17 +4,33 @@ import Foundation enum Secrets { + // OPTIONAL: Stable device ID for SOP/heartbeat telemetry + static let deviceID = "YOUR_DEVICE_UUID" + + // Prototype worker identity used during ops-api bootstrap + static let workerLoginCode = "EMBC-0001" + static let workerEmail = "" + // REQUIRED: Get your key at https://aistudio.google.com/apikey static let geminiAPIKey = "YOUR_GEMINI_API_KEY" - // OPTIONAL: OpenClaw gateway config (for agentic tool-calling) - // Use your Mac's Bonjour hostname (run: scutil --get LocalHostName) + // OPTIONAL: Private OpenClaw gateway config (for agentic tool-calling / memory) static let openClawHost = "http://YOUR_MAC_HOSTNAME.local" static let openClawPort = 18789 + static let openClawTailscaleIP = "srv1338555" + static let openClawBearerToken = "" static let openClawHookToken = "YOUR_OPENCLAW_HOOK_TOKEN" static let openClawGatewayToken = "YOUR_OPENCLAW_GATEWAY_TOKEN" - // OPTIONAL: WebRTC signaling server URL (for live POV streaming) - // Run: cd samples/CameraAccess/server && npm install && npm start - static let webrtcSignalingURL = "ws://YOUR_MAC_IP:8080" + // Operations API URL for worker bootstrap, sessions, events, and media registration. + static let opsBaseURL = "https://ops.embarcaderolabs.cloud" + + // Admin API URL for worker live ingest (/api/worker/*). + static let adminBaseURL = "https://admin.embarcaderolabs.cloud" + + // Signaling service URL for live supervisor jump-in. + static let signalBaseURL = "https://signal.embarcaderolabs.cloud" + + // Backward-compatible legacy signaling URL used by older code paths. + static let webrtcSignalingURL = "wss://signal.embarcaderolabs.cloud" } diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift index 8d63a557..53f32ceb 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsManager.swift @@ -1,14 +1,25 @@ import Foundation +import Security final class SettingsManager { static let shared = SettingsManager() private let defaults = UserDefaults.standard + private let keychain = KeychainStore( + service: Bundle.main.bundleIdentifier ?? "com.embarcaderolabs.visionclaw" + ) private enum Key: String { + case deviceID + case workerLoginCode + case workerEmail case geminiAPIKey + case opsBaseURL + case adminBaseURL + case signalBaseURL case openClawHost case openClawPort + case openClawBearerToken case openClawHookToken case openClawGatewayToken case geminiSystemPrompt @@ -16,15 +27,81 @@ final class SettingsManager { case speakerOutputEnabled case videoStreamingEnabled case proactiveNotificationsEnabled + case openClawTailscaleIP } private init() {} // MARK: - Gemini + var deviceID: String { + get { + if let stored = defaults.string(forKey: Key.deviceID.rawValue), !stored.isEmpty { + return stored + } + + if !Secrets.deviceID.isEmpty, Secrets.deviceID != "YOUR_DEVICE_UUID" { + defaults.set(Secrets.deviceID, forKey: Key.deviceID.rawValue) + return Secrets.deviceID + } + + let generated = UUID().uuidString + defaults.set(generated, forKey: Key.deviceID.rawValue) + return generated + } + set { defaults.set(newValue, forKey: Key.deviceID.rawValue) } + } + + var workerLoginCode: String { + get { defaults.string(forKey: Key.workerLoginCode.rawValue) ?? Secrets.workerLoginCode } + set { defaults.set(newValue, forKey: Key.workerLoginCode.rawValue) } + } + + var workerEmail: String { + get { defaults.string(forKey: Key.workerEmail.rawValue) ?? Secrets.workerEmail } + set { defaults.set(newValue, forKey: Key.workerEmail.rawValue) } + } + var geminiAPIKey: String { - get { defaults.string(forKey: Key.geminiAPIKey.rawValue) ?? Secrets.geminiAPIKey } - set { defaults.set(newValue, forKey: Key.geminiAPIKey.rawValue) } + get { secureString(for: .geminiAPIKey, fallback: Secrets.geminiAPIKey) } + set { setSecureString(newValue, for: .geminiAPIKey) } + } + + var opsBaseURL: String { + get { + if let stored = defaults.string(forKey: Key.opsBaseURL.rawValue), + Self.isUsableRuntimeURL(stored) { + return stored + } + return Secrets.opsBaseURL + } + set { defaults.set(newValue, forKey: Key.opsBaseURL.rawValue) } + } + + var adminBaseURL: String { + get { + if let stored = defaults.string(forKey: Key.adminBaseURL.rawValue), + Self.isUsableRuntimeURL(stored) { + return stored + } + return Secrets.adminBaseURL + } + set { defaults.set(newValue, forKey: Key.adminBaseURL.rawValue) } + } + + var signalBaseURL: String { + get { + if let stored = defaults.string(forKey: Key.signalBaseURL.rawValue), + Self.isUsableRuntimeURL(stored) { + return stored + } + if let legacy = defaults.string(forKey: Key.webrtcSignalingURL.rawValue), + Self.isUsableRuntimeURL(legacy) { + return Self.normalizeSignalBaseURL(legacy) + } + return Secrets.signalBaseURL + } + set { defaults.set(newValue, forKey: Key.signalBaseURL.rawValue) } } var geminiSystemPrompt: String { @@ -48,20 +125,34 @@ final class SettingsManager { } var openClawHookToken: String { - get { defaults.string(forKey: Key.openClawHookToken.rawValue) ?? Secrets.openClawHookToken } - set { defaults.set(newValue, forKey: Key.openClawHookToken.rawValue) } + get { secureString(for: .openClawHookToken, fallback: Secrets.openClawHookToken) } + set { setSecureString(newValue, for: .openClawHookToken) } } var openClawGatewayToken: String { - get { defaults.string(forKey: Key.openClawGatewayToken.rawValue) ?? Secrets.openClawGatewayToken } - set { defaults.set(newValue, forKey: Key.openClawGatewayToken.rawValue) } + get { secureString(for: .openClawGatewayToken, fallback: Secrets.openClawGatewayToken) } + set { setSecureString(newValue, for: .openClawGatewayToken) } + } + + var openClawTailscaleIP: String { + get { defaults.string(forKey: Key.openClawTailscaleIP.rawValue) ?? Secrets.openClawTailscaleIP } + set { defaults.set(newValue, forKey: Key.openClawTailscaleIP.rawValue) } + } + + var openClawBearerToken: String { + get { secureString(for: .openClawBearerToken, fallback: Secrets.openClawBearerToken) } + set { setSecureString(newValue, for: .openClawBearerToken) } } // MARK: - WebRTC var webrtcSignalingURL: String { - get { defaults.string(forKey: Key.webrtcSignalingURL.rawValue) ?? Secrets.webrtcSignalingURL } - set { defaults.set(newValue, forKey: Key.webrtcSignalingURL.rawValue) } + get { Self.normalizeWebSocketURL(signalBaseURL) } + set { + let normalized = Self.normalizeSignalBaseURL(newValue) + defaults.set(normalized, forKey: Key.signalBaseURL.rawValue) + defaults.set(normalized, forKey: Key.webrtcSignalingURL.rawValue) + } } // MARK: - Audio @@ -88,11 +179,133 @@ final class SettingsManager { // MARK: - Reset func resetAll() { - for key in [Key.geminiAPIKey, .geminiSystemPrompt, .openClawHost, .openClawPort, - .openClawHookToken, .openClawGatewayToken, .webrtcSignalingURL, - .speakerOutputEnabled, .videoStreamingEnabled, + for key in [Key.geminiSystemPrompt, .workerLoginCode, .workerEmail, .opsBaseURL, .adminBaseURL, .signalBaseURL, + .openClawHost, .openClawPort, .webrtcSignalingURL, .openClawTailscaleIP, + .deviceID, .speakerOutputEnabled, .videoStreamingEnabled, .proactiveNotificationsEnabled] { defaults.removeObject(forKey: key.rawValue) } + for key in [Key.geminiAPIKey, .openClawBearerToken, .openClawHookToken, .openClawGatewayToken] { + defaults.removeObject(forKey: key.rawValue) + keychain.removeValue(for: key.rawValue) + } + } + + private func secureString(for key: Key, fallback: String) -> String { + if let stored = keychain.string(for: key.rawValue), !stored.isEmpty { + return stored + } + + if let legacy = defaults.string(forKey: key.rawValue), !legacy.isEmpty { + keychain.set(legacy, for: key.rawValue) + defaults.removeObject(forKey: key.rawValue) + return legacy + } + + if !fallback.isEmpty, !fallback.contains("YOUR_") { + keychain.set(fallback, for: key.rawValue) + return fallback + } + + return fallback + } + + private func setSecureString(_ value: String, for key: Key) { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + defaults.removeObject(forKey: key.rawValue) + if trimmed.isEmpty { + keychain.removeValue(for: key.rawValue) + } else { + keychain.set(trimmed, for: key.rawValue) + } + } + + private static func normalizeSignalBaseURL(_ raw: String) -> String { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return trimmed } + if trimmed.hasPrefix("wss://") { + return "https://" + String(trimmed.dropFirst("wss://".count)) + } + if trimmed.hasPrefix("ws://") { + return "http://" + String(trimmed.dropFirst("ws://".count)) + } + if trimmed.hasPrefix("https://") || trimmed.hasPrefix("http://") { + return trimmed + } + return "https://\(trimmed)" + } + + private static func isUsableRuntimeURL(_ raw: String) -> Bool { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return false } + + let blockedMarkers = [ + "YOUR_", + "YOUR_MAC_IP", + "example.com", + ] + + return !blockedMarkers.contains { trimmed.localizedCaseInsensitiveContains($0) } + } + + private static func normalizeWebSocketURL(_ raw: String) -> String { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return trimmed } + if trimmed.hasPrefix("wss://") || trimmed.hasPrefix("ws://") { + return trimmed + } + if trimmed.hasPrefix("https://") { + return "wss://" + String(trimmed.dropFirst("https://".count)) + } + if trimmed.hasPrefix("http://") { + return "ws://" + String(trimmed.dropFirst("http://".count)) + } + return "wss://\(trimmed)" + } +} + +private struct KeychainStore { + let service: String + + func string(for key: String) -> String? { + var query = baseQuery(for: key) + query[kSecReturnData as String] = kCFBooleanTrue + query[kSecMatchLimit as String] = kSecMatchLimitOne + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + guard status == errSecSuccess, + let data = result as? Data, + let value = String(data: data, encoding: .utf8) + else { + return nil + } + return value + } + + func set(_ value: String, for key: String) { + let data = Data(value.utf8) + let query = baseQuery(for: key) + let attributes = [kSecValueData as String: data] + let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) + + if status == errSecItemNotFound { + var item = query + item[kSecValueData as String] = data + item[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + SecItemAdd(item as CFDictionary, nil) + } + } + + func removeValue(for key: String) { + SecItemDelete(baseQuery(for: key) as CFDictionary) + } + + private func baseQuery(for key: String) -> [String: Any] { + [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: key, + ] } } diff --git a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift index 8e22fe33..d3045171 100644 --- a/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift +++ b/samples/CameraAccess/CameraAccess/Settings/SettingsView.swift @@ -4,9 +4,15 @@ struct SettingsView: View { @Environment(\.dismiss) private var dismiss private let settings = SettingsManager.shared + @State private var workerLoginCode: String = "" + @State private var workerEmail: String = "" @State private var geminiAPIKey: String = "" + @State private var opsBaseURL: String = "" + @State private var adminBaseURL: String = "" + @State private var signalBaseURL: String = "" @State private var openClawHost: String = "" @State private var openClawPort: String = "" + @State private var openClawTailscaleIP: String = "" @State private var openClawHookToken: String = "" @State private var openClawGatewayToken: String = "" @State private var geminiSystemPrompt: String = "" @@ -19,12 +25,71 @@ struct SettingsView: View { var body: some View { NavigationView { Form { + Section(header: Text("Worker")) { + VStack(alignment: .leading, spacing: 4) { + Text("Worker Email") + .font(.caption) + .foregroundColor(.secondary) + TextField("worker@company.com", text: $workerEmail) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.emailAddress) + .textInputAutocapitalization(.never) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Login Code") + .font(.caption) + .foregroundColor(.secondary) + TextField("EMBC-0001", text: $workerLoginCode) + .autocapitalization(.none) + .disableAutocorrection(true) + .font(.system(.body, design: .monospaced)) + } + } + + Section(header: Text("Operations Backend"), footer: Text("Ops Base URL handles worker bootstrap, sessions, events, interventions, and evidence uploads. Admin Base URL handles the /api/worker live ingest endpoints used for live frames, heartbeats, and final video replay sync.")) { + VStack(alignment: .leading, spacing: 4) { + Text("Ops Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://ops.embarcaderolabs.cloud", text: $opsBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Admin Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://admin.embarcaderolabs.cloud", text: $adminBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + + VStack(alignment: .leading, spacing: 4) { + Text("Signal Base URL") + .font(.caption) + .foregroundColor(.secondary) + TextField("https://signal.embarcaderolabs.cloud", text: $signalBaseURL) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.URL) + .font(.system(.body, design: .monospaced)) + } + } + Section(header: Text("Gemini API")) { VStack(alignment: .leading, spacing: 4) { Text("API Key") .font(.caption) .foregroundColor(.secondary) - TextField("Enter Gemini API key", text: $geminiAPIKey) + SecureField("Enter Gemini API key", text: $geminiAPIKey) .autocapitalization(.none) .disableAutocorrection(true) .font(.system(.body, design: .monospaced)) @@ -37,7 +102,7 @@ struct SettingsView: View { .frame(minHeight: 200) } - Section(header: Text("OpenClaw"), footer: Text("Connect to an OpenClaw gateway running on your Mac for agentic tool-calling.")) { + Section(header: Text("Video AI Analyst"), footer: Text("Private analyst connectivity stays separate from ops-api so memory links and agent-style actions can evolve independently.")) { VStack(alignment: .leading, spacing: 4) { Text("Host") .font(.caption) @@ -58,11 +123,22 @@ struct SettingsView: View { .font(.system(.body, design: .monospaced)) } + VStack(alignment: .leading, spacing: 4) { + Text("Tailscale Host") + .font(.caption) + .foregroundColor(.secondary) + TextField("srv1338555", text: $openClawTailscaleIP) + .autocapitalization(.none) + .disableAutocorrection(true) + .keyboardType(.numbersAndPunctuation) + .font(.system(.body, design: .monospaced)) + } + VStack(alignment: .leading, spacing: 4) { Text("Hook Token") .font(.caption) .foregroundColor(.secondary) - TextField("Hook token", text: $openClawHookToken) + SecureField("Hook token", text: $openClawHookToken) .autocapitalization(.none) .disableAutocorrection(true) .font(.system(.body, design: .monospaced)) @@ -72,7 +148,7 @@ struct SettingsView: View { Text("Gateway Token") .font(.caption) .foregroundColor(.secondary) - TextField("Gateway auth token", text: $openClawGatewayToken) + SecureField("Gateway auth token", text: $openClawGatewayToken) .autocapitalization(.none) .disableAutocorrection(true) .font(.system(.body, design: .monospaced)) @@ -100,7 +176,7 @@ struct SettingsView: View { Toggle("Video Streaming", isOn: $videoStreamingEnabled) } - Section(header: Text("Notifications"), footer: Text("Receive proactive updates from OpenClaw (heartbeat, scheduled tasks) spoken through the glasses.")) { + Section(header: Text("Notifications"), footer: Text("Receive proactive updates from Video AI Analyst (heartbeat, scheduled tasks) spoken through the glasses.")) { Toggle("Proactive Notifications", isOn: $proactiveNotificationsEnabled) } @@ -143,10 +219,16 @@ struct SettingsView: View { } private func loadCurrentValues() { + workerLoginCode = settings.workerLoginCode + workerEmail = settings.workerEmail geminiAPIKey = settings.geminiAPIKey + opsBaseURL = settings.opsBaseURL + adminBaseURL = settings.adminBaseURL + signalBaseURL = settings.signalBaseURL geminiSystemPrompt = settings.geminiSystemPrompt openClawHost = settings.openClawHost openClawPort = String(settings.openClawPort) + openClawTailscaleIP = settings.openClawTailscaleIP openClawHookToken = settings.openClawHookToken openClawGatewayToken = settings.openClawGatewayToken webrtcSignalingURL = settings.webrtcSignalingURL @@ -156,12 +238,18 @@ struct SettingsView: View { } private func save() { + settings.workerLoginCode = workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) + settings.workerEmail = workerEmail.trimmingCharacters(in: .whitespacesAndNewlines) settings.geminiAPIKey = geminiAPIKey.trimmingCharacters(in: .whitespacesAndNewlines) + settings.opsBaseURL = opsBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + settings.adminBaseURL = adminBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + settings.signalBaseURL = signalBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) settings.geminiSystemPrompt = geminiSystemPrompt.trimmingCharacters(in: .whitespacesAndNewlines) settings.openClawHost = openClawHost.trimmingCharacters(in: .whitespacesAndNewlines) if let port = Int(openClawPort.trimmingCharacters(in: .whitespacesAndNewlines)) { settings.openClawPort = port } + settings.openClawTailscaleIP = openClawTailscaleIP.trimmingCharacters(in: .whitespacesAndNewlines) settings.openClawHookToken = openClawHookToken.trimmingCharacters(in: .whitespacesAndNewlines) settings.openClawGatewayToken = openClawGatewayToken.trimmingCharacters(in: .whitespacesAndNewlines) settings.webrtcSignalingURL = webrtcSignalingURL.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift index 29203cd8..df5babf0 100644 --- a/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/ViewModels/StreamSessionViewModel.swift @@ -19,6 +19,9 @@ import CoreMedia import CoreVideo import MWDATCamera import MWDATCore +import AVFoundation +import Combine +import Speech import SwiftUI import VideoToolbox @@ -33,6 +36,1313 @@ enum StreamingMode { case iPhone } +enum ChecklistCompletionSource: String, Codable { + case pending + case manual + case voice + case vision +} + +enum SopTerminationStatus: String, Codable { + case timedOut = "timed_out" + case allItemsChecked = "all_items_checked" + case userEnded = "user_ended" +} + +enum DossierPipelineStatusKind { + case info + case active + case success + case error +} + +struct SOPTemplate: Identifiable, Hashable { + let id: UUID + let remoteID: String? + let name: String + let steps: [SOPStepTemplate] + let estimatedDuration: Double + let shiftID: String? + let shiftName: String? + let packageID: String? + let packageRunID: String? + let packageTitle: String? + let packageVersion: Int? + let sopVersion: Int? + let sourceType: String + let sortOrder: Int + let required: Bool + + var items: [String] { + steps.map(\.title) + } + + var validationSummary: String { + let labels = Array(Set(steps.map { $0.validation.uppercased() })).sorted() + return labels.isEmpty ? "NO VALIDATION" : labels.joined(separator: " + ") + } + + init( + id: UUID = UUID(), + remoteID: String? = nil, + name: String, + steps: [SOPStepTemplate]? = nil, + items: [String] = [], + estimatedDuration: Double = 15.0, + shiftID: String? = nil, + shiftName: String? = nil, + packageID: String? = nil, + packageRunID: String? = nil, + packageTitle: String? = nil, + packageVersion: Int? = nil, + sopVersion: Int? = nil, + sourceType: String = "standalone", + sortOrder: Int = 0, + required: Bool = true + ) { + self.id = id + self.remoteID = remoteID + self.name = name + let resolvedSteps = steps ?? items.enumerated().map { index, item in + SOPStepTemplate( + id: ChecklistItemState.normalizedItemID(from: item), + order: index + 1, + title: item, + aiPrompt: "Look at the image and confirm whether \"\(item)\" has been completed." + ) + } + self.steps = resolvedSteps.sorted { $0.order < $1.order } + self.estimatedDuration = estimatedDuration + self.shiftID = shiftID + self.shiftName = shiftName + self.packageID = packageID + self.packageRunID = packageRunID + self.packageTitle = packageTitle + self.packageVersion = packageVersion + self.sopVersion = sopVersion + self.sourceType = sourceType + self.sortOrder = sortOrder + self.required = required + } +} + +private func validRemoteUUID(_ value: String?) -> String? { + guard let value, + UUID(uuidString: value) != nil + else { return nil } + return value +} + +struct SOPStepTemplate: Identifiable, Hashable { + let id: String + let order: Int + let title: String + let description: String + let duration: String + let validation: String + let critical: Bool + let aiPrompt: String + let expectedObjects: [String] + let allowManualComplete: Bool + + init( + id: String, + order: Int, + title: String, + description: String = "", + duration: String = "30s", + validation: String = "visual", + critical: Bool = false, + aiPrompt: String, + expectedObjects: [String] = [], + allowManualComplete: Bool = true + ) { + self.id = id + self.order = order + self.title = title + self.description = description + self.duration = duration + self.validation = validation + self.critical = critical + self.aiPrompt = aiPrompt + self.expectedObjects = expectedObjects + self.allowManualComplete = allowManualComplete + } +} + +private struct RemoteSOPListResponse: Decodable { + let version: String? + + private let sops: [RemoteSOP]? + private let data: [RemoteSOP]? + private let templates: [RemoteSOP]? + private let sopTemplates: [RemoteSOP]? + + var allSOPs: [RemoteSOP] { + sops ?? data ?? templates ?? sopTemplates ?? [] + } + + private enum CodingKeys: String, CodingKey { + case version + case sops + case data + case templates + case sopTemplates = "sop_templates" + } +} + +private struct RemoteSOP: Decodable { + let id: String + let name: String + let items: [RemoteSOPItem] + let estimatedDuration: Double? + let updatedAt: Date? + let createdAt: Date? + + private enum CodingKeys: String, CodingKey { + case id + case uuid + case sopID = "sop_id" + case name + case title + case items + case estimatedDuration = "estimatedDuration" + case estimatedDurationSnake = "estimated_duration" + case duration + case updatedAt = "updatedAt" + case updatedAtSnake = "updated_at" + case modifiedAtSnake = "modified_at" + case createdAt = "createdAt" + case createdAtSnake = "created_at" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let decodedID = try container.decodeIfPresent(String.self, forKey: .id) + let decodedUUID = try container.decodeIfPresent(String.self, forKey: .uuid) + let decodedSopID = try container.decodeIfPresent(String.self, forKey: .sopID) + id = decodedID ?? decodedUUID ?? decodedSopID ?? UUID().uuidString + + let decodedName = try container.decodeIfPresent(String.self, forKey: .name) + let decodedTitle = try container.decodeIfPresent(String.self, forKey: .title) + name = decodedName ?? decodedTitle ?? "Untitled SOP" + + if let decodedItems = try container.decodeIfPresent([RemoteSOPItem].self, forKey: .items) { + items = decodedItems + } else if let stringItems = try container.decodeIfPresent([String].self, forKey: .items) { + items = stringItems.map { RemoteSOPItem(name: $0) } + } else { + items = [] + } + + if let estimate = try container.decodeIfPresent(Double.self, forKey: .estimatedDuration) { + estimatedDuration = estimate + } else if let estimate = try container.decodeIfPresent(Double.self, forKey: .estimatedDurationSnake) { + estimatedDuration = estimate + } else if let estimate = try container.decodeIfPresent(Double.self, forKey: .duration) { + estimatedDuration = estimate + } else if let estimateInt = try container.decodeIfPresent(Int.self, forKey: .estimatedDuration) { + estimatedDuration = Double(estimateInt) + } else if let estimateInt = try container.decodeIfPresent(Int.self, forKey: .estimatedDurationSnake) { + estimatedDuration = Double(estimateInt) + } else if let estimateInt = try container.decodeIfPresent(Int.self, forKey: .duration) { + estimatedDuration = Double(estimateInt) + } else { + estimatedDuration = nil + } + + let decodedUpdatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt) + let decodedUpdatedAtSnake = try container.decodeIfPresent(String.self, forKey: .updatedAtSnake) + let decodedModifiedAtSnake = try container.decodeIfPresent(String.self, forKey: .modifiedAtSnake) + let updatedRaw = decodedUpdatedAt ?? decodedUpdatedAtSnake ?? decodedModifiedAtSnake + + let decodedCreatedAt = try container.decodeIfPresent(String.self, forKey: .createdAt) + let decodedCreatedAtSnake = try container.decodeIfPresent(String.self, forKey: .createdAtSnake) + let createdRaw = decodedCreatedAt ?? decodedCreatedAtSnake + + updatedAt = Self.parseDate(updatedRaw) + createdAt = Self.parseDate(createdRaw) + } + + private static func parseDate(_ raw: String?) -> Date? { + guard let raw, !raw.isEmpty else { return nil } + + let isoFormatter = ISO8601DateFormatter() + isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + if let date = isoFormatter.date(from: raw) { return date } + + let fallbackISO = ISO8601DateFormatter() + if let date = fallbackISO.date(from: raw) { return date } + + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter.date(from: raw) + } +} + +private struct RemoteSOPItem: Decodable { + let name: String + + init(name: String) { + self.name = name + } + + private enum CodingKeys: String, CodingKey { + case name + case title + case label + case item + } + + init(from decoder: Decoder) throws { + if let singleValueContainer = try? decoder.singleValueContainer(), + let raw = try? singleValueContainer.decode(String.self) + { + name = raw + return + } + + let container = try decoder.container(keyedBy: CodingKeys.self) + let decodedName = try container.decodeIfPresent(String.self, forKey: .name) + let decodedTitle = try container.decodeIfPresent(String.self, forKey: .title) + let decodedLabel = try container.decodeIfPresent(String.self, forKey: .label) + let decodedItem = try container.decodeIfPresent(String.self, forKey: .item) + name = decodedName ?? decodedTitle ?? decodedLabel ?? decodedItem ?? "Unknown Item" + } +} + +private final class SopVideoRecorder: @unchecked Sendable { + private let queue = DispatchQueue(label: "sop.video.recorder", qos: .userInitiated) + private var writer: AVAssetWriter? + private var writerInput: AVAssetWriterInput? + private var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor? + private var recordingStartHostTime: CFTimeInterval? + private(set) var outputURL: URL? + private var isFinishing = false + private var appendedFrameCount = 0 + private let sourcePixelFormat = VideoFrameBufferFactory.pixelFormat + + func appendFrame(_ image: UIImage) { + queue.async { [weak self] in + guard let self, !self.isFinishing else { return } + guard let cgImage = image.cgImage else { return } + self.configureWriterIfNeeded(width: cgImage.width, height: cgImage.height) + + guard + let pixelBuffer = VideoFrameBufferFactory.makePixelBuffer( + from: image, + using: self.pixelBufferAdaptor?.pixelBufferPool + ) + else { + return + } + + self.appendPixelBufferInternal(pixelBuffer) + } + } + + func appendPixelBuffer(_ pixelBuffer: CVPixelBuffer) { + queue.async { [weak self] in + guard let self, !self.isFinishing else { return } + self.configureWriterIfNeeded( + width: CVPixelBufferGetWidth(pixelBuffer), + height: CVPixelBufferGetHeight(pixelBuffer) + ) + self.appendPixelBufferInternal(pixelBuffer) + } + } + + func finishRecording() async -> URL? { + await withCheckedContinuation { continuation in + queue.async { [weak self] in + guard let self else { + continuation.resume(returning: nil) + return + } + + NSLog( + "[SOPRecorder] finishRecording called (frames=%d, hasWriter=%@, outputURL=%@)", + self.appendedFrameCount, + self.writer == nil ? "no" : "yes", + self.outputURL?.path ?? "nil") + + guard let writer = self.writer, + let writerInput = self.writerInput, + writer.status == .writing else { + if let writer = self.writer { + NSLog("[SOPRecorder] finishRecording returning nil because writer status=%d", writer.status.rawValue) + } else { + NSLog("[SOPRecorder] finishRecording returning nil because writer was never created") + } + continuation.resume(returning: nil) + return + } + + self.isFinishing = true + writerInput.markAsFinished() + writer.finishWriting { + NSLog( + "[SOPRecorder] finishWriting completed (status=%d, outputURL=%@)", + writer.status.rawValue, + self.outputURL?.path ?? "nil") + continuation.resume(returning: writer.status == .completed ? self.outputURL : nil) + } + } + } + } + + private func appendPixelBufferInternal(_ pixelBuffer: CVPixelBuffer) { + guard let writer = writer, + writer.status == .writing, + let writerInput = writerInput, + let adaptor = pixelBufferAdaptor, + writerInput.isReadyForMoreMediaData, + let start = recordingStartHostTime else { + if writer == nil { + NSLog("[SOPRecorder] Dropping frame because writer was never configured") + } else if let writer { + NSLog("[SOPRecorder] Dropping frame because writer is not writable (status=%d)", writer.status.rawValue) + } + return + } + + let elapsed = CACurrentMediaTime() - start + let presentationTime = CMTime(seconds: max(0, elapsed), preferredTimescale: 600) + let bufferForWriter = + VideoFrameBufferFactory.copyPixelBuffer(pixelBuffer, using: adaptor.pixelBufferPool) + ?? pixelBuffer + + let appended = adaptor.append(bufferForWriter, withPresentationTime: presentationTime) + if appended { + appendedFrameCount += 1 + if appendedFrameCount == 1 { + NSLog("[SOPRecorder] First frame appended successfully") + } else if appendedFrameCount % 60 == 0 { + NSLog("[SOPRecorder] Appended %d frames", appendedFrameCount) + } + } else { + NSLog( + "[SOPRecorder] Failed appending frame at %.3fs (writer status=%d)", + elapsed, + writer.status.rawValue + ) + } + } + + private func configureWriterIfNeeded(width: Int, height: Int) { + guard writer == nil else { return } + + let size = Self.normalizedSize(width: width, height: height) + guard size.width > 0, size.height > 0 else { + NSLog("[SOPRecorder] Invalid normalized size: %@", NSCoder.string(for: size)) + return + } + + let fileURL = FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(UUID().uuidString)") + .appendingPathExtension("mp4") + + try? FileManager.default.removeItem(at: fileURL) + + NSLog("[SOPRecorder] Creating writer at %@ with size %@", fileURL.path, NSCoder.string(for: size)) + + guard let writer = try? AVAssetWriter(outputURL: fileURL, fileType: .mp4) else { + NSLog("[SOPRecorder] Failed to create AVAssetWriter") + return + } + + let outputSettings: [String: Any] = [ + AVVideoCodecKey: AVVideoCodecType.h264, + AVVideoWidthKey: Int(size.width), + AVVideoHeightKey: Int(size.height), + AVVideoCompressionPropertiesKey: [ + AVVideoAverageBitRateKey: 2_500_000, + AVVideoProfileLevelKey: AVVideoProfileLevelH264MainAutoLevel + ] + ] + + let input = AVAssetWriterInput(mediaType: .video, outputSettings: outputSettings) + input.expectsMediaDataInRealTime = true + + let sourceAttributes: [String: Any] = [ + kCVPixelBufferPixelFormatTypeKey as String: Int(sourcePixelFormat), + kCVPixelBufferWidthKey as String: Int(size.width), + kCVPixelBufferHeightKey as String: Int(size.height), + kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], + ] + + let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: sourceAttributes) + + guard writer.canAdd(input) else { + NSLog("[SOPRecorder] Writer cannot add AVAssetWriterInput") + return + } + writer.add(input) + + guard writer.startWriting() else { + NSLog("[SOPRecorder] startWriting failed: %@", writer.error?.localizedDescription ?? "unknown") + return + } + writer.startSession(atSourceTime: .zero) + NSLog("[SOPRecorder] Writer started successfully") + + self.writer = writer + self.writerInput = input + self.pixelBufferAdaptor = adaptor + self.recordingStartHostTime = CACurrentMediaTime() + self.outputURL = fileURL + } + + private static func normalizedSize(width: Int, height: Int) -> CGSize { + var width = max(2, width) + var height = max(2, height) + if width % 2 != 0 { width += 1 } + if height % 2 != 0 { height += 1 } + return CGSize(width: width, height: height) + } +} + +struct ChecklistItemState: Identifiable, Codable, Hashable { + let id: UUID + let itemID: String + let name: String + let description: String + let duration: String + let validation: String + let critical: Bool + let aiPrompt: String + let expectedObjects: [String] + let allowManualComplete: Bool + var isChecked: Bool + var completionSource: ChecklistCompletionSource + + init( + id: UUID = UUID(), + itemID: String? = nil, + name: String, + description: String = "", + duration: String = "30s", + validation: String = "visual", + critical: Bool = false, + aiPrompt: String? = nil, + expectedObjects: [String] = [], + allowManualComplete: Bool = true, + isChecked: Bool = false, + completionSource: ChecklistCompletionSource = .pending + ) { + self.id = id + self.itemID = itemID ?? ChecklistItemState.normalizedItemID(from: name) + self.name = name + self.description = description + self.duration = duration + self.validation = validation + self.critical = critical + self.aiPrompt = aiPrompt ?? "Look at the image and confirm whether \"\(name)\" has been completed." + self.expectedObjects = expectedObjects + self.allowManualComplete = allowManualComplete + self.isChecked = isChecked + self.completionSource = completionSource + } + + static func normalizedItemID(from name: String) -> String { + let lowered = name.lowercased() + let filtered = lowered.map { ch in + ch.isLetter || ch.isNumber ? ch : "_" + } + let collapsed = String(filtered) + .replacingOccurrences(of: "__+", with: "_", options: .regularExpression) + .trimmingCharacters(in: CharacterSet(charactersIn: "_")) + return collapsed.isEmpty ? UUID().uuidString.lowercased() : collapsed + } +} + +private enum WorkerLiveLogger { + static func log( + _ event: String, + sessionID: String? = nil, + roomCode: String? = nil, + assetID: String? = nil, + assetType: String? = nil, + bucket: String? = nil, + path: String? = nil, + byteSize: Int? = nil, + retryCount: Int? = nil, + uploadState: String? = nil, + error: String? = nil + ) { + let payload: [String: Any] = [ + "event": event, + "sessionId": sessionID ?? NSNull(), + "roomCode": roomCode ?? NSNull(), + "assetId": assetID ?? NSNull(), + "assetType": assetType ?? NSNull(), + "bucket": bucket ?? NSNull(), + "path": path ?? NSNull(), + "byteSize": byteSize ?? NSNull(), + "retryCount": retryCount ?? NSNull(), + "uploadState": uploadState ?? NSNull(), + "error": error ?? NSNull() + ] + + guard JSONSerialization.isValidJSONObject(payload), + let data = try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]), + let encoded = String(data: data, encoding: .utf8) + else { + NSLog("[worker-live] %@", event) + return + } + + NSLog("[worker-live] %@", encoded) + } +} + +struct WorkerMediaUploadResult: Equatable { + let assetType: String + let assetID: String? + let bucket: String? + let path: String? + let byteSize: Int + let uploadState: String + let errorMessage: String? + + var succeeded: Bool { + uploadState == "uploaded" + } +} + +actor WorkerAdminLiveSessionCoordinator { + typealias Sleeper = @Sendable (UInt64) async -> Void + typealias FileLoader = @Sendable (URL) async -> Data? + + private let api: WorkerAdminAPI + private let heartbeatIntervalNanoseconds: UInt64 + private let sleeper: Sleeper + private let fileLoader: FileLoader + + private var sessionID: String? + private var roomCode: String? + private var currentStepIndex: Int = 0 + private var helpRequested: Bool = false + private var lastFrameBucket: String? + private var lastFramePath: String? + private var heartbeatTask: Task? + private var queuedFrameData: Data? + private var frameUploadTask: Task? + + init( + api: WorkerAdminAPI, + sessionID: String? = nil, + heartbeatIntervalNanoseconds: UInt64 = 7_000_000_000, + sleeper: @escaping Sleeper = { nanoseconds in + guard nanoseconds > 0 else { return } + try? await Task.sleep(nanoseconds: nanoseconds) + }, + fileLoader: @escaping FileLoader = { url in + await Task.detached(priority: .utility) { + try? Data(contentsOf: url) + }.value + } + ) { + self.api = api + self.sessionID = sessionID + self.heartbeatIntervalNanoseconds = heartbeatIntervalNanoseconds + self.sleeper = sleeper + self.fileLoader = fileLoader + } + + func start( + sessionID: String, + currentStepIndex: Int, + helpRequested: Bool, + roomCode: String? = nil + ) async { + self.sessionID = sessionID + self.currentStepIndex = currentStepIndex + self.helpRequested = helpRequested + if let roomCode = Self.trimmed(roomCode) { + self.roomCode = roomCode + } + + if heartbeatTask == nil, heartbeatIntervalNanoseconds > 0 { + heartbeatTask = Task { [heartbeatIntervalNanoseconds] in + while !Task.isCancelled { + await self.sleeper(heartbeatIntervalNanoseconds) + if Task.isCancelled { break } + await self.sendHeartbeat() + } + } + } + + await sendHeartbeat() + } + + func updateRoomCode(_ roomCode: String?, sendImmediateHeartbeat: Bool = true) async { + guard let roomCode = Self.trimmed(roomCode) else { return } + self.roomCode = roomCode + if sendImmediateHeartbeat { + await sendHeartbeat() + } + } + + func updateCurrentStepIndex(_ currentStepIndex: Int, sendImmediateHeartbeat: Bool = false) async { + self.currentStepIndex = currentStepIndex + if sendImmediateHeartbeat { + await sendHeartbeat() + } + } + + func updateHelpRequested(_ helpRequested: Bool, sendImmediateHeartbeat: Bool = true) async { + self.helpRequested = helpRequested + if sendImmediateHeartbeat { + await sendHeartbeat() + } + } + + func enqueueFrameUpload(data: Data) async { + queuedFrameData = data + guard frameUploadTask == nil else { return } + frameUploadTask = Task { + await self.drainQueuedFrames() + } + } + + func uploadVideoRecording( + from fileURL: URL?, + source: String = "session-recording" + ) async -> WorkerMediaUploadResult { + let byteSize: Int + let data: Data? + let missingDataError: String + + if let fileURL { + data = await fileLoader(fileURL) + if let data { + byteSize = data.count + missingDataError = data.isEmpty ? "Recording file is empty." : "Recording file could not be loaded." + } else { + byteSize = 0 + missingDataError = "Recording file could not be loaded." + } + } else { + data = nil + byteSize = 0 + missingDataError = "Recording file was not created." + } + + return await uploadAsset( + assetType: "video", + filename: "recording.mp4", + contentType: "video/mp4", + data: data, + byteSize: byteSize, + missingDataError: missingDataError, + source: source + ) + } + + func completeSession( + videoFileURL: URL?, + videoSource: String = "session-recording", + onBeforeMarkEnded: () async -> Void + ) async -> WorkerMediaUploadResult { + queuedFrameData = nil + frameUploadTask?.cancel() + frameUploadTask = nil + + let result = await uploadVideoRecording(from: videoFileURL, source: videoSource) + await sendHeartbeat() + await onBeforeMarkEnded() + + heartbeatTask?.cancel() + heartbeatTask = nil + return result + } + + func stop() async { + queuedFrameData = nil + frameUploadTask?.cancel() + frameUploadTask = nil + heartbeatTask?.cancel() + heartbeatTask = nil + } + + private func sendHeartbeat() async { + guard let sessionID else { return } + + let heartbeat = WorkerLiveHeartbeatRequest( + sessionID: sessionID, + webrtcRoomCode: roomCode, + currentStepIndex: currentStepIndex, + helpRequested: helpRequested, + status: "active", + lastFrameBucket: lastFrameBucket, + lastFramePath: lastFramePath + ) + + WorkerLiveLogger.log( + "heartbeat_sent", + sessionID: sessionID, + roomCode: roomCode, + bucket: lastFrameBucket, + path: lastFramePath, + uploadState: "active" + ) + + do { + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetType: nil, + bucket: lastFrameBucket, + path: lastFramePath, + uploadState: "active" + ) { + try await api.sendWorkerLiveHeartbeat(heartbeat) + } + + WorkerLiveLogger.log( + "heartbeat_result", + sessionID: sessionID, + roomCode: roomCode, + bucket: lastFrameBucket, + path: lastFramePath, + uploadState: "active" + ) + } catch { + WorkerLiveLogger.log( + "heartbeat_result", + sessionID: sessionID, + roomCode: roomCode, + bucket: lastFrameBucket, + path: lastFramePath, + uploadState: "active", + error: error.localizedDescription + ) + } + } + + private func drainQueuedFrames() async { + while !Task.isCancelled { + guard let frameData = queuedFrameData else { break } + queuedFrameData = nil + + let result = await uploadAsset( + assetType: "frame", + filename: "last-frame.jpg", + contentType: "image/jpeg", + data: frameData, + byteSize: frameData.count, + missingDataError: "Frame JPEG data was empty.", + source: "live-preview" + ) + + if result.succeeded { + lastFrameBucket = result.bucket + lastFramePath = result.path + if !Task.isCancelled { + await sendHeartbeat() + } + } + } + + frameUploadTask = nil + if queuedFrameData != nil, !Task.isCancelled { + frameUploadTask = Task { + await self.drainQueuedFrames() + } + } + } + + private func uploadAsset( + assetType: String, + filename: String, + contentType: String, + data: Data?, + byteSize: Int, + missingDataError: String, + source: String? = nil + ) async -> WorkerMediaUploadResult { + guard let sessionID else { + return WorkerMediaUploadResult( + assetType: assetType, + assetID: nil, + bucket: nil, + path: nil, + byteSize: byteSize, + uploadState: "failed", + errorMessage: "Session ID missing." + ) + } + + let logPrefix = assetType == "frame" ? "frame" : "video" + + do { + let target = try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetType: assetType, + byteSize: byteSize, + uploadState: "pending" + ) { + try await api.requestWorkerMediaUploadTarget( + sessionID: sessionID, + assetType: assetType, + filename: filename, + contentType: contentType, + byteSize: byteSize, + source: source + ) + } + + WorkerLiveLogger.log( + "\(logPrefix)_upload_target", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "pending" + ) + + guard let data, !data.isEmpty else { + WorkerLiveLogger.log( + "\(logPrefix)_upload_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: missingDataError + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: missingDataError + ) + } + + do { + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "pending" + ) { + try await api.uploadBinary(to: target, data: data, contentType: contentType) + } + + WorkerLiveLogger.log( + "\(logPrefix)_upload_success", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "pending" + ) + + do { + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded" + ) { + try await api.finalizeWorkerMediaUpload( + WorkerMediaFinalizeRequest( + assetID: target.assetID, + sessionID: sessionID, + bucket: target.bucket, + path: target.path, + status: "uploaded", + byteSize: byteSize, + error: nil + ) + ) + } + + WorkerLiveLogger.log( + "\(logPrefix)_finalize_success", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded" + ) + + return WorkerMediaUploadResult( + assetType: assetType, + assetID: target.assetID, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded", + errorMessage: nil + ) + } catch { + let finalizeError = "Finalize uploaded failed: \(error.localizedDescription)" + WorkerLiveLogger.log( + "\(logPrefix)_finalize_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "uploaded", + error: finalizeError + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: finalizeError + ) + } + } catch { + let uploadError = error.localizedDescription + WorkerLiveLogger.log( + "\(logPrefix)_upload_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: uploadError + ) + return await finalizeFailure( + logPrefix: logPrefix, + sessionID: sessionID, + assetType: assetType, + target: target, + byteSize: byteSize, + errorMessage: uploadError + ) + } + } catch { + WorkerLiveLogger.log( + "\(logPrefix)_upload_failure", + sessionID: sessionID, + roomCode: roomCode, + assetType: assetType, + byteSize: byteSize, + uploadState: "failed", + error: error.localizedDescription + ) + + return WorkerMediaUploadResult( + assetType: assetType, + assetID: nil, + bucket: nil, + path: nil, + byteSize: byteSize, + uploadState: "failed", + errorMessage: error.localizedDescription + ) + } + } + + private func finalizeFailure( + logPrefix: String, + sessionID: String, + assetType: String, + target: WorkerMediaUploadTarget, + byteSize: Int, + errorMessage: String + ) async -> WorkerMediaUploadResult { + do { + try await retry( + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed" + ) { + try await api.finalizeWorkerMediaUpload( + WorkerMediaFinalizeRequest( + assetID: target.assetID, + sessionID: sessionID, + bucket: target.bucket, + path: target.path, + status: "failed", + byteSize: byteSize, + error: errorMessage + ) + ) + } + + WorkerLiveLogger.log( + "\(logPrefix)_finalize_success", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: errorMessage + ) + } catch { + WorkerLiveLogger.log( + "\(logPrefix)_finalize_failure", + sessionID: sessionID, + roomCode: roomCode, + assetID: target.assetID, + assetType: assetType, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + error: error.localizedDescription + ) + } + + return WorkerMediaUploadResult( + assetType: assetType, + assetID: target.assetID, + bucket: target.bucket, + path: target.path, + byteSize: byteSize, + uploadState: "failed", + errorMessage: errorMessage + ) + } + + private func retry( + sessionID: String?, + roomCode: String?, + assetID: String? = nil, + assetType: String?, + bucket: String? = nil, + path: String? = nil, + byteSize: Int? = nil, + uploadState: String? = nil, + operation: () async throws -> T + ) async throws -> T { + let backoffSchedule: [UInt64] = [750_000_000, 1_500_000_000, 3_000_000_000] + var attempt = 0 + + while true { + do { + return try await operation() + } catch { + guard !Task.isCancelled, attempt < backoffSchedule.count, Self.isTransient(error) else { + throw error + } + + let retryCount = attempt + 1 + WorkerLiveLogger.log( + "retry_scheduled", + sessionID: sessionID, + roomCode: roomCode, + assetID: assetID, + assetType: assetType, + bucket: bucket, + path: path, + byteSize: byteSize, + retryCount: retryCount, + uploadState: uploadState, + error: error.localizedDescription + ) + + await sleeper(backoffSchedule[attempt]) + attempt += 1 + } + } + } + + private static func trimmed(_ value: String?) -> String? { + guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines), + !value.isEmpty + else { + return nil + } + return value + } + + private static func isTransient(_ error: Error) -> Bool { + if error is CancellationError { + return false + } + + if let urlError = error as? URLError { + switch urlError.code { + case .timedOut, + .cannotFindHost, + .cannotConnectToHost, + .networkConnectionLost, + .dnsLookupFailed, + .notConnectedToInternet, + .resourceUnavailable, + .dataNotAllowed, + .callIsActive, + .internationalRoamingOff: + return true + default: + return false + } + } + + if let opsError = error as? OpsAPIError { + switch opsError { + case .invalidResponse: + return true + case .server(let statusCode, _): + return [408, 409, 425, 429, 500, 502, 503, 504].contains(statusCode) + case .notConfigured, .invalidURL, .missingWorkerSession, .missingWorkerBearerToken: + return false + } + } + + return false + } +} + +struct ShippedSessionRecord: Identifiable, Codable { + let id: UUID + let timestamp: Date + let sopName: String + let status: String + + init(id: UUID = UUID(), timestamp: Date, sopName: String, status: String) { + self.id = id + self.timestamp = timestamp + self.sopName = sopName + self.status = status + } + + var timestampText: String { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .medium + return formatter.string(from: timestamp) + } +} + +private struct PendingWorkerRecording: Codable, Equatable { + let sessionID: String + let filePath: String +} + +private struct IPhoneAnalysisFrameEnvelope: @unchecked Sendable { + let image: UIImage + let shouldRecordAudit: Bool + let enqueuedAt: CFTimeInterval +} + +private final class IPhoneAnalysisLane: @unchecked Sendable { + private let queue = DispatchQueue( + label: "visionclaw.iphone.analysis-lane", + qos: .userInitiated + ) + private var pendingFrame: IPhoneAnalysisFrameEnvelope? + private var isProcessing = false + private var submittedCount: Int64 = 0 + private var processedCount: Int64 = 0 + private var droppedCount: Int64 = 0 + + var onFrameReady: (@Sendable (IPhoneAnalysisFrameEnvelope, @escaping @Sendable () -> Void) -> Void)? + + func submit(_ image: UIImage, shouldRecordAudit: Bool) { + queue.async { + self.submittedCount += 1 + + if self.pendingFrame != nil { + self.droppedCount += 1 + } + + self.pendingFrame = IPhoneAnalysisFrameEnvelope( + image: image, + shouldRecordAudit: shouldRecordAudit, + enqueuedAt: CACurrentMediaTime() + ) + + guard !self.isProcessing else { return } + self.isProcessing = true + self.drain() + } + } + + func reset() { + queue.async { + self.pendingFrame = nil + self.isProcessing = false + self.submittedCount = 0 + self.processedCount = 0 + self.droppedCount = 0 + } + } + + private func drain() { + guard let frame = pendingFrame else { + isProcessing = false + return + } + + pendingFrame = nil + let completion: @Sendable () -> Void = { [weak self] in + guard let self else { return } + self.queue.async { + self.processedCount += 1 + self.logIfNeeded(lastLatencyMs: (CACurrentMediaTime() - frame.enqueuedAt) * 1000) + self.drain() + } + } + + if let onFrameReady { + onFrameReady(frame, completion) + } else { + completion() + } + } + + private func logIfNeeded(lastLatencyMs: Double) { + guard processedCount == 1 || processedCount % 20 == 0 else { return } + let queueDepth = pendingFrame == nil ? 0 : 1 + NSLog( + "[Stream] iPhone analysis lane processed=%lld dropped=%lld queue-depth=%d last-latency=%.1fms", + processedCount, + droppedCount, + queueDepth, + lastLatencyMs + ) + } +} + @MainActor class StreamSessionViewModel: ObservableObject { @Published var currentVideoFrame: UIImage? @@ -43,323 +1353,2677 @@ class StreamSessionViewModel: ObservableObject { @Published var hasActiveDevice: Bool = false @Published var streamingMode: StreamingMode = .glasses @Published var selectedResolution: StreamingResolution = .low + @Published var preferredCaptureMode: StreamingMode = .iPhone + @Published var isSopAuditRunning: Bool = false + @Published var sopAuditSecondsRemaining: Double = 15.0 + @Published var sopAuditStatusMessage: String = "" + @Published var selectedSOP: SOPTemplate? + @Published var checklistItems: [ChecklistItemState] = [] + @Published var shouldDismissCapture: Bool = false + @Published var showShipSuccessToast: Bool = false + @Published var isListeningForVoice: Bool = false + @Published var isDossierUploading: Bool = false + @Published var dossierPipelineStatusMessage: String = "" + @Published var dossierPipelineStatusKind: DossierPipelineStatusKind = .info + @Published var dossierPipelineStatusTimestamp: String = "" + @Published var dossierSpotterHitCount: Int = 0 + @Published var shippedHistory: [ShippedSessionRecord] = [] + @Published var isSyncingOperations: Bool = false + @Published var operationsSyncError: String? + @Published var operationsSyncWarning: String? + @Published var workerProfile: BackendWorker? + @Published var registeredDevice: BackendDevice? + @Published var activeShift: BackendShift? + @Published var assignedPackages: [BackendAssignedPackage] = [] + @Published var activeExecutionSession: BackendExecutionSession? + @Published var helpRequestNotes: String = "" + @Published var helpStatusMessage: String = "" + @Published var isRequestingHelp: Bool = false + @Published var packageClosureStatusMessage: String = "" + @Published var isClosingPackage: Bool = false + @Published var activeCaptureSOP: SOPTemplate? + @Published var geminiInstructionSyncStatus: String = "" + @Published var iPhonePreviewSession: AVCaptureSession? + + @Published var availableSOPs: [SOPTemplate] = [] + @Published private(set) var locallyCompletedPendingTaskKeys: Set = [] + + var isStreaming: Bool { + streamingStatus != .stopped + } + + var resolutionLabel: String { + switch selectedResolution { + case .low: return "360x640" + case .medium: return "504x896" + case .high: return "720x1280" + @unknown default: return "Unknown" + } + } + + var progressText: String { + "\(checklistItems.filter { $0.isChecked }.count)/\(checklistItems.count)" + } + + var currentAssignedSOP: SOPTemplate? { + if let selectedSOP, pendingTaskSOPs.contains(selectedSOP) { + return selectedSOP + } + return pendingTaskSOPs.first + } + + var workerDisplayName: String { + if isDemoWorkerMode { + return "Lucas Pereira" + } + return workerProfile?.displayName ?? "Unassigned Worker" + } + + var workerRoleText: String { + workerProfile?.role?.uppercased() ?? "WORKER" + } + + var activePackageTitle: String { + assignedPackages.first?.title + ?? activeShift?.package?.title + ?? currentAssignedSOP?.packageTitle + ?? "No Active Package" + } + + var pendingTaskSOPs: [SOPTemplate] { + availableSOPs + .sorted { $0.sortOrder < $1.sortOrder } + .filter { !locallyCompletedPendingTaskKeys.contains(pendingTaskKey(for: $0)) } + } + + var pendingShiftLabel: String { + activeShift?.shiftName + ?? currentAssignedSOP?.shiftName + ?? "MORNING SHIFT" + } + + var pendingTaskHeaderSummary: String { + let count = pendingTaskSOPs.count + if count == 0 { + return "ALL PENDING TASKS COMPLETE" + } + return "\(count) PENDING TASK\(count == 1 ? "" : "S") · \(selectedCaptureModeLabel)" + } + + var activeAssignedPackageCount: Int { + assignedPackages.count + } + + var currentPackageProgressText: String { + guard let key = currentPackageCompletionKey, !currentPackageRequiredRemoteIDs.isEmpty else { + return availableSOPs.isEmpty ? "NO PACKAGE QUEUE" : "\(availableSOPs.count) SOPS QUEUED" + } + + let completedCount = locallyCompletedSopsByPackageKey[key]?.count ?? 0 + return "\(completedCount)/\(currentPackageRequiredRemoteIDs.count) PACKAGE SOPS COMPLETE" + } + + var currentSessionSyncLabel: String { + if let activeExecutionSession { + return "SESSION \(activeExecutionSession.id.prefix(8))" + } + if currentSopSessionId != nil { + return "LOCAL SESSION" + } + return "NOT STARTED" + } + + var canRequestHelp: Bool { + isSopAuditRunning + } + + var canCloseCurrentPackage: Bool { + guard activePackageRunID != nil else { return false } + guard let key = currentPackageCompletionKey, !currentPackageRequiredRemoteIDs.isEmpty else { return false } + let completed = locallyCompletedSopsByPackageKey[key] ?? [] + return Set(currentPackageRequiredRemoteIDs).isSubset(of: completed) + } + + var selectedCaptureModeLabel: String { + switch preferredCaptureMode { + case .glasses: return "META CAMERA" + case .iPhone: return "IPHONE CAMERA" + } + } + + // Photo capture properties + @Published var capturedPhoto: UIImage? + @Published var showPhotoPreview: Bool = false + + // Operational backend integration + private let opsAPIClient = OpsAPIClient() + let geminiAssistant = GeminiSessionViewModel() + private let geminiLiveSpotter = GeminiLiveSpotter() + let webrtcViewModel = WebRTCSessionViewModel() + private var workerAdminSync: WorkerAdminLiveSessionCoordinator? + private var currentSopSessionId: String? + private var sopCountdownTask: Task? + private var sopVideoRecorder: SopVideoRecorder? + private let liveFrameProcessingQueue = DispatchQueue( + label: "stream.live.frame-processing", + qos: .userInitiated + ) + private var proofImagesByTargetID: [String: Data] = [:] + private var lastSpotterInferenceTime: Date = .distantPast + private var isSpotterInferenceInFlight = false + private var isFinalizingAndShipping = false + private var successToastTask: Task? + private var hasLoadedWorkerContext = false + private var hasEnteredWorkerHome = false + private var isUsingLocalSessionFallback = false + private var roomCodeCancellable: AnyCancellable? + private var connectionStateCancellable: AnyCancellable? + private var locallyCompletedSopsByPackageKey: [String: Set] = [:] + private var lastLivePreviewSyncAt: Date = .distantPast + private var hasActiveHelpEscalation = false + private var hasLoggedRoomCreatedForSession = false + private var hasLoggedRoomJoinedForSession = false + private var didAttemptPendingRecordingRecovery = false + + private var isDemoWorkerMode: Bool { + let configuredCode = GeminiConfig.workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) + let workerCode = workerProfile?.loginCode?.trimmingCharacters(in: .whitespacesAndNewlines) + let loginCode = workerCode?.isEmpty == false ? workerCode! : configuredCode + return loginCode.uppercased() == "EMBC-0001" + } + + private func pendingTaskKey(for sop: SOPTemplate) -> String { + [ + sop.shiftID ?? "shift", + sop.packageRunID ?? sop.packageID ?? "standalone", + sop.remoteID ?? sop.id.uuidString, + "\(sop.sortOrder)" + ].joined(separator: "::") + } + + // Hold-to-talk speech recognition + private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US")) + private let audioEngine = AVAudioEngine() + private var speechRequest: SFSpeechAudioBufferRecognitionRequest? + private var speechTask: SFSpeechRecognitionTask? + private var lastProcessedTranscript: String = "" + + private var currentPackageCompletionKey: String? { + if let runID = activePackageRunID { + return runID + } + return currentAssignedSOP?.packageID ?? assignedPackages.first?.id + } + + private var activePackageRunID: String? { + currentAssignedSOP?.packageRunID + ?? assignedPackages.first(where: { $0.id == currentAssignedSOP?.packageID })?.packageRunID + ?? assignedPackages.first?.packageRunID + } + + private var currentPackageRequiredRemoteIDs: [String] { + let packageID = currentAssignedSOP?.packageID ?? assignedPackages.first?.id + return availableSOPs + .filter { sop in + sop.required && + sop.packageID == packageID && + sop.sourceType == "package" + } + .compactMap(\.remoteID) + } + + private let historyDefaultsKey = "visionclaw.shipped.history.v2" + private let pendingRecordingDefaultsKey = "visionclaw.pending.worker.recording.v1" + private static let pipelineTimestampFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "HH:mm:ss" + return formatter + }() + + // The core DAT SDK StreamSession - handles all streaming operations + private var streamSession: StreamSession + // Listener tokens are used to manage DAT SDK event subscriptions + private var stateListenerToken: AnyListenerToken? + private var videoFrameListenerToken: AnyListenerToken? + private var errorListenerToken: AnyListenerToken? + private var photoDataListenerToken: AnyListenerToken? + private let wearables: WearablesInterface + private let deviceSelector: AutoDeviceSelector + private var deviceMonitorTask: Task? + private var iPhoneCameraManager: IPhoneCameraManager? + private let iPhoneAnalysisLane = IPhoneAnalysisLane() + + // CPU-based CIContext for rendering decoded pixel buffers in background + private let cpuCIContext = CIContext(options: [.useSoftwareRenderer: true]) + // VideoDecoder for decompressing HEVC/H.264 frames in background + private let videoDecoder = VideoDecoder() + private var backgroundFrameCount = 0 + private var bgDiagLogged = false + + init(wearables: WearablesInterface) { + self.wearables = wearables + // Let the SDK auto-select from available devices + self.deviceSelector = AutoDeviceSelector(wearables: wearables) + let config = StreamSessionConfig( + videoCodec: VideoCodec.raw, + resolution: StreamingResolution.low, + frameRate: 24) + streamSession = StreamSession(streamSessionConfig: config, deviceSelector: deviceSelector) + + // Monitor device availability + deviceMonitorTask = Task { @MainActor in + for await device in deviceSelector.activeDeviceStream() { + self.hasActiveDevice = device != nil + } + } + + setupVideoDecoder() + attachListeners() + loadHistoryFromDefaults() + requestSpeechPermissionsIfNeeded() + observeWebRTCSession() + iPhoneAnalysisLane.onFrameReady = { [weak self] frame, completion in + Task { @MainActor [weak self] in + guard let self else { + completion() + return + } + self.handleIPhoneAnalysisFrame(frame) + completion() + } + } + } + + private func setupVideoDecoder() { + videoDecoder.setFrameCallback { [weak self] decodedFrame in + Task { @MainActor [weak self] in + guard let self else { return } + let pixelBuffer = decodedFrame.pixelBuffer + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let rect = CGRect(x: 0, y: 0, width: width, height: height) + let timeStampNs = decodedFrame.presentationTimeStamp.isValid + ? Int64(CMTimeGetSeconds(decodedFrame.presentationTimeStamp) * 1_000_000_000) + : VideoFrameBufferFactory.currentTimestampNs() + if self.webrtcViewModel.isActive { + self.webrtcViewModel.realtimeVideoForwarder.enqueuePixelBuffer( + pixelBuffer, + timeStampNs: timeStampNs + ) + } + if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { + let image = UIImage(cgImage: cgImage) + self.handleProcessedLiveFrame( + image: image, + pixelBuffer: pixelBuffer, + timeStampNs: timeStampNs, + shouldForwardToWebRTC: false, + shouldRecordAudit: self.isSopAuditRunning + ) + if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { + NSLog("[Stream] Background frame #%d decoded and forwarded (%dx%d)", + self.backgroundFrameCount, width, height) + } + } + } + } + } + + /// Recreate the StreamSession with the current selectedResolution. + /// Only call when not actively streaming. + func updateResolution(_ resolution: StreamingResolution) { + guard !isStreaming else { return } + selectedResolution = resolution + let config = StreamSessionConfig( + videoCodec: VideoCodec.raw, + resolution: resolution, + frameRate: 24) + streamSession = StreamSession(streamSessionConfig: config, deviceSelector: deviceSelector) + attachListeners() + NSLog("[Stream] Resolution changed to %@", resolutionLabel) + } + + private func attachListeners() { + let realtimeVideoForwarder = webrtcViewModel.realtimeVideoForwarder + + // Subscribe to session state changes using the DAT SDK listener pattern + stateListenerToken = streamSession.statePublisher.listen { [weak self] state in + Task { @MainActor [weak self] in + self?.updateStatusFromState(state) + } + } + + // Subscribe to video frames from the device camera + // This callback fires whether the app is in the foreground or background, + // enabling continuous streaming even when the screen is locked. + videoFrameListenerToken = streamSession.videoFramePublisher.listen { [weak self] videoFrame in + Task { @MainActor [weak self] in + guard let self else { return } + let shouldForwardToWebRTC = self.webrtcViewModel.isActive + let shouldRecordAudit = self.isSopAuditRunning + + let isInBackground = UIApplication.shared.applicationState == .background + + if !isInBackground { + self.backgroundFrameCount = 0 + self.bgDiagLogged = false + + self.liveFrameProcessingQueue.async { [weak self] in + guard let self else { return } + guard let image = videoFrame.makeUIImage() else { return } + + let pixelBuffer = + (shouldForwardToWebRTC || shouldRecordAudit) + ? VideoFrameBufferFactory.makePixelBuffer(from: image) + : nil + let timeStampNs = VideoFrameBufferFactory.currentTimestampNs() + + if shouldForwardToWebRTC { + if let pixelBuffer { + realtimeVideoForwarder.enqueuePixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } else { + realtimeVideoForwarder.enqueueImage(image) + } + } + + Task { @MainActor [weak self] in + self?.handleProcessedLiveFrame( + image: image, + pixelBuffer: pixelBuffer, + timeStampNs: timeStampNs, + shouldForwardToWebRTC: false, + shouldRecordAudit: shouldRecordAudit + ) + } + } + } else { + // In background: makeUIImage() uses VideoToolbox GPU rendering which iOS suspends. + // Instead, use our VideoDecoder (VTDecompressionSession) to decode compressed + // frames into pixel buffers, then convert via CPU CIContext. + self.backgroundFrameCount += 1 + + let sampleBuffer = videoFrame.sampleBuffer + let hasCompressedData = CMSampleBufferGetDataBuffer(sampleBuffer) != nil + + if hasCompressedData { + // Compressed frame (HEVC/H.264) - decode via VTDecompressionSession + do { + try self.videoDecoder.decode(sampleBuffer) + } catch { + if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { + NSLog("[Stream] Background frame #%d decode error: %@", + self.backgroundFrameCount, String(describing: error)) + } + } + } else if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { + // Raw pixel buffer - convert directly via CPU CIContext + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + let timeStampNs = Self.rtcTimestampNs(from: sampleBuffer) + if shouldForwardToWebRTC { + realtimeVideoForwarder.enqueuePixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let rect = CGRect(x: 0, y: 0, width: width, height: height) + if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { + let image = UIImage(cgImage: cgImage) + self.handleProcessedLiveFrame( + image: image, + pixelBuffer: pixelBuffer, + timeStampNs: timeStampNs, + shouldForwardToWebRTC: false, + shouldRecordAudit: shouldRecordAudit + ) + } + self.videoDecoder.invalidateSession() + } + } + } + } + + // Subscribe to streaming errors + errorListenerToken = streamSession.errorPublisher.listen { [weak self] error in + Task { @MainActor [weak self] in + guard let self else { return } + // Suppress device-not-found errors when user hasn't started streaming yet + if self.streamingStatus == .stopped { + if case .deviceNotConnected = error { return } + if case .deviceNotFound = error { return } + } + let newErrorMessage = formatStreamingError(error) + if newErrorMessage != self.errorMessage { + showError(newErrorMessage) + } + } + } + + updateStatusFromState(streamSession.state) + + // Subscribe to photo capture events + photoDataListenerToken = streamSession.photoDataPublisher.listen { [weak self] photoData in + Task { @MainActor [weak self] in + guard let self else { return } + if let uiImage = UIImage(data: photoData.data) { + self.capturedPhoto = uiImage + self.showPhotoPreview = true + } + } + } + } + + func handleStartStreaming() async { + let permission = Permission.camera + do { + let status = try await wearables.checkPermissionStatus(permission) + if status == .granted { + await startSession() + return + } + let requestStatus = try await wearables.requestPermission(permission) + if requestStatus == .granted { + await startSession() + return + } + showError("Permission denied") + } catch { + showError("Permission error: \(error.description)") + } + } + + func startSession() async { + geminiAssistant.streamingMode = streamingMode + await streamSession.start() + } + + private func showError(_ message: String) { + errorMessage = message + showError = true + } + + private func handleProcessedLiveFrame( + image: UIImage, + pixelBuffer: CVPixelBuffer?, + timeStampNs: Int64, + shouldForwardToWebRTC: Bool, + shouldRecordAudit: Bool + ) { + if shouldForwardToWebRTC { + if let pixelBuffer { + webrtcViewModel.pushVideoPixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } else { + webrtcViewModel.pushVideoFrame(image) + } + } + + currentVideoFrame = image + if !hasReceivedFirstFrame { + hasReceivedFirstFrame = true + } + + geminiAssistant.sendVideoFrameIfThrottled(image: image) + + if shouldRecordAudit { + Task { await syncLivePreviewFrameIfNeeded(image: image) } + if let pixelBuffer { + sopVideoRecorder?.appendPixelBuffer(pixelBuffer) + } else { + sopVideoRecorder?.appendFrame(image) + } + spotChecklistItemsIfThrottled(image: image) + } + } + + private func enqueueIPhoneAnalysisFrame(_ image: UIImage, shouldRecordAudit: Bool) { + iPhoneAnalysisLane.submit(image, shouldRecordAudit: shouldRecordAudit) + } + + private func handleIPhoneAnalysisFrame(_ frame: IPhoneAnalysisFrameEnvelope) { + currentVideoFrame = frame.image + geminiAssistant.sendVideoFrameIfThrottled(image: frame.image) + + if frame.shouldRecordAudit { + Task { await syncLivePreviewFrameIfNeeded(image: frame.image) } + spotChecklistItemsIfThrottled(image: frame.image) + } + } + + private func resetIPhoneAnalysisLane() { + iPhoneAnalysisLane.reset() + } + + private static func rtcTimestampNs(from sampleBuffer: CMSampleBuffer) -> Int64 { + let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + if presentationTime.isValid { + return Int64(CMTimeGetSeconds(presentationTime) * 1_000_000_000) + } + return VideoFrameBufferFactory.currentTimestampNs() + } + + func stopSession() async { + geminiAssistant.stopSession() + if isSopAuditRunning { + await endAndShip(status: .userEnded) + } else { + await workerAdminSync?.stop() + workerAdminSync = nil + } + + if webrtcViewModel.isActive { + webrtcViewModel.stopSession() + } + activeCaptureSOP = nil + + if streamingMode == .iPhone { + stopIPhoneSession() + return + } + await streamSession.stop() + } + + func toggleGeminiAssistant() async { + geminiAssistant.streamingMode = streamingMode + if geminiAssistant.isGeminiActive { + geminiAssistant.stopSession() + return + } + + await geminiAssistant.startSession(systemInstruction: buildGeminiSessionInstruction()) + if let errorMessage = geminiAssistant.errorMessage, !errorMessage.isEmpty { + sopAuditStatusMessage = errorMessage + } + } + + func beginLiveCapture(for sop: SOPTemplate) async { + selectedSOP = sop + activeCaptureSOP = sop + configureChecklist(for: sop) + showShipSuccessToast = false + shouldDismissCapture = false + sopAuditStatusMessage = "" + helpStatusMessage = "" + + if !hasLoadedWorkerContext { + await loadWorkerContextIfNeeded() + } + + if !isStreaming { + await startPreferredCamera() + } + + guard isStreaming else { return } + + await startSopAudit(for: sop) + } + + func selectCaptureMode(_ mode: StreamingMode) { + guard mode != .glasses || hasActiveDevice else { + sopAuditStatusMessage = "Meta camera not connected." + return + } + preferredCaptureMode = mode + if webrtcViewModel.isActive { + do { + if let routeWarning = try configureWorkerAudioRoute(for: mode, reason: .viewer) { + helpStatusMessage = routeWarning + } + } catch { + helpStatusMessage = "Audio route update failed: \(error.localizedDescription)" + } + } + } + + func presentCapture(for sop: SOPTemplate) { + selectedSOP = sop + activeCaptureSOP = sop + shouldDismissCapture = false + } + + func handleWorkerHomeEntered() async { + if !hasLoadedWorkerContext { + await loadWorkerContextIfNeeded() + } + + guard !hasEnteredWorkerHome else { return } + hasEnteredWorkerHome = true + await resetDemoShiftForHomeIfNeeded(reloadAssignments: false) + } + + func handleWorkerAppBecameActive() async { + guard hasEnteredWorkerHome else { return } + guard !isSopAuditRunning, activeCaptureSOP == nil else { return } + await resetDemoShiftForHomeIfNeeded(reloadAssignments: true) + } + + func restoreActiveCaptureIfNeeded() { + guard activeCaptureSOP == nil else { return } + guard isSopAuditRunning else { return } + if let activeSOP = selectedSOP ?? currentAssignedSOP { + activeCaptureSOP = activeSOP + } + } + + func switchToPreferredCaptureModeIfNeeded() async { + guard let _ = selectedSOP else { return } + + if preferredCaptureMode == .glasses, !hasActiveDevice { + sopAuditStatusMessage = "Meta camera not connected." + return + } + + if isStreaming && streamingMode == preferredCaptureMode { + return + } + + await stopCurrentCameraTransportOnly() + await startPreferredCamera() + } + + func loadWorkerContextIfNeeded() async { + guard !hasLoadedWorkerContext else { return } + await refreshWorkerContext() + } + + func refreshWorkerContext() async { + guard GeminiConfig.isOpsConfigured else { + setCriticalOperationsSyncIssue( + phase: "bootstrap", + message: "Set the ops-api URL in Settings to load assignments." + ) + return + } + + let loginCode = GeminiConfig.workerLoginCode.trimmingCharacters(in: .whitespacesAndNewlines) + let workerEmail = GeminiConfig.workerEmail.trimmingCharacters(in: .whitespacesAndNewlines) + guard !loginCode.isEmpty || !workerEmail.isEmpty else { + setCriticalOperationsSyncIssue( + phase: "bootstrap", + message: "Set a worker email or login code in Settings to bootstrap assignments." + ) + return + } + + isSyncingOperations = true + clearOperationsSyncState() + packageClosureStatusMessage = "" + defer { isSyncingOperations = false } + + do { + let payload = try await opsAPIClient.bootstrap( + loginCode: loginCode.isEmpty ? nil : loginCode, + email: workerEmail.isEmpty ? nil : workerEmail, + platform: "ios", + label: UIDevice.current.name + ) + + workerProfile = payload.worker + registeredDevice = payload.device + activeShift = payload.shift + let canonicalLucasTemplates = lucasDemoQueueTemplates() + let resolvedQueue = canonicalLucasQueue(from: payload.queue) + assignedPackages = + payload.assignedPackages.isEmpty + ? deriveAssignedPackages(from: resolvedQueue, canonicalTemplates: canonicalLucasTemplates) + : payload.assignedPackages + availableSOPs = resolvedQueue.map { hydrateQueueItem($0, canonicalTemplates: canonicalLucasTemplates) } + if selectedSOP == nil || !pendingTaskSOPs.contains(selectedSOP!) { + selectedSOP = pendingTaskSOPs.first + } + hasLoadedWorkerContext = true + + if availableSOPs.isEmpty { + if isDemoWorkerMode { + applyLucasDemoWorkerFallback(reason: "No remote SOPs were assigned yet. Using the local Lucas demo queue.") + } else { + setCriticalOperationsSyncIssue( + phase: "bootstrap", + message: "No SOPs assigned to this worker yet." + ) + } + } + + await recoverPendingWorkerRecordingIfNeeded() + } catch { + if isDemoWorkerMode { + applyLucasDemoWorkerFallback( + reason: "Assignment sync failed: \(error.localizedDescription). Using the local Lucas demo queue." + ) + hasLoadedWorkerContext = true + } else { + setCriticalOperationsSyncIssue( + phase: "bootstrap", + message: "Assignment sync failed: \(error.localizedDescription)" + ) + } + } + } + + private func canonicalLucasQueue(from queue: [WorkerQueueItem]) -> [WorkerQueueItem] { + var seen = Set() + return queue + .sorted { lhs, rhs in + let leftOrder = lhs.sortOrder == 0 ? lucasCanonicalOrder(for: lhs.sopID) : lhs.sortOrder + let rightOrder = rhs.sortOrder == 0 ? lucasCanonicalOrder(for: rhs.sopID) : rhs.sortOrder + if leftOrder == rightOrder { + return lhs.sopTitle < rhs.sopTitle + } + return leftOrder < rightOrder + } + .filter { item in + let key = item.sopID.lowercased() + guard !seen.contains(key) else { return false } + seen.insert(key) + return true + } + } + + private func hydrateQueueItem( + _ queueItem: WorkerQueueItem, + canonicalTemplates: [SOPTemplate] + ) -> SOPTemplate { + let canonical = canonicalTemplates.first { template in + template.name.caseInsensitiveCompare(queueItem.sopTitle) == .orderedSame + } + + let stepTemplates: [SOPStepTemplate] + if queueItem.steps.isEmpty { + stepTemplates = canonical?.steps ?? [] + } else { + stepTemplates = queueItem.steps.enumerated().map { index, step in + SOPStepTemplate( + id: step.id, + order: step.order == 0 ? index + 1 : step.order, + title: step.title, + description: step.description, + duration: step.duration, + validation: step.validation, + critical: step.critical, + aiPrompt: step.aiPrompt, + expectedObjects: step.expectedObjects, + allowManualComplete: step.allowManualComplete + ) + } + } + + let resolvedSortOrder = canonical?.sortOrder ?? (queueItem.sortOrder == 0 ? lucasCanonicalOrder(for: queueItem.sopID) : queueItem.sortOrder) + let resolvedShiftName = queueItem.shiftName ?? canonical?.shiftName ?? "Morning" + let resolvedPackageTitle = queueItem.packageTitle ?? canonical?.packageTitle + let resolvedPackageVersion = queueItem.packageVersion ?? canonical?.packageVersion + let resolvedSopVersion = queueItem.sopVersion ?? canonical?.sopVersion + let resolvedSourceType = + queueItem.packageID == nil && canonical?.packageID != nil + ? canonical?.sourceType ?? queueItem.sourceType + : queueItem.sourceType + + return SOPTemplate( + id: UUID(uuidString: queueItem.sopID) ?? canonical?.id ?? UUID(), + remoteID: queueItem.sopID, + name: canonical?.name ?? queueItem.sopTitle, + steps: stepTemplates, + estimatedDuration: canonical?.estimatedDuration ?? max(Double(max(stepTemplates.count, 1)) * 18.0, 18.0), + shiftID: validRemoteUUID(queueItem.shiftAssignmentID), + shiftName: resolvedShiftName, + packageID: queueItem.packageID ?? canonical?.packageID, + packageRunID: queueItem.packageRunID ?? canonical?.packageRunID, + packageTitle: resolvedPackageTitle, + packageVersion: resolvedPackageVersion, + sopVersion: resolvedSopVersion, + sourceType: resolvedSourceType, + sortOrder: resolvedSortOrder, + required: queueItem.required + ) + } + + private func deriveAssignedPackages( + from queue: [WorkerQueueItem], + canonicalTemplates: [SOPTemplate] + ) -> [BackendAssignedPackage] { + var resolved: [String: BackendAssignedPackage] = [:] + + for item in queue { + guard let packageID = item.packageID else { continue } + let matchingTemplate = canonicalTemplates.first { template in + template.name.caseInsensitiveCompare(item.sopTitle) == .orderedSame + } + resolved[packageID] = BackendAssignedPackage( + id: packageID, + title: item.packageTitle ?? matchingTemplate?.packageTitle ?? "Assigned Package", + description: nil, + outcome: nil, + version: item.packageVersion ?? matchingTemplate?.packageVersion, + shiftName: item.shiftName ?? matchingTemplate?.shiftName ?? "Morning", + active: item.active ?? true, + packageRunID: item.packageRunID, + packageRunStatus: nil, + packageRunStartedAt: item.startsAt, + packageRunCompletedAt: item.endsAt + ) + } + + return resolved.values.sorted { lhs, rhs in + let leftOrder = lucasCanonicalPackageOrder(for: lhs.title) + let rightOrder = lucasCanonicalPackageOrder(for: rhs.title) + if leftOrder == rightOrder { + return lhs.title < rhs.title + } + return leftOrder < rightOrder + } + } + + private func lucasCanonicalOrder(for sopID: String) -> Int { + switch sopID { + case "22222222-2222-2222-2222-222222222222": + return 1 + case "a1000001-0000-0000-0000-000000000001": + return 2 + case "a1000002-0000-0000-0000-000000000002": + return 3 + case "a1000003-0000-0000-0000-000000000003": + return 4 + default: + return 99 + } + } + + private func lucasCanonicalPackageOrder(for packageTitle: String) -> Int { + switch packageTitle { + case "Inbound Cold Chain Audit": + return 1 + case "QSR Value Meal Order": + return 2 + default: + return 99 + } + } + + private func applyLucasDemoWorkerFallback(reason: String) { + workerProfile = BackendWorker( + id: "worker-lucas", + loginCode: "EMBC-0001", + displayName: "Lucas Pereira", + role: "Kitchen Staff", + status: "active" + ) + + let inboundPackage = BackendAssignedPackage( + id: "33333333-3333-3333-3333-333333333333", + title: "Inbound Cold Chain Audit", + description: "Verify cold-chain compliance for inbound product before storing.", + outcome: "Cold Chain Verified", + version: 2, + shiftName: "Morning", + active: true, + packageRunID: nil, + packageRunStatus: nil, + packageRunStartedAt: nil, + packageRunCompletedAt: nil + ) + + let mealPackage = BackendAssignedPackage( + id: "b2000001-0000-0000-0000-000000000001", + title: "QSR Value Meal Order", + description: "Standard meal execution from assembly to drink handoff.", + outcome: "Order Fulfilled", + version: 2, + shiftName: "Morning", + active: true, + packageRunID: nil, + packageRunStatus: nil, + packageRunStartedAt: nil, + packageRunCompletedAt: nil + ) + + let shiftPackage = BackendPackage( + id: inboundPackage.id, + title: inboundPackage.title, + description: inboundPackage.description, + outcome: inboundPackage.outcome, + version: inboundPackage.version, + status: "active" + ) + + activeShift = BackendShift( + id: "shift-lucas-morning", + packageID: inboundPackage.id, + shiftName: "Morning", + startsAt: nil, + endsAt: nil, + active: true, + package: shiftPackage + ) + + assignedPackages = [inboundPackage, mealPackage] + availableSOPs = lucasDemoQueueTemplates() + selectedSOP = pendingTaskSOPs.first + setCriticalOperationsSyncIssue(phase: "bootstrap", message: reason) + } + + private func lucasDemoQueueTemplates() -> [SOPTemplate] { + [ + SOPTemplate( + remoteID: "22222222-2222-2222-2222-222222222222", + name: "Cold Chain Verification SOP", + steps: [ + SOPStepTemplate( + id: "inspect_packaging_seal", + order: 1, + title: "Inspect packaging seal", + description: "Check the inbound package seal before accepting the delivery.", + duration: "30s", + validation: "visual", + critical: true, + aiPrompt: "Look at the image and confirm whether the operator inspected the package seal before accepting the delivery.", + expectedObjects: ["seal", "package"], + allowManualComplete: true + ), + SOPStepTemplate( + id: "record_temperature_log", + order: 2, + title: "Record temperature log", + description: "Read the temperature and confirm it is entered into the log.", + duration: "30s", + validation: "visual", + critical: false, + aiPrompt: "Look at the image and confirm whether the operator recorded the product temperature in the log.", + expectedObjects: ["thermometer", "clipboard"], + allowManualComplete: true + ), + SOPStepTemplate( + id: "verify_lot_number", + order: 3, + title: "Verify lot number", + description: "Confirm the lot number is visible and matches the manifest.", + duration: "30s", + validation: "visual", + critical: false, + aiPrompt: "Look at the image and confirm whether the operator verified the lot number on the inbound package.", + expectedObjects: ["label", "lot"], + allowManualComplete: true + ), + SOPStepTemplate( + id: "sign_off", + order: 4, + title: "Sign off", + description: "Acknowledge the cold-chain verification and release storage.", + duration: "30s", + validation: "tap", + critical: false, + aiPrompt: "Look at the image and confirm whether the cold-chain verification was signed off.", + expectedObjects: ["clipboard", "signature"], + allowManualComplete: true + ), + ], + estimatedDuration: 72, + shiftID: nil, + shiftName: "Morning", + packageID: "33333333-3333-3333-3333-333333333333", + packageTitle: "Inbound Cold Chain Audit", + packageVersion: 2, + sopVersion: 1, + sourceType: "package", + sortOrder: 1, + required: true + ), + SOPTemplate( + remoteID: "a1000001-0000-0000-0000-000000000001", + name: "Burger Assembly", + steps: [ + SOPStepTemplate(id: "toast_the_bun", order: 1, title: "Toast the bun", description: "Place bun halves on the grill until golden.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the bun has been toasted.", expectedObjects: ["bun"], allowManualComplete: true), + SOPStepTemplate(id: "place_patty_on_grill", order: 2, title: "Place patty on grill", description: "Place the patty on the grill and season as needed.", duration: "30s", validation: "visual", critical: true, aiPrompt: "Look at the image and confirm whether the patty was placed on the grill.", expectedObjects: ["patty", "grill"], allowManualComplete: true), + SOPStepTemplate(id: "add_cheese_slice", order: 3, title: "Add cheese slice", description: "Place cheese slice on the patty before removal.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether a cheese slice was added to the patty.", expectedObjects: ["cheese", "patty"], allowManualComplete: true), + SOPStepTemplate(id: "apply_condiments", order: 4, title: "Apply condiments", description: "Apply standard condiments to the bottom bun.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether condiments were applied to the bun.", expectedObjects: ["bun", "condiments"], allowManualComplete: true), + SOPStepTemplate(id: "stack_ingredients", order: 5, title: "Stack ingredients", description: "Assemble ingredients in the correct order.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the burger ingredients were stacked in the correct order.", expectedObjects: ["bun", "patty", "lettuce"], allowManualComplete: true), + SOPStepTemplate(id: "quality_check", order: 6, title: "Quality check", description: "Confirm finished burger matches the reference build.", duration: "30s", validation: "visual", critical: true, aiPrompt: "Look at the image and confirm whether the finished burger matches the reference build.", expectedObjects: ["burger"], allowManualComplete: true), + ], + estimatedDuration: 108, + shiftID: nil, + shiftName: "Morning", + packageID: "b2000001-0000-0000-0000-000000000001", + packageTitle: "QSR Value Meal Order", + packageVersion: 2, + sopVersion: 2, + sourceType: "package", + sortOrder: 2, + required: true + ), + SOPTemplate( + remoteID: "a1000002-0000-0000-0000-000000000002", + name: "Fries Assembly", + steps: [ + SOPStepTemplate(id: "load_fry_basket", order: 1, title: "Load fry basket", description: "Fill the basket to the correct portion.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the fry basket was loaded to the correct portion.", expectedObjects: ["basket", "fries"], allowManualComplete: true), + SOPStepTemplate(id: "cook_fries", order: 2, title: "Cook fries", description: "Start the fryer and monitor the timer.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the fries are cooking in the fryer.", expectedObjects: ["fryer", "basket"], allowManualComplete: true), + SOPStepTemplate(id: "drain_and_salt", order: 3, title: "Drain and salt", description: "Drain basket and season fries.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the fries were drained and salted.", expectedObjects: ["fries", "salt"], allowManualComplete: true), + SOPStepTemplate(id: "bag_fries", order: 4, title: "Bag fries", description: "Transfer fries into the correct serving container.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the fries were transferred into the serving container.", expectedObjects: ["fries", "container"], allowManualComplete: true), + ], + estimatedDuration: 72, + shiftID: nil, + shiftName: "Morning", + packageID: "b2000001-0000-0000-0000-000000000001", + packageTitle: "QSR Value Meal Order", + packageVersion: 2, + sopVersion: 2, + sourceType: "package", + sortOrder: 3, + required: true + ), + SOPTemplate( + remoteID: "a1000003-0000-0000-0000-000000000003", + name: "Drink Prep", + steps: [ + SOPStepTemplate(id: "select_cup_size", order: 1, title: "Select cup size", description: "Choose the cup size that matches the ticket.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the correct cup size was selected.", expectedObjects: ["cup"], allowManualComplete: true), + SOPStepTemplate(id: "fill_beverage", order: 2, title: "Fill beverage", description: "Dispense the beverage to the marked fill line.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the beverage was filled to the marked line.", expectedObjects: ["cup", "drink"], allowManualComplete: true), + SOPStepTemplate(id: "add_lid_and_straw", order: 3, title: "Add lid and straw", description: "Seal the cup and attach the straw.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the lid and straw were added to the drink.", expectedObjects: ["lid", "straw"], allowManualComplete: true), + SOPStepTemplate(id: "stage_for_pickup", order: 4, title: "Stage for pickup", description: "Place the drink in the order hand-off zone.", duration: "30s", validation: "visual", critical: false, aiPrompt: "Look at the image and confirm whether the drink was staged for pickup.", expectedObjects: ["cup", "handoff"], allowManualComplete: true), + ], + estimatedDuration: 72, + shiftID: nil, + shiftName: "Morning", + packageID: "b2000001-0000-0000-0000-000000000001", + packageTitle: "QSR Value Meal Order", + packageVersion: 2, + sopVersion: 1, + sourceType: "package", + sortOrder: 4, + required: true + ), + ] + } + + func startSopAudit(for sop: SOPTemplate) async { + guard !isSopAuditRunning else { return } + + let sessionId = await createOrFallbackSessionID(for: sop) + await workerAdminSync?.stop() + workerAdminSync = WorkerAdminLiveSessionCoordinator(api: opsAPIClient) + currentSopSessionId = sessionId + activeCaptureSOP = sop + isSopAuditRunning = true + sopAuditSecondsRemaining = sop.estimatedDuration + sopAuditStatusMessage = "" + proofImagesByTargetID = [:] + lastLivePreviewSyncAt = .distantPast + hasLoggedRoomCreatedForSession = false + hasLoggedRoomJoinedForSession = false + if streamingMode == .iPhone { + sopVideoRecorder = nil + } else { + sopVideoRecorder = SopVideoRecorder() + } + isDossierUploading = false + dossierSpotterHitCount = 0 + updateDossierPipelineStatus("Recording execution...", kind: .info) + lastSpotterInferenceTime = .distantPast + isSpotterInferenceInFlight = false + isFinalizingAndShipping = false + lastProcessedTranscript = "" + helpStatusMessage = "" + hasActiveHelpEscalation = false + packageClosureStatusMessage = "" + clearOperationsSyncState() + + WorkerLiveLogger.log( + "session_start", + sessionID: sessionId, + roomCode: webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode, + uploadState: "active" + ) + await workerAdminSync?.start( + sessionID: sessionId, + currentStepIndex: nextIncompleteStepIndex(), + helpRequested: false, + roomCode: webrtcViewModel.roomCode + ) + + do { + if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { + helpStatusMessage = routeWarning + } + } catch { + helpStatusMessage = "Audio route error: \(error.localizedDescription)" + } + + if streamingMode == .iPhone { + iPhoneCameraManager?.startRecording(sessionID: sessionId) + rememberPendingRecording(sessionID: sessionId, fileURL: expectedIPhoneRecordingURL(for: sessionId)) + } + + await ensureLiveRoomSession() + + // No countdown/auto-timeout for long SOP runs. + sopCountdownTask?.cancel() + sopCountdownTask = nil + } + + func startSopAudit() { + let sop = selectedSOP ?? pendingTaskSOPs.first ?? availableSOPs.first ?? SOPTemplate(name: "Wallet & Thermos", items: ["Wallet", "Thermos"]) + selectedSOP = sop + configureChecklist(for: sop) + Task { await startSopAudit(for: sop) } + } + + func toggleChecklistItem(itemID: UUID, viaVoice: Bool) { + guard let index = checklistItems.firstIndex(where: { $0.id == itemID }) else { return } + if !checklistItems[index].allowManualComplete && !checklistItems[index].isChecked && !viaVoice { + sopAuditStatusMessage = "This step completes through visual AI only." + return + } + + checklistItems[index].isChecked.toggle() + checklistItems[index].completionSource = checklistItems[index].isChecked + ? (viaVoice ? .voice : .manual) + : .pending + + let item = checklistItems[index] + Task { + await handleChecklistMutation( + item: item, + stepIndex: index, + eventType: item.isChecked ? "step_complete" : "step_reopened" + ) + } + + if checklistItems.allSatisfy({ $0.isChecked }) { + Task { await endAndShip(status: .allItemsChecked) } + } + } + + func userTappedEndAndShip() { + Task { await endAndShip(status: .userEnded) } + } + + func requestSupervisorHelp() { + Task { await requestSupervisorHelpFlow() } + } + + func closeCurrentPackage() { + Task { await closeCurrentPackageFlow() } + } + + func closeSupervisorRoom() { + webrtcViewModel.stopSession() + helpStatusMessage = "Supervisor room closed." + hasActiveHelpEscalation = false + Task { @MainActor in + await workerAdminSync?.updateHelpRequested(false) + await patchActiveExecutionSession( + ExecutionSessionPatch( + helpRequested: false + ) + ) + } + } + + func clearCaptureDismissFlag() { + shouldDismissCapture = false + } + + private func recordPackageProgressIfNeeded(for sop: SOPTemplate) { + guard sop.sourceType == "package" else { return } + guard let completionKey = sop.packageRunID ?? sop.packageID else { return } + guard let remoteSOPID = sop.remoteID else { return } + + var completed = locallyCompletedSopsByPackageKey[completionKey] ?? [] + completed.insert(remoteSOPID) + locallyCompletedSopsByPackageKey[completionKey] = completed + } + + private func markPendingTaskComplete(_ sop: SOPTemplate) { + locallyCompletedPendingTaskKeys.insert(pendingTaskKey(for: sop)) + } + + private func formatOperationsSyncMessage(phase: String, message: String) -> String { + "[\(phase)] \(message)" + } + + private func clearOperationsSyncState(clearWarning: Bool = true) { + operationsSyncError = nil + if clearWarning { + operationsSyncWarning = nil + } + } + + private func setCriticalOperationsSyncIssue(phase: String, message: String) { + operationsSyncError = formatOperationsSyncMessage(phase: phase, message: message) + } + + private func setOperationsSyncWarning(phase: String, message: String) { + operationsSyncWarning = formatOperationsSyncMessage(phase: phase, message: message) + } + + private func resetDemoShiftForHomeIfNeeded(reloadAssignments: Bool) async { + guard isDemoWorkerMode else { return } + guard !isSopAuditRunning, activeCaptureSOP == nil else { return } + + locallyCompletedPendingTaskKeys = [] + selectedSOP = nil + await syncGeminiSessionInstruction() + shouldDismissCapture = false + helpStatusMessage = "" + packageClosureStatusMessage = "" + + if reloadAssignments, hasLoadedWorkerContext { + await refreshWorkerContext() + } + } + + func endAndShip(status: SopTerminationStatus, cancelCountdownTask: Bool = true) async { + guard !isFinalizingAndShipping, isSopAuditRunning, currentSopSessionId != nil else { return } + isFinalizingAndShipping = true + stopHoldToTalk() + let sessionID = currentSopSessionId + let syncedToBackend = activeExecutionSession != nil + let completedSOP = selectedSOP + let roomCodeAtEnd = webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode + + WorkerLiveLogger.log( + "session_end_requested", + sessionID: sessionID, + roomCode: roomCodeAtEnd, + uploadState: "active" + ) + + isSopAuditRunning = false + isDossierUploading = true + updateDossierPipelineStatus("Finalizing session media...", kind: .active) + if cancelCountdownTask { + sopCountdownTask?.cancel() + } + sopCountdownTask = nil + + let wasIPhoneRecording = streamingMode == .iPhone + var recordedVideoURL: URL? + if wasIPhoneRecording { + recordedVideoURL = await iPhoneCameraManager?.stopRecording() + stopIPhoneSession() + } else { + await streamSession.stop() + if let videoRecorder = sopVideoRecorder { + recordedVideoURL = await videoRecorder.finishRecording() + } + } + + let proofImages = proofImagesByTargetID + sopVideoRecorder = nil + proofImagesByTargetID = [:] + let checklistPayload: [[String: Any]] = checklistItems.map { + [ + "name": $0.name, + "checked": $0.isChecked, + "source": $0.completionSource.rawValue + ] + } + let completedCount = checklistItems.filter(\.isChecked).count + let finalStepIndex = nextIncompleteStepIndex() + + await workerAdminSync?.updateCurrentStepIndex(finalStepIndex) + await workerAdminSync?.updateHelpRequested(false, sendImmediateHeartbeat: false) + + let videoUploadResult: WorkerMediaUploadResult + if let workerAdminSync { + videoUploadResult = await workerAdminSync.completeSession( + videoFileURL: recordedVideoURL, + videoSource: wasIPhoneRecording ? "phone-recording" : "stream-capture" + ) { [weak self] in + guard let self else { return } + + if activeExecutionSession != nil { + await self.postExecutionEvent( + type: "session_completed", + payload: [ + "termination_status": status.rawValue, + "completed_steps": completedCount, + "total_steps": self.checklistItems.count, + "checklist": checklistPayload + ] + ) + + await self.patchActiveExecutionSession( + ExecutionSessionPatch( + status: status == .allItemsChecked ? "completed" : "ended", + currentSopID: self.selectedSOP?.remoteID, + currentStepIndex: finalStepIndex, + helpRequested: false, + endedAt: ISO8601DateFormatter().string(from: Date()) + ) + ) + } else if recordedVideoURL == nil { + self.updateDossierPipelineStatus("Execution ended locally. No backend session was active.", kind: .info) + } + } + } else { + videoUploadResult = WorkerMediaUploadResult( + assetType: "video", + assetID: nil, + bucket: nil, + path: nil, + byteSize: 0, + uploadState: "failed", + errorMessage: "Worker admin sync was unavailable during teardown." + ) + + if activeExecutionSession != nil { + await postExecutionEvent( + type: "session_completed", + payload: [ + "termination_status": status.rawValue, + "completed_steps": completedCount, + "total_steps": checklistItems.count, + "checklist": checklistPayload + ] + ) + + await patchActiveExecutionSession( + ExecutionSessionPatch( + status: status == .allItemsChecked ? "completed" : "ended", + currentSopID: selectedSOP?.remoteID, + currentStepIndex: finalStepIndex, + helpRequested: false, + endedAt: ISO8601DateFormatter().string(from: Date()) + ) + ) + } else if recordedVideoURL == nil { + updateDossierPipelineStatus("Execution ended locally. No backend session was active.", kind: .info) + } + } + + if let activeExecutionSession { + if let remoteSOPID = completedSOP?.remoteID { + await createExecutionMemoryLink( + sessionID: activeExecutionSession.id, + sopID: remoteSOPID, + completedSteps: completedCount + ) + } + await uploadEvidenceMediaAssets( + sessionID: activeExecutionSession.id, + proofImages: proofImages + ) + } + + if status == .allItemsChecked, let completedSOP { + recordPackageProgressIfNeeded(for: completedSOP) + } + + if let completedSOP { + markPendingTaskComplete(completedSOP) + } + + if videoUploadResult.succeeded { + clearPendingRecording() + } else if let recordedVideoURL, let sessionID { + rememberPendingRecording(sessionID: sessionID, fileURL: recordedVideoURL) + didAttemptPendingRecordingRecovery = false + } + + if let recordedVideoURL, videoUploadResult.succeeded { + try? FileManager.default.removeItem(at: recordedVideoURL) + } + + if !videoUploadResult.succeeded { + let errorMessage = videoUploadResult.errorMessage ?? "Video finalize failed." + let recordingLabel = wasIPhoneRecording ? "Phone recording" : "Session recording" + setCriticalOperationsSyncIssue( + phase: "media_finalize", + message: "\(recordingLabel) finalize failed: \(errorMessage)" + ) + updateDossierPipelineStatus("\(recordingLabel) finalize failed.", kind: .error) + } else { + updateDossierPipelineStatus( + wasIPhoneRecording ? "Phone recording finalized." : "Session recording finalized.", + kind: .success + ) + } + + isDossierUploading = false + + appendHistoryRecord( + ShippedSessionRecord( + timestamp: Date(), + sopName: completedSOP?.name ?? "Unknown SOP", + status: videoUploadResult.succeeded ? "Replay ready" : "Finalize failed" + ) + ) + + if let sessionID { + WorkerLiveLogger.log( + "session_end_completed", + sessionID: sessionID, + roomCode: roomCodeAtEnd, + assetID: videoUploadResult.assetID, + assetType: videoUploadResult.assetType, + bucket: videoUploadResult.bucket, + path: videoUploadResult.path, + byteSize: videoUploadResult.byteSize, + uploadState: videoUploadResult.uploadState, + error: videoUploadResult.errorMessage + ) + } + + if webrtcViewModel.isActive { + webrtcViewModel.stopSession() + } + geminiAssistant.stopSession() + await workerAdminSync?.stop() + workerAdminSync = nil + + hasActiveHelpEscalation = false + activeExecutionSession = nil + currentSopSessionId = nil + activeCaptureSOP = nil + selectedSOP = nil + await syncGeminiSessionInstruction() + sopAuditSecondsRemaining = 0.0 + if canCloseCurrentPackage { + packageClosureStatusMessage = "All required SOPs are complete. Close the package from NOW." + } + sopAuditStatusMessage = videoUploadResult.succeeded + ? (syncedToBackend ? "Execution synced" : "Execution uploaded") + : "Execution ended with media finalize issues" + isSpotterInferenceInFlight = false + shouldDismissCapture = true + showShipSuccessToast = true + successToastTask?.cancel() + successToastTask = Task { @MainActor [weak self] in + try? await Task.sleep(nanoseconds: 2_000_000_000) + self?.showShipSuccessToast = false + } + isFinalizingAndShipping = false + } + + // MARK: - iPhone Camera Mode + + func handleStartIPhone() async { + let granted = await IPhoneCameraManager.requestPermission() + if granted { + startIPhoneSession() + } else { + showError("Camera permission denied. Please grant access in Settings.") + } + } + + private func startIPhoneSession() { + streamingMode = .iPhone + geminiAssistant.streamingMode = .iPhone + currentVideoFrame = nil + hasReceivedFirstFrame = false + resetIPhoneAnalysisLane() + let camera = IPhoneCameraManager() + let realtimeVideoForwarder = webrtcViewModel.realtimeVideoForwarder + camera.onFirstPreviewFrame = { [weak self] in + Task { @MainActor [weak self] in + self?.hasReceivedFirstFrame = true + } + } + camera.onSampleBufferCaptured = { sampleBuffer in + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + realtimeVideoForwarder.enqueuePixelBuffer( + pixelBuffer, + timeStampNs: Self.rtcTimestampNs(from: sampleBuffer) + ) + } + camera.onFrameCaptured = { [weak self] image in + Task { @MainActor [weak self] in + guard let self else { return } + self.enqueueIPhoneAnalysisFrame(image, shouldRecordAudit: self.isSopAuditRunning) + } + } + camera.start() + iPhoneCameraManager = camera + iPhonePreviewSession = camera.previewSession + streamingStatus = .streaming + NSLog("[Stream] iPhone camera mode started") + } + + private func observeWebRTCSession() { + roomCodeCancellable = webrtcViewModel.$roomCode + .removeDuplicates() + .sink { [weak self] roomCode in + guard let self else { return } + Task { @MainActor [weak self] in + guard let self else { return } + if !roomCode.isEmpty, !self.hasLoggedRoomCreatedForSession { + self.hasLoggedRoomCreatedForSession = true + WorkerLiveLogger.log( + "room_created", + sessionID: self.currentSopSessionId, + roomCode: roomCode, + uploadState: "active" + ) + } + await self.syncLiveRoomState(roomCode: roomCode) + } + } + + connectionStateCancellable = webrtcViewModel.$connectionState + .removeDuplicates { lhs, rhs in + String(describing: lhs) == String(describing: rhs) + } + .sink { [weak self] state in + guard let self else { return } + Task { @MainActor [weak self] in + self?.updateHelpStatus(for: state) + } + } + } + + private func stopIPhoneSession() { + sopCountdownTask?.cancel() + sopCountdownTask = nil + isSopAuditRunning = false + isSpotterInferenceInFlight = false + stopHoldToTalk() + resetIPhoneAnalysisLane() + + iPhoneCameraManager?.stop() + iPhoneCameraManager = nil + iPhonePreviewSession = nil + currentVideoFrame = nil + hasReceivedFirstFrame = false + streamingStatus = .stopped + streamingMode = .glasses + geminiAssistant.streamingMode = .glasses + NSLog("[Stream] iPhone camera mode stopped") + } + + func dismissError() { + showError = false + errorMessage = "" + } + + func capturePhoto() { + streamSession.capturePhoto(format: .jpeg) + } + + func dismissPhotoPreview() { + showPhotoPreview = false + capturedPhoto = nil + } + + private func updateStatusFromState(_ state: StreamSessionState) { + switch state { + case .stopped: + currentVideoFrame = nil + streamingStatus = .stopped + case .waitingForDevice, .starting, .stopping, .paused: + streamingStatus = .waiting + case .streaming: + streamingStatus = .streaming + } + } - var isStreaming: Bool { - streamingStatus != .stopped + private func formatStreamingError(_ error: StreamSessionError) -> String { + switch error { + case .internalError: + return "An internal error occurred. Please try again." + case .deviceNotFound: + return "Device not found. Please ensure your device is connected." + case .deviceNotConnected: + return "Device not connected. Please check your connection and try again." + case .timeout: + return "The operation timed out. Please try again." + case .videoStreamingError: + return "Video streaming failed. Please try again." + case .audioStreamingError: + return "Audio streaming failed. Please try again." + case .permissionDenied: + return "Camera permission denied. Please grant permission in Settings." + case .hingesClosed: + return "The hinges on the glasses were closed. Please open the hinges and try again." + @unknown default: + return "An unknown streaming error occurred." + } } - var resolutionLabel: String { - switch selectedResolution { - case .low: return "360x640" - case .medium: return "504x896" - case .high: return "720x1280" - @unknown default: return "Unknown" + private func configureChecklist(for sop: SOPTemplate) { + checklistItems = sop.steps + .sorted { $0.order < $1.order } + .map { step in + ChecklistItemState( + itemID: step.id, + name: step.title, + description: step.description, + duration: step.duration, + validation: step.validation, + critical: step.critical, + aiPrompt: step.aiPrompt, + expectedObjects: step.expectedObjects, + allowManualComplete: step.allowManualComplete + ) + } + + Task { @MainActor [weak self] in + await self?.syncGeminiSessionInstruction(for: sop) } } - // Photo capture properties - @Published var capturedPhoto: UIImage? - @Published var showPhotoPreview: Bool = false + private func appendHistoryRecord(_ record: ShippedSessionRecord) { + shippedHistory.insert(record, at: 0) + if shippedHistory.count > 100 { + shippedHistory = Array(shippedHistory.prefix(100)) + } + saveHistoryToDefaults() + } - // Gemini Live integration - var geminiSessionVM: GeminiSessionViewModel? + private func loadHistoryFromDefaults() { + guard let data = UserDefaults.standard.data(forKey: historyDefaultsKey) else { return } + guard let decoded = try? JSONDecoder().decode([ShippedSessionRecord].self, from: data) else { return } + shippedHistory = decoded + } - // WebRTC Live streaming integration - var webrtcSessionVM: WebRTCSessionViewModel? + private func saveHistoryToDefaults() { + guard let encoded = try? JSONEncoder().encode(shippedHistory) else { return } + UserDefaults.standard.set(encoded, forKey: historyDefaultsKey) + } - // The core DAT SDK StreamSession - handles all streaming operations - private var streamSession: StreamSession - // Listener tokens are used to manage DAT SDK event subscriptions - private var stateListenerToken: AnyListenerToken? - private var videoFrameListenerToken: AnyListenerToken? - private var errorListenerToken: AnyListenerToken? - private var photoDataListenerToken: AnyListenerToken? - private let wearables: WearablesInterface - private let deviceSelector: AutoDeviceSelector - private var deviceMonitorTask: Task? - private var iPhoneCameraManager: IPhoneCameraManager? + private func rememberPendingRecording(sessionID: String, fileURL: URL) { + let pending = PendingWorkerRecording(sessionID: sessionID, filePath: fileURL.path) + guard let encoded = try? JSONEncoder().encode(pending) else { return } + UserDefaults.standard.set(encoded, forKey: pendingRecordingDefaultsKey) + } - // CPU-based CIContext for rendering decoded pixel buffers in background - private let cpuCIContext = CIContext(options: [.useSoftwareRenderer: true]) - // VideoDecoder for decompressing HEVC/H.264 frames in background - private let videoDecoder = VideoDecoder() - private var backgroundFrameCount = 0 - private var bgDiagLogged = false + private func clearPendingRecording() { + UserDefaults.standard.removeObject(forKey: pendingRecordingDefaultsKey) + } - init(wearables: WearablesInterface) { - self.wearables = wearables - // Let the SDK auto-select from available devices - self.deviceSelector = AutoDeviceSelector(wearables: wearables) - let config = StreamSessionConfig( - videoCodec: VideoCodec.raw, - resolution: StreamingResolution.low, - frameRate: 24) - streamSession = StreamSession(streamSessionConfig: config, deviceSelector: deviceSelector) + private func loadPendingRecording() -> PendingWorkerRecording? { + guard let data = UserDefaults.standard.data(forKey: pendingRecordingDefaultsKey) else { return nil } + return try? JSONDecoder().decode(PendingWorkerRecording.self, from: data) + } + + private func expectedIPhoneRecordingURL(for sessionID: String) -> URL { + FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(sessionID)") + .appendingPathExtension("mp4") + } + + private func recoverPendingWorkerRecordingIfNeeded() async { + guard !didAttemptPendingRecordingRecovery else { return } + guard let pendingRecording = loadPendingRecording() else { + didAttemptPendingRecordingRecovery = true + return + } + + didAttemptPendingRecordingRecovery = true + let pendingURL = URL(fileURLWithPath: pendingRecording.filePath) + let recoveryURL = FileManager.default.fileExists(atPath: pendingURL.path) ? pendingURL : nil + let recoverySync = WorkerAdminLiveSessionCoordinator( + api: opsAPIClient, + sessionID: pendingRecording.sessionID, + heartbeatIntervalNanoseconds: 0 + ) + + let result = await recoverySync.uploadVideoRecording( + from: recoveryURL, + source: "phone-recording" + ) + if result.succeeded { + clearPendingRecording() + if let recoveryURL { + try? FileManager.default.removeItem(at: recoveryURL) + } + } else { + setCriticalOperationsSyncIssue( + phase: "media_finalize", + message: "Recovered recording finalize failed: \(result.errorMessage ?? "Unknown error")" + ) + } + + do { + _ = try await opsAPIClient.updateExecutionSession( + id: pendingRecording.sessionID, + patch: ExecutionSessionPatch( + status: "ended", + helpRequested: false, + endedAt: ISO8601DateFormatter().string(from: Date()) + ) + ) + } catch { + if result.succeeded { + setCriticalOperationsSyncIssue( + phase: "session_patch", + message: "Recovered video uploaded, but session end sync failed: \(error.localizedDescription)" + ) + } + } + } + + private func requestSpeechPermissionsIfNeeded() { + SFSpeechRecognizer.requestAuthorization { status in + if status != .authorized { + NSLog("[Speech] Speech recognition authorization denied: %@", String(describing: status)) + } + } + + AVAudioApplication.requestRecordPermission { granted in + if !granted { + NSLog("[Speech] Microphone permission denied") + } + } + } + + func startHoldToTalk() { + guard !isListeningForVoice else { return } + guard let speechRecognizer, speechRecognizer.isAvailable else { + sopAuditStatusMessage = "Speech recognizer unavailable" + return + } + + do { + if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .holdToTalk) { + helpStatusMessage = routeWarning + } + } catch { + sopAuditStatusMessage = "Audio session error: \(error.localizedDescription)" + return + } + + speechTask?.cancel() + speechTask = nil + + let request = SFSpeechAudioBufferRecognitionRequest() + request.shouldReportPartialResults = true + speechRequest = request + + let inputNode = audioEngine.inputNode + let recordingFormat = inputNode.outputFormat(forBus: 0) + inputNode.removeTap(onBus: 0) + inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in + request.append(buffer) + } + + audioEngine.prepare() + do { + try audioEngine.start() + } catch { + sopAuditStatusMessage = "Mic start failed: \(error.localizedDescription)" + stopHoldToTalk() + return + } + + isListeningForVoice = true + + speechTask = speechRecognizer.recognitionTask(with: request) { [weak self] result, error in + guard let self else { return } + + if let result { + let transcript = result.bestTranscription.formattedString.lowercased() + self.handleVoiceTranscript(transcript) + } + + if error != nil { + self.stopHoldToTalk() + } + } + } + + func stopHoldToTalk() { + guard isListeningForVoice || audioEngine.isRunning else { return } + + if audioEngine.isRunning { + audioEngine.stop() + audioEngine.inputNode.removeTap(onBus: 0) + } + + speechRequest?.endAudio() + speechRequest = nil + speechTask?.cancel() + speechTask = nil + isListeningForVoice = false + + if webrtcViewModel.isActive { + do { + if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { + helpStatusMessage = routeWarning + } + } catch { + NSLog("[Speech] Failed to restore talkback audio route: %@", error.localizedDescription) + } + } else { + do { + try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) + } catch { + NSLog("[Speech] Failed to deactivate audio session: %@", error.localizedDescription) + } + } + } + + private func handleVoiceTranscript(_ transcript: String) { + guard transcript != lastProcessedTranscript else { return } + lastProcessedTranscript = transcript + + if transcript.contains("done") { + markFirstUncheckedAsVoice() + return + } + + guard let checkRange = transcript.range(of: "check ") else { return } + let spokenItem = transcript[checkRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines) + guard !spokenItem.isEmpty else { return } + + if let matched = checklistItems.first(where: { $0.name.lowercased().contains(spokenItem) || spokenItem.contains($0.name.lowercased()) }) { + setChecklistItemChecked(itemID: matched.id, source: .voice) + } + } + + private func markFirstUncheckedAsVoice() { + guard let firstUnchecked = checklistItems.first(where: { !$0.isChecked }) else { return } + setChecklistItemChecked(itemID: firstUnchecked.id, source: .voice) + } + + private func setChecklistItemChecked(itemID: UUID, source: ChecklistCompletionSource) { + guard let index = checklistItems.firstIndex(where: { $0.id == itemID }) else { return } + guard !checklistItems[index].isChecked else { return } + checklistItems[index].isChecked = true + checklistItems[index].completionSource = source + + let item = checklistItems[index] + Task { + await handleChecklistMutation( + item: item, + stepIndex: index, + eventType: "step_complete" + ) + } + + if checklistItems.allSatisfy({ $0.isChecked }) { + Task { await endAndShip(status: .allItemsChecked) } + } + } + + private func setChecklistItemCheckedBySpotterID(_ itemID: String) { + guard let index = checklistItems.firstIndex(where: { $0.itemID == itemID }) else { return } + guard !checklistItems[index].isChecked else { return } + + checklistItems[index].isChecked = true + checklistItems[index].completionSource = .vision + dossierSpotterHitCount += 1 + updateDossierPipelineStatus("Live spotter hit #\(dossierSpotterHitCount)", kind: .active) + + let item = checklistItems[index] + Task { + await handleChecklistMutation( + item: item, + stepIndex: index, + eventType: "step_complete" + ) + } + + if checklistItems.allSatisfy({ $0.isChecked }) { + Task { await endAndShip(status: .allItemsChecked) } + } + } + + private func captureProofImageIfNeeded(for itemID: String, from image: UIImage) { + guard proofImagesByTargetID[itemID] == nil else { return } + guard let jpegData = image.jpegData(compressionQuality: 0.9) else { return } + proofImagesByTargetID[itemID] = jpegData + } + + private func buildDossierMetadataJSONString() -> String { + let checkedCount = checklistItems.filter { $0.isChecked }.count + let complianceLevel: String + if !checklistItems.isEmpty, checkedCount == checklistItems.count { + complianceLevel = "FULLY" + } else if checkedCount == 0 { + complianceLevel = "NON" + } else { + complianceLevel = "PARTIALLY" + } + + let foundItems: [[String: Any]] = checklistItems.map { item in + let notes: String + if item.isChecked { + switch item.completionSource { + case .vision: + notes = "Spotted live by edge AI" + case .voice: + notes = "Confirmed by voice" + case .manual: + notes = "Checked by operator" + case .pending: + notes = "Found" + } + } else { + notes = "Not found" + } + + return [ + "target": item.itemID, + "found": item.isChecked, + "notes": notes + ] + } + + let checklist: [[String: Any]] = checklistItems.map { item in + [ + "name": item.name, + "checked": item.isChecked + ] + } + + let metadata: [String: Any] = [ + "compliance_level": complianceLevel, + "found_items": foundItems, + "checklist": checklist + ] + + guard let data = try? JSONSerialization.data(withJSONObject: metadata, options: []), + let json = String(data: data, encoding: .utf8) else { + return "{}" + } + + return json + } + + private func spotChecklistItemsIfThrottled(image: UIImage) { + guard isSopAuditRunning else { return } + guard !isSpotterInferenceInFlight else { return } + + let now = Date() + guard now.timeIntervalSince(lastSpotterInferenceTime) >= 0.6 else { return } // ~1.6 FPS + lastSpotterInferenceTime = now + isSpotterInferenceInFlight = true + + let pendingItems = activeSpotterRequestItems() + + guard !pendingItems.isEmpty else { + isSpotterInferenceInFlight = false + return + } + + Task { [weak self] in + guard let self else { return } + let matchedIDs: [String] + let requestStartedAt = CACurrentMediaTime() + do { + matchedIDs = try await self.geminiLiveSpotter.detectVisibleItemIDs(image: image, items: pendingItems) + } catch { + matchedIDs = [] + } + let durationMs = (CACurrentMediaTime() - requestStartedAt) * 1000 + NSLog( + "[Spotter] Active-step review targets=%@ matched=%@ duration=%.1fms", + pendingItems.map(\.id).joined(separator: ","), + matchedIDs.joined(separator: ","), + durationMs + ) + + await MainActor.run { + matchedIDs.forEach { + self.captureProofImageIfNeeded(for: $0, from: image) + self.setChecklistItemCheckedBySpotterID($0) + } + self.isSpotterInferenceInFlight = false + } + } + } + + private func startPreferredCamera() async { + switch preferredCaptureMode { + case .iPhone: + await handleStartIPhone() + case .glasses: + guard hasActiveDevice else { + showError("Meta camera unavailable. Connect glasses or switch to iPhone.") + return + } + await handleStartStreaming() + } + } + + private func stopCurrentCameraTransportOnly() async { + switch streamingMode { + case .iPhone: + iPhoneCameraManager?.stop() + iPhoneCameraManager = nil + currentVideoFrame = nil + hasReceivedFirstFrame = false + streamingStatus = .stopped + // Default to glasses mode when stopped; next start sets active mode explicitly. + streamingMode = .glasses + case .glasses: + await streamSession.stop() + currentVideoFrame = nil + hasReceivedFirstFrame = false + streamingStatus = .stopped + } + } + + private enum WorkerAudioRouteReason { + case viewer + case holdToTalk + } + + private func describeAudioPorts(_ ports: [AVAudioSessionPortDescription]) -> String { + if ports.isEmpty { + return "none" + } + + return ports + .map { "\($0.portType.rawValue):\($0.portName)" } + .joined(separator: ",") + } + + private func logWorkerAudioRoute( + session: AVAudioSession, + mode: StreamingMode, + reason: WorkerAudioRouteReason, + note: String? = nil + ) { + let modeLabel = mode == .iPhone ? "iphone" : "glasses" + let reasonLabel = reason == .viewer ? "viewer" : "hold_to_talk" + NSLog( + "[WorkerAudio] reason=%@ mode=%@ muted=%@ inputs=%@ outputs=%@ note=%@", + reasonLabel, + modeLabel, + webrtcViewModel.isMuted ? "true" : "false", + describeAudioPorts(session.currentRoute.inputs), + describeAudioPorts(session.currentRoute.outputs), + note ?? "none" + ) + } + + private func hasBluetoothTalkbackRoute(_ route: AVAudioSessionRouteDescription) -> Bool { + route.outputs.contains { + $0.portType == .bluetoothA2DP + || $0.portType == .bluetoothHFP + || $0.portType == .bluetoothLE + } + } + + @discardableResult + private func configureWorkerAudioRoute( + for mode: StreamingMode, + reason: WorkerAudioRouteReason + ) throws -> String? { + let session = AVAudioSession.sharedInstance() + var options: AVAudioSession.CategoryOptions = [.allowBluetoothHFP, .allowBluetoothA2DP] + let audioMode: AVAudioSession.Mode + + switch mode { + case .iPhone: + audioMode = .voiceChat + options.formUnion([.defaultToSpeaker, .duckOthers]) + case .glasses: + audioMode = .videoChat + } + + try session.setCategory(.playAndRecord, mode: audioMode, options: options) + try session.setPreferredIOBufferDuration(0.02) + try session.setActive(true, options: .notifyOthersOnDeactivation) + + switch mode { + case .iPhone: + try session.overrideOutputAudioPort(.speaker) + logWorkerAudioRoute(session: session, mode: mode, reason: reason) + return nil + case .glasses: + if hasBluetoothTalkbackRoute(session.currentRoute) { + try session.overrideOutputAudioPort(.none) + logWorkerAudioRoute(session: session, mode: mode, reason: reason) + return nil + } + try session.overrideOutputAudioPort(.speaker) + switch reason { + case .viewer: + let note = "Meta audio route unavailable. Using phone speaker until glasses/Bluetooth audio connects." + logWorkerAudioRoute(session: session, mode: mode, reason: reason, note: note) + return note + case .holdToTalk: + let note = "Meta mic route unavailable. Hold-to-talk is using the phone until glasses/Bluetooth audio connects." + logWorkerAudioRoute(session: session, mode: mode, reason: reason, note: note) + return note + } + } + } + + private func liveRoomStatusMessage(localOnly: Bool, helpRequested: Bool, roomCode: String? = nil) -> String { + if localOnly { + return helpRequested + ? "Live room is local-only. Admin can't join until the backend session sync succeeds." + : "Local live room active. Admin visibility will start once the backend session sync succeeds." + } + + if let roomCode, !roomCode.isEmpty { + return helpRequested + ? "Supervisor request sent. Room \(roomCode) is ready for manager join." + : "Live room active: \(roomCode)" + } + + return helpRequested + ? "Supervisor request sent. Waiting for the live room to finish syncing." + : "Opening live execution room..." + } + + private func waitForRoomCode(timeoutNanoseconds: UInt64 = 5_000_000_000) async -> String? { + if !webrtcViewModel.roomCode.isEmpty { + return webrtcViewModel.roomCode + } + + let step: UInt64 = 150_000_000 + var waited: UInt64 = 0 + while waited < timeoutNanoseconds { + try? await Task.sleep(nanoseconds: step) + waited += step + if !webrtcViewModel.roomCode.isEmpty { + return webrtcViewModel.roomCode + } + } + return nil + } + + private func createOrFallbackSessionID(for sop: SOPTemplate) async -> String { + if let activeExecutionSession { + currentSopSessionId = activeExecutionSession.id + isUsingLocalSessionFallback = false + return activeExecutionSession.id + } + + guard let workerID = workerProfile?.id else { + isUsingLocalSessionFallback = true + let fallback = UUID().uuidString + setCriticalOperationsSyncIssue( + phase: "session_create", + message: "Worker context unavailable. Recording locally until ops-api is reachable." + ) + return fallback + } + + do { + let shiftID = validRemoteUUID(sop.shiftID) ?? validRemoteUUID(activeShift?.id) + let packageID = validRemoteUUID(sop.packageID) ?? validRemoteUUID(activeShift?.packageID) ?? validRemoteUUID(activeShift?.package?.id) + let packageRunID = validRemoteUUID(sop.packageRunID) ?? validRemoteUUID(activePackageRunID) + let currentSopID = validRemoteUUID(sop.remoteID) + let createdSession = try await opsAPIClient.createExecutionSession( + workerID: workerID, + deviceID: registeredDevice?.id, + shiftID: shiftID, + packageID: packageID, + packageRunID: packageRunID, + currentSopID: currentSopID, + sopVersion: sop.sopVersion, + packageVersion: sop.packageVersion + ) + activeExecutionSession = createdSession + isUsingLocalSessionFallback = false + clearOperationsSyncState(clearWarning: false) + await postExecutionEvent( + type: "session_started", + payload: [ + "sop_name": sop.name, + "capture_mode": selectedCaptureModeLabel.lowercased() + ] + ) + return createdSession.id + } catch { + isUsingLocalSessionFallback = true + setCriticalOperationsSyncIssue( + phase: "session_create", + message: "Execution session could not sync. Continuing locally: \(error.localizedDescription)" + ) + return UUID().uuidString + } + } + + private func handleChecklistMutation( + item: ChecklistItemState, + stepIndex: Int, + eventType: String + ) async { + let nextIndex = nextIncompleteStepIndex() + await workerAdminSync?.updateCurrentStepIndex(nextIndex, sendImmediateHeartbeat: true) + await postExecutionEvent( + type: eventType, + payload: [ + "step_index": stepIndex, + "step_name": item.name, + "source": item.completionSource.rawValue, + "checked": item.isChecked + ] + ) + await patchActiveExecutionSession( + ExecutionSessionPatch( + status: "active", + currentSopID: selectedSOP?.remoteID, + currentStepIndex: nextIndex + ) + ) + await syncGeminiSessionInstruction() + } + + private func nextIncompleteStepIndex() -> Int { + checklistItems.firstIndex(where: { !$0.isChecked }) ?? checklistItems.count + } + + private func activeSpotterRequestItems() -> [GeminiLiveSpotter.SpotterRequestItem] { + let nextIndex = nextIncompleteStepIndex() + guard nextIndex < checklistItems.count else { return [] } + let currentStep = checklistItems[nextIndex] + return [ + GeminiLiveSpotter.SpotterRequestItem( + id: currentStep.itemID, + name: currentStep.name, + aiPrompt: currentStep.aiPrompt, + expectedObjects: currentStep.expectedObjects + ) + ] + } + + private func nextCriticalStepTitleAfterActive(in sop: SOPTemplate, nextIndex: Int) -> String? { + let orderedSteps = sop.steps.sorted { $0.order < $1.order } + guard nextIndex + 1 < orderedSteps.count else { return nil } + return orderedSteps + .dropFirst(nextIndex + 1) + .first(where: { $0.critical })? + .title + } + + func debugSpotterTargetIDs() -> [String] { + activeSpotterRequestItems().map(\.id) + } + + private func buildGeminiSessionInstruction(for sopOverride: SOPTemplate? = nil) -> String? { + let sop = sopOverride ?? activeCaptureSOP ?? selectedSOP ?? currentAssignedSOP + guard let sop else { + geminiInstructionSyncStatus = "" + return nil + } + + let orderedSteps = sop.steps.sorted { $0.order < $1.order } + let baseInstruction = GeminiConfig.systemInstruction.trimmingCharacters(in: .whitespacesAndNewlines) + let resolvedBaseInstruction = baseInstruction.isEmpty ? GeminiConfig.defaultSystemInstruction : baseInstruction + + guard !orderedSteps.isEmpty else { + geminiInstructionSyncStatus = "Gemini sync: \(sop.name) · no structured steps" + return """ + \(resolvedBaseInstruction) + + Active SOP: \(sop.name) + The SOP has no structured steps loaded. Ask clarifying questions, narrate what you need to see, and guide the worker toward the next safe action. + """ + } + + let nextIndex = nextIncompleteStepIndex() + let hasRemainingSteps = nextIndex < orderedSteps.count + let step = orderedSteps[min(nextIndex, orderedSteps.count - 1)] + let stepDescription = step.description.trimmingCharacters(in: .whitespacesAndNewlines) + let aiPrompt = step.aiPrompt.trimmingCharacters(in: .whitespacesAndNewlines) + let expectedObjects = step.expectedObjects + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .joined(separator: ", ") + + let directNextAction: String + if hasRemainingSteps { + directNextAction = "Guide the worker through this step now: \(step.title). Use the live camera to decide if it is truly complete before moving on." + } else { + directNextAction = "All SOP steps are marked complete. Confirm the finished state, call out any missing proof, and help the worker wrap up cleanly." + } - // Monitor device availability - deviceMonitorTask = Task { @MainActor in - for await device in deviceSelector.activeDeviceStream() { - self.hasActiveDevice = device != nil - } + geminiInstructionSyncStatus = hasRemainingSteps + ? "Gemini sync: \(sop.name) · Step \(step.order)/\(orderedSteps.count) · \(step.title)" + : "Gemini sync: \(sop.name) · wrap-up guidance active" + + var lines = [ + resolvedBaseInstruction, + "", + "Live SOP context:", + "SOP title: \(sop.name)", + "Current step: \(step.order) of \(orderedSteps.count)", + "Step title: \(step.title)" + ] + + if !stepDescription.isEmpty { + lines.append("Step description: \(stepDescription)") } - setupVideoDecoder() - attachListeners() - } + lines.append("Vision completion prompt: \(aiPrompt.isEmpty ? "Use the step title and description as the visual rule." : aiPrompt)") - private func setupVideoDecoder() { - videoDecoder.setFrameCallback { [weak self] decodedFrame in - Task { @MainActor [weak self] in - guard let self else { return } - let pixelBuffer = decodedFrame.pixelBuffer - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - let rect = CGRect(x: 0, y: 0, width: width, height: height) - if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { - let image = UIImage(cgImage: cgImage) - self.geminiSessionVM?.sendVideoFrameIfThrottled(image: image) - self.webrtcSessionVM?.pushVideoFrame(image) - if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { - NSLog("[Stream] Background frame #%d decoded and forwarded (%dx%d)", - self.backgroundFrameCount, width, height) - } - } - } + if !expectedObjects.isEmpty { + lines.append("Expected objects to look for: \(expectedObjects)") } - } - /// Recreate the StreamSession with the current selectedResolution. - /// Only call when not actively streaming. - func updateResolution(_ resolution: StreamingResolution) { - guard !isStreaming else { return } - selectedResolution = resolution - let config = StreamSessionConfig( - videoCodec: VideoCodec.raw, - resolution: resolution, - frameRate: 24) - streamSession = StreamSession(streamSessionConfig: config, deviceSelector: deviceSelector) - attachListeners() - NSLog("[Stream] Resolution changed to %@", resolutionLabel) + if let nextCritical = nextCriticalStepTitleAfterActive(in: sop, nextIndex: nextIndex) { + lines.append("Next critical checkpoint after this: \(nextCritical)") + } + + lines.append("Direct next action: \(directNextAction)") + lines.append("Treat the vision prompt and expected objects as the active verification rule for your spoken guidance.") + + return lines.joined(separator: "\n") } - private func attachListeners() { - // Subscribe to session state changes using the DAT SDK listener pattern - stateListenerToken = streamSession.statePublisher.listen { [weak self] state in - Task { @MainActor [weak self] in - self?.updateStatusFromState(state) - } - } + func debugGeminiInstructionPreview(for sop: SOPTemplate) -> String? { + buildGeminiSessionInstruction(for: sop) + } - // Subscribe to video frames from the device camera - // This callback fires whether the app is in the foreground or background, - // enabling continuous streaming even when the screen is locked. - videoFrameListenerToken = streamSession.videoFramePublisher.listen { [weak self] videoFrame in - Task { @MainActor [weak self] in - guard let self else { return } + private func syncGeminiSessionInstruction(for sopOverride: SOPTemplate? = nil) async { + await geminiAssistant.refreshSessionInstruction(buildGeminiSessionInstruction(for: sopOverride)) + } - let isInBackground = UIApplication.shared.applicationState == .background + private func syncLivePreviewFrameIfNeeded(image: UIImage) async { + guard isSopAuditRunning else { return } + guard let sessionID = currentSopSessionId else { return } - if !isInBackground { - self.backgroundFrameCount = 0 - self.bgDiagLogged = false - if let image = videoFrame.makeUIImage() { - self.currentVideoFrame = image - if !self.hasReceivedFirstFrame { - self.hasReceivedFirstFrame = true - } - self.geminiSessionVM?.sendVideoFrameIfThrottled(image: image) - self.webrtcSessionVM?.pushVideoFrame(image) - } - } else { - // In background: makeUIImage() uses VideoToolbox GPU rendering which iOS suspends. - // Instead, use our VideoDecoder (VTDecompressionSession) to decode compressed - // frames into pixel buffers, then convert via CPU CIContext. - self.backgroundFrameCount += 1 + let now = Date() + let uploadInterval = hasActiveHelpEscalation ? 1.0 : 2.0 + guard now.timeIntervalSince(lastLivePreviewSyncAt) >= uploadInterval else { return } + lastLivePreviewSyncAt = now - let sampleBuffer = videoFrame.sampleBuffer - let hasCompressedData = CMSampleBufferGetDataBuffer(sampleBuffer) != nil + let compressionQuality = hasActiveHelpEscalation ? 0.55 : 0.65 + guard let jpegData = image.jpegData(compressionQuality: compressionQuality) else { return } - if hasCompressedData { - // Compressed frame (HEVC/H.264) - decode via VTDecompressionSession - do { - try self.videoDecoder.decode(sampleBuffer) - } catch { - if self.backgroundFrameCount <= 5 || self.backgroundFrameCount % 120 == 0 { - NSLog("[Stream] Background frame #%d decode error: %@", - self.backgroundFrameCount, String(describing: error)) - } - } - } else if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { - // Raw pixel buffer - convert directly via CPU CIContext - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - let rect = CGRect(x: 0, y: 0, width: width, height: height) - if let cgImage = self.cpuCIContext.createCGImage(ciImage, from: rect) { - let image = UIImage(cgImage: cgImage) - self.geminiSessionVM?.sendVideoFrameIfThrottled(image: image) - self.webrtcSessionVM?.pushVideoFrame(image) - } - self.videoDecoder.invalidateSession() - } - } - } + if streamingMode == .glasses, let fileURL = sopVideoRecorder?.outputURL { + rememberPendingRecording(sessionID: sessionID, fileURL: fileURL) } - // Subscribe to streaming errors - errorListenerToken = streamSession.errorPublisher.listen { [weak self] error in - Task { @MainActor [weak self] in - guard let self else { return } - // Suppress device-not-found errors when user hasn't started streaming yet - if self.streamingStatus == .stopped { - if case .deviceNotConnected = error { return } - if case .deviceNotFound = error { return } - } - let newErrorMessage = formatStreamingError(error) - if newErrorMessage != self.errorMessage { - showError(newErrorMessage) - } - } + await workerAdminSync?.enqueueFrameUpload(data: jpegData) + } + + private func requestSupervisorHelpFlow() async { + guard canRequestHelp else { + helpStatusMessage = "Start an SOP before requesting live support." + return } - updateStatusFromState(streamSession.state) + isRequestingHelp = true + defer { isRequestingHelp = false } - // Subscribe to photo capture events - photoDataListenerToken = streamSession.photoDataPublisher.listen { [weak self] photoData in - Task { @MainActor [weak self] in - guard let self else { return } - if let uiImage = UIImage(data: photoData.data) { - self.capturedPhoto = uiImage - self.showPhotoPreview = true - } - } + await ensureLiveRoomSession() + + let notes = helpRequestNotes.trimmingCharacters(in: .whitespacesAndNewlines) + + guard let sessionID = activeExecutionSession?.id else { + helpStatusMessage = liveRoomStatusMessage(localOnly: true, helpRequested: true, roomCode: webrtcViewModel.roomCode) + return } - } - func handleStartStreaming() async { - let permission = Permission.camera do { - let status = try await wearables.checkPermissionStatus(permission) - if status == .granted { - await startSession() - return - } - let requestStatus = try await wearables.requestPermission(permission) - if requestStatus == .granted { - await startSession() - return - } - showError("Permission denied") + _ = try await opsAPIClient.createIntervention( + sessionID: sessionID, + type: "help_request", + notes: notes.isEmpty ? "Worker requested assistance." : notes + ) + hasActiveHelpEscalation = true + await workerAdminSync?.updateHelpRequested(true) + await postExecutionEvent( + type: "help_requested", + payload: [ + "notes": notes, + "room_code": webrtcViewModel.roomCode + ] + ) + await patchActiveExecutionSession( + ExecutionSessionPatch( + helpRequested: true, + webrtcRoomCode: webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode + ) + ) + helpStatusMessage = liveRoomStatusMessage(localOnly: false, helpRequested: true, roomCode: webrtcViewModel.roomCode) } catch { - showError("Permission error: \(error.description)") + helpStatusMessage = "Live room is open locally, but the backend help request failed: \(error.localizedDescription)" } } - func startSession() async { - await streamSession.start() - } + private func syncLiveRoomState(roomCode: String) async { + guard !roomCode.isEmpty else { return } + await workerAdminSync?.updateRoomCode(roomCode) + guard activeExecutionSession != nil else { + helpStatusMessage = liveRoomStatusMessage(localOnly: true, helpRequested: hasActiveHelpEscalation, roomCode: roomCode) + return + } - private func showError(_ message: String) { - errorMessage = message - showError = true + helpStatusMessage = liveRoomStatusMessage(localOnly: false, helpRequested: hasActiveHelpEscalation, roomCode: roomCode) + await patchActiveExecutionSession( + ExecutionSessionPatch( + helpRequested: hasActiveHelpEscalation, + webrtcRoomCode: roomCode + ) + ) } - func stopSession() async { - if streamingMode == .iPhone { - stopIPhoneSession() + private func closeCurrentPackageFlow() async { + guard !isClosingPackage else { return } + guard canCloseCurrentPackage else { + packageClosureStatusMessage = "Complete all required SOPs before closing the package." + return + } + guard let packageRunID = activePackageRunID else { + packageClosureStatusMessage = "Package run is not synced yet. Ask ops-api to expose the package close route." + return + } + guard let workerID = workerProfile?.id else { + packageClosureStatusMessage = "Worker context missing. Refresh the assignment queue first." return } - await streamSession.stop() - } - // MARK: - iPhone Camera Mode + isClosingPackage = true + defer { isClosingPackage = false } - func handleStartIPhone() async { - let granted = await IPhoneCameraManager.requestPermission() - if granted { - startIPhoneSession() - } else { - showError("Camera permission denied. Please grant access in Settings.") + do { + _ = try await opsAPIClient.closePackageRun( + packageRunID: packageRunID, + workerID: workerID + ) + packageClosureStatusMessage = "Package closed and synced." + await refreshWorkerContext() + } catch { + packageClosureStatusMessage = "Package close failed: \(error.localizedDescription)" } } - private func startIPhoneSession() { - streamingMode = .iPhone - let camera = IPhoneCameraManager() - camera.onFrameCaptured = { [weak self] image in - Task { @MainActor [weak self] in - guard let self else { return } - self.currentVideoFrame = image - if !self.hasReceivedFirstFrame { - self.hasReceivedFirstFrame = true - } - self.geminiSessionVM?.sendVideoFrameIfThrottled(image: image) - self.webrtcSessionVM?.pushVideoFrame(image) + private func updateHelpStatus(for state: WebRTCConnectionState) { + let localOnly = activeExecutionSession == nil + switch state { + case .connected: + if !hasLoggedRoomJoinedForSession { + hasLoggedRoomJoinedForSession = true + WorkerLiveLogger.log( + "room_joined", + sessionID: currentSopSessionId, + roomCode: webrtcViewModel.roomCode.isEmpty ? nil : webrtcViewModel.roomCode, + uploadState: "active" + ) + } + helpStatusMessage = localOnly + ? "Local live room connected. Admin join stays unavailable until backend sync succeeds." + : "Live viewer connected." + case .waitingForPeer: + if !webrtcViewModel.roomCode.isEmpty { + helpStatusMessage = liveRoomStatusMessage( + localOnly: localOnly, + helpRequested: hasActiveHelpEscalation, + roomCode: webrtcViewModel.roomCode + ) + } + case .connecting: + helpStatusMessage = localOnly + ? "Opening local live room..." + : "Opening live execution room..." + case .backgrounded: + helpStatusMessage = "Live room paused in background. Returning will reconnect." + case .error(let message): + helpStatusMessage = "Live room error: \(message)" + case .disconnected: + if !isRequestingHelp { + helpStatusMessage = "" } } - camera.start() - iPhoneCameraManager = camera - streamingStatus = .streaming - NSLog("[Stream] iPhone camera mode started") } - private func stopIPhoneSession() { - iPhoneCameraManager?.stop() - iPhoneCameraManager = nil - currentVideoFrame = nil - hasReceivedFirstFrame = false - streamingStatus = .stopped - streamingMode = .glasses - NSLog("[Stream] iPhone camera mode stopped") + private func ensureLiveRoomSession() async { + do { + if let routeWarning = try configureWorkerAudioRoute(for: preferredCaptureMode, reason: .viewer) { + helpStatusMessage = routeWarning + } + } catch { + helpStatusMessage = "Audio route error: \(error.localizedDescription)" + } + + let hasRoomCode = !webrtcViewModel.roomCode.isEmpty + + if webrtcViewModel.isActive { + switch webrtcViewModel.connectionState { + case .connected, .waitingForPeer: + if hasRoomCode { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + return + } + case .connecting: + if hasRoomCode { + await syncLiveRoomState(roomCode: webrtcViewModel.roomCode) + return + } + case .backgrounded, .error, .disconnected: + break + } + + helpStatusMessage = "Restarting live room for this SOP..." + webrtcViewModel.stopSession() + } + + await webrtcViewModel.startSession(captureMode: streamingMode) + if let roomCode = await waitForRoomCode() { + await syncLiveRoomState(roomCode: roomCode) + } else if activeExecutionSession == nil { + helpStatusMessage = "Opening local-only live room..." + setOperationsSyncWarning( + phase: "session_patch", + message: "Live room is local-only until the backend execution session sync succeeds." + ) + } else { + helpStatusMessage = "Live room still syncing. Manager join will unlock once the room code is published." + setOperationsSyncWarning( + phase: "session_patch", + message: "Live room did not publish a room code yet. Verify signal settings and session sync before expecting admin join." + ) + } } - func dismissError() { - showError = false - errorMessage = "" + private func postExecutionEvent(type: String, payload: [String: Any]) async { + guard let sessionID = activeExecutionSession?.id else { return } + do { + _ = try await opsAPIClient.postExecutionEvent( + sessionID: sessionID, + eventType: type, + payload: payload + ) + } catch { + setOperationsSyncWarning( + phase: "session_event", + message: "Event sync failed: \(error.localizedDescription)" + ) + } } - func capturePhoto() { - streamSession.capturePhoto(format: .jpeg) + private func patchActiveExecutionSession(_ patch: ExecutionSessionPatch) async { + guard let sessionID = activeExecutionSession?.id else { return } + do { + let updatedSession = try await opsAPIClient.updateExecutionSession(id: sessionID, patch: patch) + activeExecutionSession = updatedSession + if let warning = updatedSession.packageProgressWarning?.trimmingCharacters(in: .whitespacesAndNewlines), + !warning.isEmpty { + setOperationsSyncWarning(phase: "package_progress", message: warning) + } + } catch { + setCriticalOperationsSyncIssue( + phase: "session_patch", + message: "Session sync failed: \(error.localizedDescription)" + ) + } } - func dismissPhotoPreview() { - showPhotoPreview = false - capturedPhoto = nil + private func uploadMediaAssetIfPossible( + assetID: String, + data: Data, + contentType: String, + label: String + ) async -> Bool { + guard !data.isEmpty else { return false } + + do { + let uploadTarget = try await opsAPIClient.requestMediaUploadTarget( + assetID: assetID, + contentType: contentType, + byteCount: data.count + ) + try await opsAPIClient.uploadBinary(to: uploadTarget, data: data, contentType: contentType) + _ = try await opsAPIClient.finalizeMediaAssetUpload( + assetID: assetID, + byteCount: data.count, + contentType: contentType + ) + return true + } catch { + setOperationsSyncWarning( + phase: "media_upload", + message: "\(label) upload is pending until ops-api exposes upload targets. \(error.localizedDescription)" + ) + return false + } } - private func updateStatusFromState(_ state: StreamSessionState) { - switch state { - case .stopped: - currentVideoFrame = nil - streamingStatus = .stopped - case .waitingForDevice, .starting, .stopping, .paused: - streamingStatus = .waiting - case .streaming: - streamingStatus = .streaming + private func uploadEvidenceMediaAssets( + sessionID: String, + proofImages: [String: Data] + ) async { + for (targetID, imageData) in proofImages { + do { + let evidenceAsset = try await opsAPIClient.registerMediaAsset( + sessionID: sessionID, + bucket: "evidence-images", + path: "sessions/\(sessionID)/proof/\(targetID).jpg", + assetType: "photo", + metadata: [ + "item_id": targetID, + "upload_state": "pending", + "bytes": imageData.count + ] + ) + _ = await uploadMediaAssetIfPossible( + assetID: evidenceAsset.id, + data: imageData, + contentType: "image/jpeg", + label: "Evidence image" + ) + } catch { + setOperationsSyncWarning( + phase: "evidence_upload", + message: "Evidence registration failed: \(error.localizedDescription)" + ) + } } } - private func formatStreamingError(_ error: StreamSessionError) -> String { - switch error { - case .internalError: - return "An internal error occurred. Please try again." - case .deviceNotFound: - return "Device not found. Please ensure your device is connected." - case .deviceNotConnected: - return "Device not connected. Please check your connection and try again." - case .timeout: - return "The operation timed out. Please try again." - case .videoStreamingError: - return "Video streaming failed. Please try again." - case .audioStreamingError: - return "Audio streaming failed. Please try again." - case .permissionDenied: - return "Camera permission denied. Please grant permission in Settings." - case .hingesClosed: - return "The hinges on the glasses were closed. Please open the hinges and try again." - @unknown default: - return "An unknown streaming error occurred." + private func createExecutionMemoryLink( + sessionID: String, + sopID: String, + completedSteps: Int + ) async { + do { + _ = try await opsAPIClient.createMemoryLink( + sourceID: sessionID, + sourceType: "execution_session", + targetID: sopID, + targetType: "sop", + linkType: "executed", + metadata: [ + "completed_steps": completedSteps, + "total_steps": checklistItems.count + ] + ) + } catch { + setOperationsSyncWarning( + phase: "memory_link", + message: "Memory link sync failed: \(error.localizedDescription)" + ) } } + + private func updateDossierPipelineStatus(_ message: String, kind: DossierPipelineStatusKind) { + dossierPipelineStatusMessage = message + dossierPipelineStatusKind = kind + dossierPipelineStatusTimestamp = Self.pipelineTimestampFormatter.string(from: Date()) + } } diff --git a/samples/CameraAccess/CameraAccess/Views/CaptureView.swift b/samples/CameraAccess/CameraAccess/Views/CaptureView.swift new file mode 100644 index 00000000..a944870e --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Views/CaptureView.swift @@ -0,0 +1,468 @@ +import AVFoundation +import SwiftUI +import UIKit + +struct CaptureView: View { + @Environment(\.dismiss) private var dismiss + @ObservedObject var viewModel: StreamSessionViewModel + let sop: SOPTemplate + @State private var highlightedChecklistItemID: UUID? + @State private var isFinishingSOP: Bool = false + @State private var finishButtonPulse: Bool = false + + var body: some View { + GeometryReader { geometry in + ZStack { + DesignSystem.colors.deepNavy + .ignoresSafeArea() + + if viewModel.streamingMode == .iPhone, let previewSession = viewModel.iPhonePreviewSession { + IPhoneCameraPreviewSurface(session: previewSession) + .frame(width: geometry.size.width, height: geometry.size.height) + .clipped() + .ignoresSafeArea() + + if !viewModel.hasReceivedFirstFrame { + ProgressView() + .progressViewStyle(.circular) + .tint(DesignSystem.colors.vibrantTeal) + } + } else if let frame = viewModel.currentVideoFrame, viewModel.hasReceivedFirstFrame { + Image(uiImage: frame) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width, height: geometry.size.height) + .clipped() + .ignoresSafeArea() + } else { + ProgressView() + .progressViewStyle(.circular) + .tint(DesignSystem.colors.vibrantTeal) + } + + if viewModel.streamingMode == .iPhone, + viewModel.webrtcViewModel.incomingRemoteVideoEnabled, + viewModel.webrtcViewModel.hasRemoteVideo { + VStack { + HStack { + Spacer() + RTCVideoView(videoTrack: viewModel.webrtcViewModel.remoteVideoTrack) + .frame(width: 116, height: 156) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(DesignSystem.colors.white.opacity(0.8), lineWidth: 1) + ) + .padding(.top, 12) + .padding(.trailing, 12) + } + Spacer() + } + } + + VStack { + checklistOverlay(maxHeight: geometry.size.height * 0.2) + .padding(.top, 12) + .padding(.horizontal, 12) + + if !viewModel.dossierPipelineStatusMessage.isEmpty || viewModel.isDossierUploading { + pipelineStatusBadge + .padding(.top, 8) + .padding(.horizontal, 12) + } + + if viewModel.canRequestHelp || viewModel.webrtcViewModel.isActive || !viewModel.helpStatusMessage.isEmpty { + supportStatusBar + .padding(.top, 8) + .padding(.horizontal, 12) + } + + Spacer() + + bottomBar + .padding(12) + } + } + } + .navigationTitle(sop.name) + .navigationBarTitleDisplayMode(.inline) + .toolbarColorScheme(.dark, for: .navigationBar) + .task { + await viewModel.beginLiveCapture(for: sop) + } + .onChange(of: viewModel.shouldDismissCapture) { _, shouldDismiss in + guard shouldDismiss else { return } + viewModel.clearCaptureDismissFlag() + dismiss() + } + .onDisappear { + viewModel.stopHoldToTalk() + if viewModel.isSopAuditRunning { + viewModel.userTappedEndAndShip() + } + } + } + + private func checklistOverlay(maxHeight: CGFloat) -> some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("CHECKLIST") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + Spacer() + Text(viewModel.progressText) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + } + + ScrollView(showsIndicators: true) { + VStack(alignment: .leading, spacing: 6) { + ForEach(viewModel.checklistItems) { item in + let isHighlighted = highlightedChecklistItemID == item.id + + Button { + withAnimation(.spring(response: 0.28, dampingFraction: 0.76)) { + viewModel.toggleChecklistItem(itemID: item.id, viaVoice: false) + } + animateChecklistSelection(item.id) + } label: { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 8) { + Image(systemName: item.isChecked ? "checkmark" : "minus") + .font(.system(size: 12, weight: .bold, design: .monospaced)) + .foregroundColor(item.isChecked ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.blueGrey) + .symbolEffect(.bounce, value: item.isChecked) + + Text(item.name) + .font(DesignSystem.fonts.mono(size: 14, weight: .semibold)) + .foregroundColor(item.isChecked ? DesignSystem.colors.white : DesignSystem.colors.blueGrey) + .strikethrough(item.isChecked, color: DesignSystem.colors.vibrantTeal) + .multilineTextAlignment(.leading) + + Spacer(minLength: 0) + } + + HStack(spacing: 8) { + if !item.duration.isEmpty { + Text(item.duration.uppercased()) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + + Text(item.validation.uppercased()) + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + + if item.critical { + Text("CRITICAL") + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(.red) + } + + if !item.allowManualComplete && !item.isChecked { + Text("VISION") + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(.orange) + } + } + } + .padding(.vertical, 4) + .padding(.horizontal, 6) + .background(DesignSystem.colors.vibrantTeal.opacity(isHighlighted ? 0.16 : 0.0)) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(DesignSystem.colors.vibrantTeal.opacity(isHighlighted ? 0.85 : 0.0), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 6)) + .scaleEffect(isHighlighted ? 1.015 : 1.0) + .animation(.easeInOut(duration: 0.22), value: isHighlighted) + } + .disabled(!item.allowManualComplete && !item.isChecked) + .opacity((!item.allowManualComplete && !item.isChecked) ? 0.75 : 1) + } + } + } + } + .padding(12) + .frame(maxWidth: .infinity, maxHeight: maxHeight, alignment: .topLeading) + .background(DesignSystem.colors.deepNavy.opacity(0.62)) + .overlay(Rectangle().stroke(DesignSystem.colors.blueGrey.opacity(0.5), lineWidth: 1)) + } + + private var bottomBar: some View { + HStack(spacing: 12) { + holdToTalkButton + Button { + animateFinishSOPPress() + viewModel.userTappedEndAndShip() + } label: { + HStack(spacing: 8) { + if isFinishingSOP { + Image(systemName: "checkmark.circle.fill") + .transition(.scale.combined(with: .opacity)) + } + Text(isFinishingSOP ? "FINISHING..." : "FINISH SOP") + } + } + .brutalistDangerButton() + .scaleEffect(finishButtonPulse ? 1.04 : 1.0) + .opacity(isFinishingSOP ? 0.92 : 1.0) + .animation(.spring(response: 0.25, dampingFraction: 0.65), value: finishButtonPulse) + .animation(.easeInOut(duration: 0.2), value: isFinishingSOP) + .disabled(isFinishingSOP) + } + } + + private var pipelineStatusBadge: some View { + let accentColor: Color = { + switch viewModel.dossierPipelineStatusKind { + case .info: + return DesignSystem.colors.blueGrey + case .active: + return DesignSystem.colors.vibrantTeal + case .success: + return DesignSystem.colors.deepGreen + case .error: + return .red + } + }() + + let statusIconName: String = { + switch viewModel.dossierPipelineStatusKind { + case .info: + return "info.circle.fill" + case .active: + return "dot.radiowaves.left.and.right" + case .success: + return "checkmark.seal.fill" + case .error: + return "exclamationmark.triangle.fill" + } + }() + + return HStack(spacing: 8) { + if viewModel.isDossierUploading { + ProgressView() + .tint(accentColor) + .scaleEffect(0.8) + } else { + Image(systemName: statusIconName) + .foregroundColor(accentColor) + .font(.system(size: 12, weight: .semibold)) + } + + HStack(spacing: 6) { + if !viewModel.dossierPipelineStatusTimestamp.isEmpty { + Text("[\(viewModel.dossierPipelineStatusTimestamp)]") + .font(DesignSystem.fonts.mono(size: 10, weight: .semibold)) + .foregroundColor(accentColor) + .lineLimit(1) + } + + Text(viewModel.dossierPipelineStatusMessage) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .lineLimit(2) + } + + Spacer(minLength: 0) + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(DesignSystem.colors.deepNavy.opacity(0.78)) + .background(accentColor.opacity(0.12)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(accentColor.opacity(0.9), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .transition(.move(edge: .top).combined(with: .opacity)) + .animation(.easeInOut(duration: 0.2), value: viewModel.dossierPipelineStatusMessage) + .animation(.easeInOut(duration: 0.2), value: viewModel.isDossierUploading) + .animation(.easeInOut(duration: 0.2), value: viewModel.dossierPipelineStatusKind) + .animation(.easeInOut(duration: 0.2), value: viewModel.dossierPipelineStatusTimestamp) + } + + private var supportStatusBar: some View { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + if viewModel.webrtcViewModel.isActive { + WebRTCStatusBar(webrtcVM: viewModel.webrtcViewModel) + } else { + Text("SUPPORT") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(DesignSystem.colors.deepNavy.opacity(0.6)) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.8), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + + Spacer(minLength: 0) + + if viewModel.streamingMode == .iPhone, + viewModel.webrtcViewModel.isActive { + Button( + viewModel.webrtcViewModel.incomingRemoteVideoEnabled + ? "HIDE SUP VIDEO" + : "SHOW SUP VIDEO" + ) { + viewModel.webrtcViewModel.setIncomingRemoteVideoEnabled( + !viewModel.webrtcViewModel.incomingRemoteVideoEnabled + ) + } + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(DesignSystem.colors.deepNavy.opacity(0.82)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.white.opacity(0.8), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + Button(viewModel.isRequestingHelp ? "REQUESTING..." : "REQUEST HELP") { + viewModel.requestSupervisorHelp() + } + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.deepNavy) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(DesignSystem.colors.vibrantTeal) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.white, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .disabled(!viewModel.canRequestHelp || viewModel.isRequestingHelp) + .opacity(viewModel.canRequestHelp ? 1 : 0.5) + } + + if !viewModel.helpStatusMessage.isEmpty { + Text(viewModel.helpStatusMessage) + .font(DesignSystem.fonts.body(size: 12)) + .foregroundColor(DesignSystem.colors.white) + .multilineTextAlignment(.leading) + } + + if !viewModel.geminiInstructionSyncStatus.isEmpty { + Text(viewModel.geminiInstructionSyncStatus) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + .multilineTextAlignment(.leading) + } + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(DesignSystem.colors.deepNavy.opacity(0.78)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.8), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + private var holdToTalkButton: some View { + let listening = viewModel.isListeningForVoice + return Text(listening ? "LISTENING..." : "HOLD TO TALK") + .font(DesignSystem.fonts.mono(size: 15, weight: .semibold)) + .foregroundColor(listening ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) + .padding(.vertical, 14) + .frame(maxWidth: .infinity) + .background(listening ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) + .scaleEffect(listening ? 1.03 : 1.0) + .animation(.easeInOut(duration: 0.18), value: listening) + .simultaneousGesture( + DragGesture(minimumDistance: 0) + .onChanged { _ in + if !viewModel.isListeningForVoice { + viewModel.startHoldToTalk() + } + } + .onEnded { _ in + viewModel.stopHoldToTalk() + } + ) + } + + private func animateChecklistSelection(_ itemID: UUID) { + withAnimation(.easeInOut(duration: 0.18)) { + highlightedChecklistItemID = itemID + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.55) { + withAnimation(.easeOut(duration: 0.22)) { + if highlightedChecklistItemID == itemID { + highlightedChecklistItemID = nil + } + } + } + } + + private func animateFinishSOPPress() { + guard !isFinishingSOP else { return } + + withAnimation(.spring(response: 0.24, dampingFraction: 0.7)) { + isFinishingSOP = true + finishButtonPulse = true + } + + Task { @MainActor in + try? await Task.sleep(nanoseconds: 450_000_000) + withAnimation(.easeOut(duration: 0.2)) { + finishButtonPulse = false + } + + try? await Task.sleep(nanoseconds: 700_000_000) + withAnimation(.easeInOut(duration: 0.2)) { + isFinishingSOP = false + } + } + } +} + +private struct IPhoneCameraPreviewSurface: UIViewRepresentable { + let session: AVCaptureSession + + func makeUIView(context: Context) -> PreviewContainerView { + let view = PreviewContainerView() + view.previewLayer.videoGravity = .resizeAspectFill + view.previewLayer.session = session + if let connection = view.previewLayer.connection, + connection.isVideoRotationAngleSupported(90) { + connection.videoRotationAngle = 90 + } + return view + } + + func updateUIView(_ uiView: PreviewContainerView, context: Context) { + if uiView.previewLayer.session !== session { + uiView.previewLayer.session = session + } + if let connection = uiView.previewLayer.connection, + connection.isVideoRotationAngleSupported(90) { + connection.videoRotationAngle = 90 + } + } + + static func dismantleUIView(_ uiView: PreviewContainerView, coordinator: ()) { + uiView.previewLayer.session = nil + } +} + +private final class PreviewContainerView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var previewLayer: AVCaptureVideoPreviewLayer { + layer as! AVCaptureVideoPreviewLayer + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/Components/CustomButton.swift b/samples/CameraAccess/CameraAccess/Views/Components/CustomButton.swift index 2766f522..e255f7d1 100644 --- a/samples/CameraAccess/CameraAccess/Views/Components/CustomButton.swift +++ b/samples/CameraAccess/CameraAccess/Views/Components/CustomButton.swift @@ -26,20 +26,33 @@ struct CustomButton: View { var backgroundColor: Color { switch self { case .primary: - return .appPrimary + return DesignSystem.colors.vibrantTeal case .secondary: - return Color(white: 0.25) + return DesignSystem.colors.surface case .destructive: - return .destructiveBackground + return DesignSystem.colors.dangerRed } } var foregroundColor: Color { switch self { - case .primary, .secondary: + case .primary: + return DesignSystem.colors.deepNavy + case .secondary: return .white case .destructive: - return .destructiveForeground + return .white + } + } + + var borderColor: Color { + switch self { + case .primary: + return DesignSystem.colors.border + case .secondary: + return DesignSystem.colors.border + case .destructive: + return DesignSystem.colors.dangerRed } } } @@ -52,6 +65,10 @@ struct CustomButton: View { .frame(maxWidth: .infinity) .frame(height: 56) .background(style.backgroundColor) + .overlay( + RoundedRectangle(cornerRadius: 30) + .stroke(style.borderColor, lineWidth: 1) + ) .cornerRadius(30) } .disabled(isDisabled) diff --git a/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift b/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift index 67ec11fb..b711a8ce 100644 --- a/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift +++ b/samples/CameraAccess/CameraAccess/Views/Components/GeminiOverlayView.swift @@ -8,7 +8,7 @@ struct GeminiStatusBar: View { // Gemini connection pill StatusPill(color: geminiStatusColor, text: geminiStatusText) - // OpenClaw connection pill + // Video AI Analyst bridge connection pill StatusPill(color: openClawStatusColor, text: openClawStatusText) } } @@ -42,10 +42,10 @@ struct GeminiStatusBar: View { private var openClawStatusText: String { switch geminiVM.openClawConnectionState { - case .connected: return "OpenClaw" - case .checking: return "OpenClaw..." - case .unreachable: return "OpenClaw Off" - case .notConfigured: return "No OpenClaw" + case .connected: return "Video AI Analyst" + case .checking: return "Video AI Analyst..." + case .unreachable: return "Analyst Off" + case .notConfigured: return "No Analyst" } } } @@ -170,3 +170,59 @@ struct SpeakingIndicator: View { .onDisappear { animating = false } } } + +struct GeminiAssistantOverlay: View { + @ObservedObject var geminiVM: GeminiSessionViewModel + let onToggle: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .center, spacing: 10) { + GeminiStatusBar(geminiVM: geminiVM) + Spacer(minLength: 0) + Button(action: onToggle) { + Text(geminiVM.isGeminiActive ? "STOP AI" : "START AI") + .font(.system(size: 12, weight: .semibold, design: .monospaced)) + .foregroundColor(.white) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(Color.black.opacity(0.68)) + .overlay( + RoundedRectangle(cornerRadius: 14) + .stroke(Color.white.opacity(0.2), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 14)) + } + } + + if !geminiVM.userTranscript.isEmpty || !geminiVM.aiTranscript.isEmpty { + TranscriptView(userText: geminiVM.userTranscript, aiText: geminiVM.aiTranscript) + } + + HStack(spacing: 10) { + ToolCallStatusView(status: geminiVM.toolCallStatus) + if geminiVM.isModelSpeaking { + SpeakingIndicator() + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background(Color.black.opacity(0.55)) + .cornerRadius(16) + } + Spacer(minLength: 0) + } + + if let errorMessage = geminiVM.errorMessage, !errorMessage.isEmpty { + Text(errorMessage) + .font(.system(size: 12, weight: .medium)) + .foregroundColor(.white) + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background(Color.red.opacity(0.28)) + .cornerRadius(12) + } + } + .padding(.horizontal, 16) + .padding(.top, 18) + .frame(maxWidth: .infinity, alignment: .leading) + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift b/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift new file mode 100644 index 00000000..d7adba1e --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Views/DesignSystem.swift @@ -0,0 +1,105 @@ +import SwiftUI + +extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (255, 0, 0, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} + +enum DesignSystem { + enum colors { + static let deepNavy = Color(hex: "#080D18") + static let surface = Color(hex: "#111827") + static let surfaceRaised = Color(hex: "#1F2937") + static let border = Color(hex: "#374151") + static let vibrantTeal = Color(hex: "#06B6D4") + static let deepGreen = Color(hex: "#10B981") + static let warningAmber = Color(hex: "#F59E0B") + static let dangerRed = Color(hex: "#EF4444") + static let white = Color(hex: "#FFFFFF") + static let blueGrey = Color(hex: "#9CA3AF") + } + + enum fonts { + static func mono(size: CGFloat, weight: Font.Weight = .regular) -> Font { + .system(size: size, weight: weight, design: .monospaced) + } + + static func body(size: CGFloat, weight: Font.Weight = .regular) -> Font { + .system(size: size, weight: weight, design: .default) + } + } +} + +struct BrutalistCardModifier: ViewModifier { + let stroke: Color + + func body(content: Content) -> some View { + content + .padding(16) + .background(DesignSystem.colors.surface) + .overlay( + Rectangle() + .stroke(stroke, lineWidth: 1) + ) + } +} + +struct BrutalistPrimaryButtonModifier: ViewModifier { + func body(content: Content) -> some View { + content + .font(DesignSystem.fonts.mono(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.deepNavy) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(DesignSystem.colors.vibrantTeal) + .overlay(Rectangle().stroke(DesignSystem.colors.border, lineWidth: 1)) + } +} + +struct BrutalistDangerButtonModifier: ViewModifier { + func body(content: Content) -> some View { + content + .font(DesignSystem.fonts.mono(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(DesignSystem.colors.surface) + .overlay(Rectangle().stroke(DesignSystem.colors.vibrantTeal, lineWidth: 1)) + } +} + +extension View { + func brutalistCard(stroke: Color = DesignSystem.colors.blueGrey) -> some View { + modifier(BrutalistCardModifier(stroke: stroke)) + } + + func brutalistPrimaryButton() -> some View { + modifier(BrutalistPrimaryButtonModifier()) + } + + func brutalistDangerButton() -> some View { + modifier(BrutalistDangerButtonModifier()) + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/HistoryView.swift b/samples/CameraAccess/CameraAccess/Views/HistoryView.swift new file mode 100644 index 00000000..be6fa474 --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Views/HistoryView.swift @@ -0,0 +1,42 @@ +import SwiftUI + +struct HistoryView: View { + @ObservedObject var viewModel: StreamSessionViewModel + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 14) { + if viewModel.shippedHistory.isEmpty { + VStack(alignment: .leading, spacing: 8) { + Text("NO EXECUTIONS YET") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + Text("Completed sessions will appear here after you finish a run.") + .font(DesignSystem.fonts.body(size: 14)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + .brutalistCard(stroke: DesignSystem.colors.blueGrey) + } else { + ForEach(viewModel.shippedHistory) { session in + VStack(alignment: .leading, spacing: 6) { + Text(session.sopName) + .font(DesignSystem.fonts.mono(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + + Text(session.timestampText) + .font(DesignSystem.fonts.body(size: 13)) + .foregroundColor(DesignSystem.colors.blueGrey) + + Text(session.status.uppercased()) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(session.status.lowercased().contains("local") ? .orange : DesignSystem.colors.deepGreen) + } + .brutalistCard(stroke: DesignSystem.colors.blueGrey) + } + } + } + .padding(14) + } + .background(DesignSystem.colors.deepNavy) + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/HomeScreenView.swift b/samples/CameraAccess/CameraAccess/Views/HomeScreenView.swift index 8a40bbf1..5fd08572 100644 --- a/samples/CameraAccess/CameraAccess/Views/HomeScreenView.swift +++ b/samples/CameraAccess/CameraAccess/Views/HomeScreenView.swift @@ -22,59 +22,84 @@ struct HomeScreenView: View { var body: some View { ZStack { - Color.white.edgesIgnoringSafeArea(.all) + DesignSystem.colors.deepNavy + .ignoresSafeArea() - VStack(spacing: 12) { + VStack(spacing: 18) { HStack { Spacer() Button { showSettings = true } label: { - Image(systemName: "gearshape") - .resizable() - .aspectRatio(contentMode: .fit) - .foregroundColor(.black) - .frame(width: 24, height: 24) + Image(systemName: "slider.horizontal.3") + .font(.system(size: 18, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + .frame(width: 42, height: 42) + .background(DesignSystem.colors.surface) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(DesignSystem.colors.border, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 12)) } } - Spacer() - - Image(.cameraAccessIcon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 120) + Spacer(minLength: 0) + + VStack(spacing: 14) { + Image(.cameraAccessIcon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 92) + .padding(18) + .background(DesignSystem.colors.surface) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(DesignSystem.colors.vibrantTeal.opacity(0.45), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 20)) + + Text("EMBARCADERO WORKER") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + + Text("Connect your glasses, load today’s package, and execute the next step hands-free.") + .font(DesignSystem.fonts.body(size: 17)) + .foregroundColor(DesignSystem.colors.white) + .multilineTextAlignment(.center) + .padding(.horizontal, 10) + } VStack(spacing: 12) { HomeTipItemView( resource: .smartGlassesIcon, - title: "Video Capture", - text: "Record videos directly from your glasses, from your point of view." + title: "Assigned SOP Packages", + text: "Sync your worker queue from ops-api so the current package and SOP line are ready when you begin." ) HomeTipItemView( resource: .soundIcon, - title: "Open-Ear Audio", - text: "Hear notifications while keeping your ears open to the world around you." + title: "Live Supervisor Support", + text: "Request jump-in help during an execution and keep the room synced while you stay on task." ) HomeTipItemView( resource: .walkingIcon, - title: "Enjoy On-the-Go", - text: "Stay hands-free while you move through your day. Move freely, stay connected." + title: "Phone Or Glasses", + text: "Use Ray-Bans when available, or continue on iPhone without breaking the execution flow." ) } - Spacer() + Spacer(minLength: 0) - VStack(spacing: 20) { - Text("You'll be redirected to the Meta AI app to confirm your connection.") - .font(.system(size: 14)) - .foregroundColor(.gray) + VStack(spacing: 16) { + Text("You’ll be redirected to the Meta AI app once to confirm the glasses connection.") + .font(DesignSystem.fonts.body(size: 14)) + .foregroundColor(DesignSystem.colors.blueGrey) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .padding(.horizontal, 12) CustomButton( - title: viewModel.registrationState == .registering ? "Connecting..." : "Connect my glasses", + title: viewModel.registrationState == .registering ? "CONNECTING GLASSES..." : "CONNECT RAY-BAN GLASSES", style: .primary, isDisabled: viewModel.registrationState == .registering ) { @@ -82,7 +107,7 @@ struct HomeScreenView: View { } CustomButton( - title: "Start on iPhone", + title: "USE IPHONE CAMERA", style: .secondary, isDisabled: false ) { @@ -109,7 +134,7 @@ struct HomeTipItemView: View { Image(resource) .resizable() .renderingMode(.template) - .foregroundColor(.black) + .foregroundColor(DesignSystem.colors.vibrantTeal) .aspectRatio(contentMode: .fit) .frame(width: 24) .padding(.leading, 4) @@ -117,14 +142,21 @@ struct HomeTipItemView: View { VStack(alignment: .leading, spacing: 6) { Text(title) - .font(.system(size: 18, weight: .semibold)) - .foregroundColor(.black) + .font(DesignSystem.fonts.mono(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) Text(text) - .font(.system(size: 15)) - .foregroundColor(.gray) + .font(DesignSystem.fonts.body(size: 14)) + .foregroundColor(DesignSystem.colors.blueGrey) } Spacer() } + .padding(14) + .background(DesignSystem.colors.surface) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(DesignSystem.colors.border, lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 16)) } } diff --git a/samples/CameraAccess/CameraAccess/Views/HomeView.swift b/samples/CameraAccess/CameraAccess/Views/HomeView.swift new file mode 100644 index 00000000..72ab6fbc --- /dev/null +++ b/samples/CameraAccess/CameraAccess/Views/HomeView.swift @@ -0,0 +1,296 @@ +import SwiftUI + +private enum WorkerHomeSheet: String, Identifiable { + case history + case settings + + var id: String { rawValue } +} + +struct HomeView: View { + @Environment(\.scenePhase) private var scenePhase + @ObservedObject var viewModel: StreamSessionViewModel + @State private var activeSheet: WorkerHomeSheet? + + var body: some View { + VStack(alignment: .leading, spacing: 14) { + header + actionControls + captureModeControls + pendingTasksSection + } + .padding(14) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) + .toolbar(.hidden, for: .navigationBar) + .sheet(item: $activeSheet) { sheet in + switch sheet { + case .history: + NavigationStack { + HistoryView(viewModel: viewModel) + .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) + .navigationTitle("HISTORY") + .navigationBarTitleDisplayMode(.inline) + } + case .settings: + SettingsView() + } + } + .fullScreenCover(item: $viewModel.activeCaptureSOP) { sop in + NavigationStack { + CaptureView(viewModel: viewModel, sop: sop) + } + } + .task { + await viewModel.handleWorkerHomeEntered() + } + .onChange(of: scenePhase) { _, newPhase in + guard newPhase == .active else { return } + Task { + await viewModel.handleWorkerAppBecameActive() + } + } + } + + private var header: some View { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 6) { + Text(viewModel.workerDisplayName.uppercased()) + .font(DesignSystem.fonts.mono(size: 24, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + + Text("\(viewModel.workerRoleText) · \(viewModel.pendingShiftLabel)") + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + + Text(viewModel.pendingTaskHeaderSummary) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + } + + Spacer() + + Button { + activeSheet = .settings + } label: { + Image(systemName: "slider.horizontal.3") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + .frame(width: 42, height: 42) + .background(DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) + } + .buttonStyle(.plain) + } + } + + private var actionControls: some View { + HStack(spacing: 0) { + actionButton(title: "SYNC TASKS") { + Task { + await viewModel.refreshWorkerContext() + } + } + + actionButton(title: "HISTORY") { + activeSheet = .history + } + } + .frame(height: 44) + .background(DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) + } + + private var captureModeControls: some View { + HStack(spacing: 0) { + modeButton(title: "IPHONE CAMERA", mode: .iPhone, enabled: true) + modeButton(title: "META CAMERA", mode: .glasses, enabled: viewModel.hasActiveDevice) + } + .frame(height: 44) + .background(DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) + } + + private var pendingTasksSection: some View { + VStack(alignment: .leading, spacing: 0) { + Text("PENDING TASKS") + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + .padding(.bottom, 8) + + if viewModel.isSyncingOperations { + syncBanner + .padding(.bottom, 10) + } + + if let message = viewModel.operationsSyncError, + !message.isEmpty { + noticeBanner(title: "OPS NOTICE", body: message) + .padding(.bottom, viewModel.pendingTaskSOPs.isEmpty ? 0 : 10) + } + + if let warning = viewModel.operationsSyncWarning, + !warning.isEmpty { + statusBanner( + title: "SYNC WARNING", + body: warning, + accent: DesignSystem.colors.warningAmber + ) + .padding(.bottom, viewModel.pendingTaskSOPs.isEmpty ? 0 : 10) + } + + if viewModel.pendingTaskSOPs.isEmpty { + emptyStateCard( + title: "NO PENDING TASKS", + body: "All assigned SOPs for this demo shift are complete. Reopen the app to reset the shift list." + ) + } else { + VStack(spacing: 0) { + ForEach(viewModel.pendingTaskSOPs) { sop in + Button { + viewModel.presentCapture(for: sop) + } label: { + PendingTaskRow( + sop: sop, + packageTitle: sop.packageTitle, + isTopTask: sop.id == viewModel.pendingTaskSOPs.first?.id + ) + } + .buttonStyle(.plain) + + if sop.id != viewModel.pendingTaskSOPs.last?.id { + Divider() + .overlay(DesignSystem.colors.white.opacity(0.08)) + } + } + } + .background(DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white.opacity(0.16), lineWidth: 1)) + } + } + } + + private var syncBanner: some View { + HStack(spacing: 10) { + ProgressView() + .tint(DesignSystem.colors.vibrantTeal) + + Text("Syncing pending tasks…") + .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(DesignSystem.colors.deepNavy.opacity(0.75)) + .overlay(Rectangle().stroke(DesignSystem.colors.vibrantTeal.opacity(0.5), lineWidth: 1)) + } + + private func noticeBanner(title: String, body: String) -> some View { + statusBanner( + title: title, + body: body, + accent: DesignSystem.colors.vibrantTeal + ) + } + + private func statusBanner(title: String, body: String, accent: Color) -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(accent) + + Text(body) + .font(DesignSystem.fonts.body(size: 14)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(DesignSystem.colors.deepNavy.opacity(0.75)) + .overlay(Rectangle().stroke(accent.opacity(0.32), lineWidth: 1)) + } + + private func emptyStateCard(title: String, body: String) -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(DesignSystem.colors.vibrantTeal) + + Text(body) + .font(DesignSystem.fonts.body(size: 14)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white.opacity(0.16), lineWidth: 1)) + } + + private func actionButton(title: String, action: @escaping () -> Void) -> some View { + Button(action: action) { + Text(title) + .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.deepNavy) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(DesignSystem.colors.vibrantTeal) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 0.5)) + } + .buttonStyle(.plain) + } + + private func modeButton(title: String, mode: StreamingMode, enabled: Bool) -> some View { + let selected = viewModel.preferredCaptureMode == mode + return Button { + viewModel.selectCaptureMode(mode) + } label: { + Text(title) + .font(DesignSystem.fonts.mono(size: 12, weight: .semibold)) + .foregroundColor(selected ? DesignSystem.colors.deepNavy : DesignSystem.colors.white) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(selected ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.deepNavy) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 0.5)) + } + .disabled(!enabled) + .opacity(enabled ? 1.0 : 0.45) + .buttonStyle(.plain) + } +} + +private struct PendingTaskRow: View { + let sop: SOPTemplate + let packageTitle: String? + let isTopTask: Bool + + private var taskSubtitle: String { + let duration = "\(Int(max(sop.estimatedDuration, 15)))S" + let prefix = "\(sop.items.count) ITEMS · EST. \(duration)" + if let packageTitle, !packageTitle.isEmpty { + return "\(prefix) · \(packageTitle.uppercased())" + } + return prefix + } + + var body: some View { + HStack(alignment: .center, spacing: 12) { + VStack(alignment: .leading, spacing: 6) { + Text(sop.name) + .font(DesignSystem.fonts.mono(size: 22, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .multilineTextAlignment(.leading) + + Text(taskSubtitle) + .font(DesignSystem.fonts.mono(size: 11, weight: .semibold)) + .foregroundColor(isTopTask ? DesignSystem.colors.vibrantTeal : DesignSystem.colors.blueGrey) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(DesignSystem.colors.blueGrey) + } + .padding(.horizontal, 14) + .padding(.vertical, 18) + .frame(maxWidth: .infinity, alignment: .leading) + .background(isTopTask ? DesignSystem.colors.deepNavy.opacity(0.92) : DesignSystem.colors.deepNavy) + } +} diff --git a/samples/CameraAccess/CameraAccess/Views/MainAppView.swift b/samples/CameraAccess/CameraAccess/Views/MainAppView.swift index 4bfe2bde..b83cbc26 100644 --- a/samples/CameraAccess/CameraAccess/Views/MainAppView.swift +++ b/samples/CameraAccess/CameraAccess/Views/MainAppView.swift @@ -21,13 +21,21 @@ struct MainAppView: View { let wearables: WearablesInterface @ObservedObject private var viewModel: WearablesViewModel + private var canUseMockDevices: Bool { + #if DEBUG + return true + #else + return false + #endif + } + init(wearables: WearablesInterface, viewModel: WearablesViewModel) { self.wearables = wearables self.viewModel = viewModel } var body: some View { - if viewModel.registrationState == .registered || viewModel.hasMockDevice || viewModel.skipToIPhoneMode { + if viewModel.registrationState == .registered || (canUseMockDevices && viewModel.hasMockDevice) || viewModel.skipToIPhoneMode { StreamSessionView(wearables: wearables, wearablesVM: viewModel) } else { // User not registered - show registration/onboarding flow diff --git a/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift b/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift index 8fa01b55..47c960b7 100644 --- a/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift +++ b/samples/CameraAccess/CameraAccess/Views/StreamSessionView.swift @@ -19,8 +19,6 @@ struct StreamSessionView: View { let wearables: WearablesInterface @ObservedObject private var wearablesViewModel: WearablesViewModel @StateObject private var viewModel: StreamSessionViewModel - @StateObject private var geminiVM = GeminiSessionViewModel() - @StateObject private var webrtcVM = WebRTCSessionViewModel() init(wearables: WearablesInterface, wearablesVM: WearablesViewModel) { self.wearables = wearables @@ -29,23 +27,24 @@ struct StreamSessionView: View { } var body: some View { - ZStack { - if viewModel.isStreaming { - // Full-screen video view with streaming controls - StreamView(viewModel: viewModel, wearablesVM: wearablesViewModel, geminiVM: geminiVM, webrtcVM: webrtcVM) - } else { - // Pre-streaming setup view with permissions and start button - NonStreamView(viewModel: viewModel, wearablesVM: wearablesViewModel) - } - } - .task { - viewModel.geminiSessionVM = geminiVM - viewModel.webrtcSessionVM = webrtcVM - geminiVM.streamingMode = viewModel.streamingMode + NavigationStack { + HomeView(viewModel: viewModel) + .background(DesignSystem.colors.deepNavy.ignoresSafeArea()) } - .onChange(of: viewModel.streamingMode) { newMode in - geminiVM.streamingMode = newMode + .overlay(alignment: .bottom) { + if viewModel.showShipSuccessToast { + Text("Execution recorded") + .font(DesignSystem.fonts.mono(size: 13, weight: .semibold)) + .foregroundColor(DesignSystem.colors.white) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(DesignSystem.colors.deepGreen) + .overlay(Rectangle().stroke(DesignSystem.colors.white, lineWidth: 1)) + .padding(.bottom, 20) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } } + .animation(.easeInOut(duration: 0.2), value: viewModel.showShipSuccessToast) .onAppear { UIApplication.shared.isIdleTimerDisabled = true } diff --git a/samples/CameraAccess/CameraAccess/Views/StreamView.swift b/samples/CameraAccess/CameraAccess/Views/StreamView.swift index 3fc83f72..8d058ab4 100644 --- a/samples/CameraAccess/CameraAccess/Views/StreamView.swift +++ b/samples/CameraAccess/CameraAccess/Views/StreamView.swift @@ -20,8 +20,6 @@ import SwiftUI struct StreamView: View { @ObservedObject var viewModel: StreamSessionViewModel @ObservedObject var wearablesVM: WearablesViewModel - @ObservedObject var geminiVM: GeminiSessionViewModel - @ObservedObject var webrtcVM: WebRTCSessionViewModel var body: some View { ZStack { @@ -29,14 +27,8 @@ struct StreamView: View { Color.black .edgesIgnoringSafeArea(.all) - // Video backdrop: PiP when WebRTC connected, otherwise single local feed - if webrtcVM.isActive && webrtcVM.connectionState == .connected { - PiPVideoView( - localFrame: viewModel.currentVideoFrame, - remoteVideoTrack: webrtcVM.remoteVideoTrack, - hasRemoteVideo: webrtcVM.hasRemoteVideo - ) - } else if let videoFrame = viewModel.currentVideoFrame, viewModel.hasReceivedFirstFrame { + // Single local feed only (pure SOP capture interface) + if let videoFrame = viewModel.currentVideoFrame, viewModel.hasReceivedFirstFrame { GeometryReader { geometry in Image(uiImage: videoFrame) .resizable() @@ -51,67 +43,67 @@ struct StreamView: View { .foregroundColor(.white) } - // Gemini status overlay (top) + speaking indicator - if geminiVM.isGeminiActive { - VStack { - GeminiStatusBar(geminiVM: geminiVM) - Spacer() + // SOP status + single action control + VStack { + if viewModel.isSopAuditRunning { + Text(String(format: "%.1fs", viewModel.sopAuditSecondsRemaining)) + .font(.system(size: 56, weight: .bold, design: .rounded)) + .foregroundColor(.white) + .padding(.top, 40) + + Text("Uploading at 2 FPS") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white.opacity(0.9)) + } + + Spacer() - VStack(spacing: 8) { - if !geminiVM.userTranscript.isEmpty || !geminiVM.aiTranscript.isEmpty { - TranscriptView( - userText: geminiVM.userTranscript, - aiText: geminiVM.aiTranscript - ) - } + if !viewModel.sopAuditStatusMessage.isEmpty { + Text(viewModel.sopAuditStatusMessage) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.horizontal, 20) + .padding(.vertical, 12) + .background(Color.black.opacity(0.55)) + .cornerRadius(12) + .padding(.bottom, 12) + } - ToolCallStatusView(status: geminiVM.toolCallStatus) + VStack(spacing: 10) { + CustomButton( + title: viewModel.isSopAuditRunning ? "AUDIT RUNNING..." : "INITIATE WALLET AUDIT", + style: .primary, + isDisabled: viewModel.isSopAuditRunning + ) { + viewModel.startSopAudit() + } - if geminiVM.isModelSpeaking { - HStack(spacing: 8) { - Image(systemName: "speaker.wave.2.fill") - .foregroundColor(.white) - .font(.system(size: 14)) - SpeakingIndicator() - } - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background(Color.black.opacity(0.5)) - .cornerRadius(20) + CustomButton( + title: "TOGGLE AI COPILOT", + style: .secondary, + isDisabled: false + ) { + Task { + await viewModel.toggleGeminiAssistant() } } - .padding(.bottom, 80) } - .padding(.all, 24) } - - // WebRTC status overlay (top) - if webrtcVM.isActive { - VStack { - WebRTCStatusBar(webrtcVM: webrtcVM) - Spacer() + .padding(.all, 24) + } + .overlay(alignment: .top) { + GeminiAssistantOverlay(geminiVM: viewModel.geminiAssistant) { + Task { + await viewModel.toggleGeminiAssistant() } - .padding(.all, 24) } - - // Bottom controls layer - VStack { - Spacer() - ControlsView(viewModel: viewModel, geminiVM: geminiVM, webrtcVM: webrtcVM) - } - .padding(.all, 24) } .onDisappear { Task { if viewModel.streamingStatus != .stopped { await viewModel.stopSession() } - if geminiVM.isGeminiActive { - geminiVM.stopSession() - } - if webrtcVM.isActive { - webrtcVM.stopSession() - } } } // Show captured photos from DAT SDK in a preview sheet @@ -125,86 +117,5 @@ struct StreamView: View { ) } } - // Gemini error alert - .alert("AI Assistant", isPresented: Binding( - get: { geminiVM.errorMessage != nil }, - set: { if !$0 { geminiVM.errorMessage = nil } } - )) { - Button("OK") { geminiVM.errorMessage = nil } - } message: { - Text(geminiVM.errorMessage ?? "") - } - // WebRTC error alert - .alert("Live Stream", isPresented: Binding( - get: { webrtcVM.errorMessage != nil }, - set: { if !$0 { webrtcVM.errorMessage = nil } } - )) { - Button("OK") { webrtcVM.errorMessage = nil } - } message: { - Text(webrtcVM.errorMessage ?? "") - } - } -} - -// Extracted controls for clarity -struct ControlsView: View { - @ObservedObject var viewModel: StreamSessionViewModel - @ObservedObject var geminiVM: GeminiSessionViewModel - @ObservedObject var webrtcVM: WebRTCSessionViewModel - - var body: some View { - // Controls row - HStack(spacing: 8) { - CustomButton( - title: "Stop streaming", - style: .destructive, - isDisabled: false - ) { - Task { - await viewModel.stopSession() - } - } - - // Photo button (glasses mode only -- DAT SDK capture) - if viewModel.streamingMode == .glasses { - CircleButton(icon: "camera.fill", text: nil) { - viewModel.capturePhoto() - } - } - - // Gemini AI button (disabled when WebRTC is active — audio conflict) - CircleButton( - icon: geminiVM.isGeminiActive ? "waveform.circle.fill" : "waveform.circle", - text: "AI" - ) { - Task { - if geminiVM.isGeminiActive { - geminiVM.stopSession() - } else { - await geminiVM.startSession() - } - } - } - .opacity(webrtcVM.isActive ? 0.4 : 1.0) - .disabled(webrtcVM.isActive) - - // WebRTC Live Stream button (disabled when Gemini is active — audio conflict) - CircleButton( - icon: webrtcVM.isActive - ? "antenna.radiowaves.left.and.right.circle.fill" - : "antenna.radiowaves.left.and.right.circle", - text: "Live" - ) { - Task { - if webrtcVM.isActive { - webrtcVM.stopSession() - } else { - await webrtcVM.startSession() - } - } - } - .opacity(geminiVM.isGeminiActive ? 0.4 : 1.0) - .disabled(geminiVM.isGeminiActive) - } } } diff --git a/samples/CameraAccess/CameraAccess/WebRTC/CustomVideoCapturer.swift b/samples/CameraAccess/CameraAccess/WebRTC/CustomVideoCapturer.swift index 89db30f8..f17e00e2 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/CustomVideoCapturer.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/CustomVideoCapturer.swift @@ -1,61 +1,273 @@ +import CoreImage +import QuartzCore import UIKit import WebRTC -/// Bridges UIImage frames from DAT SDK / iPhone camera into WebRTC's video pipeline. -/// Creates RTCVideoFrame from UIImage and feeds it to RTCVideoSource via the capturer delegate pattern. -class CustomVideoCapturer: RTCVideoCapturer { - private var frameCount: Int64 = 0 +struct WebRTCSenderStats { + let totalFrames: Int64 + let totalDroppedFrames: Int64 + let windowFramesPerSecond: Double + let windowDroppedFrames: Int64 + let lastEnqueueDurationMs: Double? + let sourceLabel: String + let width: Int + let height: Int +} - /// Push a UIImage frame into the WebRTC video track. - /// Called on each frame from StreamSessionViewModel (24fps glasses, 30fps iPhone). - func pushFrame(_ image: UIImage) { - guard let cgImage = image.cgImage else { return } +enum VideoFrameBufferFactory { + static let pixelFormat = kCVPixelFormatType_32BGRA - let width = cgImage.width - let height = cgImage.height + private static let ciContext = CIContext() - // Create CVPixelBuffer from CGImage - var pixelBuffer: CVPixelBuffer? - let attrs: [String: Any] = [ + static func currentTimestampNs() -> Int64 { + Int64(CACurrentMediaTime() * 1_000_000_000) + } + + static func makeBufferPool( + width: Int, + height: Int, + pixelFormat: OSType = pixelFormat + ) -> CVPixelBufferPool? { + let attributes: [String: Any] = [ + kCVPixelBufferPixelFormatTypeKey as String: pixelFormat, + kCVPixelBufferWidthKey as String: width, + kCVPixelBufferHeightKey as String: height, kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true, kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], ] - let status = CVPixelBufferCreate( - kCFAllocatorDefault, width, height, - kCVPixelFormatType_32BGRA, attrs as CFDictionary, - &pixelBuffer + + var pool: CVPixelBufferPool? + let status = CVPixelBufferPoolCreate( + kCFAllocatorDefault, + nil, + attributes as CFDictionary, + &pool ) - guard status == kCVReturnSuccess, let buffer = pixelBuffer else { return } + guard status == kCVReturnSuccess else { return nil } + return pool + } + + static func makePixelBuffer( + from image: UIImage, + using pool: CVPixelBufferPool? = nil, + pixelFormat: OSType = pixelFormat + ) -> CVPixelBuffer? { + guard let cgImage = image.cgImage else { return nil } + + let width = cgImage.width + let height = cgImage.height + var pixelBuffer: CVPixelBuffer? + let status: CVReturn + + if let pool { + status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer) + } else { + let attrs: [String: Any] = [ + kCVPixelBufferCGImageCompatibilityKey as String: true, + kCVPixelBufferCGBitmapContextCompatibilityKey as String: true, + kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], + ] + status = CVPixelBufferCreate( + kCFAllocatorDefault, + width, + height, + pixelFormat, + attrs as CFDictionary, + &pixelBuffer + ) + } + + guard status == kCVReturnSuccess, let buffer = pixelBuffer else { return nil } CVPixelBufferLockBaseAddress(buffer, []) - if let context = CGContext( - data: CVPixelBufferGetBaseAddress(buffer), - width: width, height: height, - bitsPerComponent: 8, - bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), - space: CGColorSpaceCreateDeviceRGB(), - bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue - | CGBitmapInfo.byteOrder32Little.rawValue - ) { - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + defer { CVPixelBufferUnlockBaseAddress(buffer, []) } + + guard + let context = CGContext( + data: CVPixelBufferGetBaseAddress(buffer), + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue + | CGBitmapInfo.byteOrder32Little.rawValue + ) + else { + return nil + } + + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + return buffer + } + + static func copyPixelBuffer( + _ source: CVPixelBuffer, + using pool: CVPixelBufferPool? = nil, + pixelFormat: OSType = pixelFormat + ) -> CVPixelBuffer? { + let width = CVPixelBufferGetWidth(source) + let height = CVPixelBufferGetHeight(source) + var destination: CVPixelBuffer? + let status: CVReturn + + if let pool { + status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &destination) + } else { + let attrs: [String: Any] = [ + kCVPixelBufferCGImageCompatibilityKey as String: true, + kCVPixelBufferCGBitmapContextCompatibilityKey as String: true, + kCVPixelBufferIOSurfacePropertiesKey as String: [:] as [String: Any], + ] + status = CVPixelBufferCreate( + kCFAllocatorDefault, + width, + height, + pixelFormat, + attrs as CFDictionary, + &destination + ) } - CVPixelBufferUnlockBaseAddress(buffer, []) - // Wrap in WebRTC types and feed to video source - let rtcPixelBuffer = RTCCVPixelBuffer(pixelBuffer: buffer) - let timeStampNs = Int64(CACurrentMediaTime() * 1_000_000_000) + guard status == kCVReturnSuccess, let destination else { return nil } + ciContext.render(CIImage(cvPixelBuffer: source), to: destination) + return destination + } +} + +/// Bridges UIImage or CVPixelBuffer frames into WebRTC's video pipeline. +class CustomVideoCapturer: RTCVideoCapturer { + private var frameCount: Int64 = 0 + private var droppedFrameCount: Int64 = 0 + private var statsWindowStart = CACurrentMediaTime() + private var statsWindowFrames: Int64 = 0 + private var statsWindowDroppedBaseline: Int64 = 0 + private var pixelBufferPool: CVPixelBufferPool? + private var poolSize: CGSize = .zero + var onStatsSample: ((WebRTCSenderStats) -> Void)? + + func pushFrame(_ image: UIImage) { + guard let cgImage = image.cgImage else { + registerDroppedFrame(reason: "missing-cgimage") + return + } + + ensurePixelBufferPool(width: cgImage.width, height: cgImage.height) + let startedAt = CACurrentMediaTime() + guard + let buffer = VideoFrameBufferFactory.makePixelBuffer(from: image, using: pixelBufferPool) + else { + registerDroppedFrame(reason: "pixel-buffer-create") + return + } + + pushPixelBuffer( + buffer, + timeStampNs: VideoFrameBufferFactory.currentTimestampNs(), + enqueueDurationMs: (CACurrentMediaTime() - startedAt) * 1000, + sourceLabel: "image-converted" + ) + } + + func pushPixelBuffer(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64) { + pushPixelBuffer( + pixelBuffer, + timeStampNs: timeStampNs, + enqueueDurationMs: nil, + sourceLabel: "pixel-buffer" + ) + } + + private func pushPixelBuffer( + _ pixelBuffer: CVPixelBuffer, + timeStampNs: Int64, + enqueueDurationMs: Double?, + sourceLabel: String + ) { + let rtcPixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) let rtcFrame = RTCVideoFrame( buffer: rtcPixelBuffer, rotation: ._0, timeStampNs: timeStampNs ) - self.delegate?.capturer(self, didCapture: rtcFrame) + delegate?.capturer(self, didCapture: rtcFrame) frameCount += 1 - if frameCount == 1 || frameCount % 120 == 0 { - NSLog("[WebRTC] Pushed frame #%lld (%dx%d)", frameCount, width, height) + statsWindowFrames += 1 + logSenderStatsIfNeeded( + width: CVPixelBufferGetWidth(pixelBuffer), + height: CVPixelBufferGetHeight(pixelBuffer), + enqueueDurationMs: enqueueDurationMs, + sourceLabel: sourceLabel + ) + } + + private func ensurePixelBufferPool(width: Int, height: Int) { + let requestedSize = CGSize(width: width, height: height) + guard pixelBufferPool == nil || poolSize != requestedSize else { return } + pixelBufferPool = VideoFrameBufferFactory.makeBufferPool(width: width, height: height) + poolSize = requestedSize + } + + private func registerDroppedFrame(reason: String) { + droppedFrameCount += 1 + if droppedFrameCount == 1 || droppedFrameCount % 30 == 0 { + NSLog( + "[WebRTC] Sender dropped frame #%lld (%@)", + droppedFrameCount, + reason + ) + } + } + + private func logSenderStatsIfNeeded( + width: Int, + height: Int, + enqueueDurationMs: Double?, + sourceLabel: String + ) { + guard frameCount == 1 || frameCount % 60 == 0 else { return } + + let now = CACurrentMediaTime() + let elapsed = max(now - statsWindowStart, 0.001) + let fps = Double(statsWindowFrames) / elapsed + let droppedInWindow = droppedFrameCount - statsWindowDroppedBaseline + let enqueueLabel: String + + if let enqueueDurationMs { + enqueueLabel = String(format: "%.1fms", enqueueDurationMs) + } else { + enqueueLabel = "direct" } + + NSLog( + "[WebRTC] Sender stats frames=%lld dropped=%lld rate=%.1ffps last-enqueue=%@ source=%@ size=%dx%d", + frameCount, + droppedFrameCount, + fps, + enqueueLabel, + sourceLabel, + width, + height + ) + + onStatsSample?( + WebRTCSenderStats( + totalFrames: frameCount, + totalDroppedFrames: droppedFrameCount, + windowFramesPerSecond: fps, + windowDroppedFrames: droppedInWindow, + lastEnqueueDurationMs: enqueueDurationMs, + sourceLabel: sourceLabel, + width: width, + height: height + ) + ) + + statsWindowStart = now + statsWindowFrames = 0 + statsWindowDroppedBaseline = droppedFrameCount } } diff --git a/samples/CameraAccess/CameraAccess/WebRTC/SignalingClient.swift b/samples/CameraAccess/CameraAccess/WebRTC/SignalingClient.swift index aec172c6..f3f4d6ba 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/SignalingClient.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/SignalingClient.swift @@ -55,11 +55,11 @@ class SignalingClient { } func joinRoom(code: String) { - sendJSON(["type": "join", "room": code]) + sendJSON(["type": "join", "room": code, "room_code": code]) } func rejoinRoom(code: String) { - sendJSON(["type": "rejoin", "room": code]) + sendJSON(["type": "rejoin", "room": code, "room_code": code]) } func send(sdp: RTCSessionDescription) { @@ -134,7 +134,7 @@ class SignalingClient { switch type { case "room_created": - if let room = json["room"] as? String { + if let room = roomCode(from: json) { onMessageReceived?(.roomCreated(room)) } @@ -142,14 +142,14 @@ class SignalingClient { onMessageReceived?(.roomJoined) case "room_rejoined": - if let room = json["room"] as? String { + if let room = roomCode(from: json) { onMessageReceived?(.roomRejoined(room)) } - case "peer_joined": + case "peer_joined", "viewer_joined": onMessageReceived?(.peerJoined) - case "peer_left": + case "peer_left", "viewer_left": onMessageReceived?(.peerLeft) case "offer": @@ -182,6 +182,16 @@ class SignalingClient { NSLog("[Signaling] Unknown message type: %@", type) } } + + private func roomCode(from json: [String: Any]) -> String? { + if let room = json["room"] as? String, !room.isEmpty { + return room + } + if let roomCode = json["room_code"] as? String, !roomCode.isEmpty { + return roomCode + } + return nil + } } // MARK: - WebSocket Delegate diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift index ad7319c1..5eeaef4c 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCClient.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import WebRTC protocol WebRTCClientDelegate: AnyObject { @@ -6,20 +7,24 @@ protocol WebRTCClientDelegate: AnyObject { func webRTCClient(_ client: WebRTCClient, didGenerateCandidate candidate: RTCIceCandidate) func webRTCClient(_ client: WebRTCClient, didReceiveRemoteVideoTrack track: RTCVideoTrack) func webRTCClient(_ client: WebRTCClient, didRemoveRemoteVideoTrack track: RTCVideoTrack) + func webRTCClient(_ client: WebRTCClient, didUpdateSenderStats stats: WebRTCSenderStats) } /// Manages RTCPeerConnection, video/audio tracks, and SDP negotiation. -/// Video uses a custom capturer (fed by DAT SDK frames). Audio uses WebRTC's native engine. +/// Video uses a custom capturer fed by the worker camera pipeline. class WebRTCClient: NSObject { weak var delegate: WebRTCClientDelegate? private let factory: RTCPeerConnectionFactory + private var streamProfile = WebRTCConfig.supportModeGlassesProfile private var peerConnection: RTCPeerConnection? private var videoSource: RTCVideoSource! private var videoCapturer: CustomVideoCapturer! private var localVideoTrack: RTCVideoTrack? private var localAudioTrack: RTCAudioTrack? + private var localVideoSender: RTCRtpSender? private(set) var remoteVideoTrack: RTCVideoTrack? + private var receiveRemoteVideo = true override init() { RTCInitializeSSL() @@ -32,7 +37,13 @@ class WebRTCClient: NSObject { super.init() } - func setup(iceServers: [RTCIceServer]? = nil) { + func setup( + iceServers: [RTCIceServer]? = nil, + profile: WebRTCStreamProfile = WebRTCConfig.supportModeGlassesProfile, + receiveRemoteVideo: Bool = true + ) { + streamProfile = profile + self.receiveRemoteVideo = receiveRemoteVideo let config = RTCConfiguration() config.iceServers = iceServers ?? [RTCIceServer(urlStrings: WebRTCConfig.stunServers)] config.sdpSemantics = .unifiedPlan @@ -44,42 +55,100 @@ class WebRTCClient: NSObject { ) peerConnection = factory.peerConnection( - with: config, constraints: constraints, delegate: self + with: config, + constraints: constraints, + delegate: self ) createMediaTracks() } private func createMediaTracks() { - // Video track — custom source fed by DAT SDK frames videoSource = factory.videoSource() + videoSource.adaptOutputFormat( + toWidth: Int32(streamProfile.maxWidth), + height: Int32(streamProfile.maxHeight), + fps: Int32(streamProfile.maxFramerate) + ) videoCapturer = CustomVideoCapturer(delegate: videoSource) + videoCapturer.onStatsSample = { [weak self] stats in + guard let self else { return } + self.delegate?.webRTCClient(self, didUpdateSenderStats: stats) + } localVideoTrack = factory.videoTrack(with: videoSource, trackId: "video0") localVideoTrack?.isEnabled = true - peerConnection?.add(localVideoTrack!, streamIds: ["stream0"]) + if let localVideoTrack { + localVideoSender = peerConnection?.add(localVideoTrack, streamIds: ["stream0"]) + applyVideoSenderParameters() + } - // Audio track — WebRTC native audio (handles mic capture, AEC, playback) let audioConstraints = RTCMediaConstraints( - mandatoryConstraints: nil, optionalConstraints: nil + mandatoryConstraints: nil, + optionalConstraints: nil ) let audioSource = factory.audioSource(with: audioConstraints) localAudioTrack = factory.audioTrack(with: audioSource, trackId: "audio0") localAudioTrack?.isEnabled = true - peerConnection?.add(localAudioTrack!, streamIds: ["stream0"]) + if let localAudioTrack { + peerConnection?.add(localAudioTrack, streamIds: ["stream0"]) + } } - /// Called by ViewModel to push video frames from DAT SDK / iPhone camera. func pushVideoFrame(_ image: UIImage) { videoCapturer?.pushFrame(image) } + func pushPixelBuffer(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64) { + videoCapturer?.pushPixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } + + func updateStreamProfile(_ profile: WebRTCStreamProfile) { + streamProfile = profile + videoSource?.adaptOutputFormat( + toWidth: Int32(profile.maxWidth), + height: Int32(profile.maxHeight), + fps: Int32(profile.maxFramerate) + ) + applyVideoSenderParameters() + } + + private func applyVideoSenderParameters() { + guard let localVideoSender else { return } + let parameters = localVideoSender.parameters + let encodings = parameters.encodings + + if encodings.isEmpty { + NSLog("[WebRTC] Sender parameters missing encodings; bitrate tuning skipped") + return + } + + for encoding in encodings { + encoding.maxBitrateBps = NSNumber(value: streamProfile.maxBitrateBps) + encoding.maxFramerate = NSNumber(value: streamProfile.maxFramerate) + } + + parameters.encodings = encodings + parameters.degradationPreference = NSNumber( + value: RTCDegradationPreference.maintainFramerate.rawValue + ) + localVideoSender.parameters = parameters + + NSLog( + "[WebRTC] Sender tuned for support mode (%dx%d @ %dfps, %@ bps)", + streamProfile.maxWidth, + streamProfile.maxHeight, + streamProfile.maxFramerate, + NSNumber(value: streamProfile.maxBitrateBps) + ) + } + // MARK: - SDP Negotiation func createOffer(completion: @escaping (RTCSessionDescription) -> Void) { let constraints = RTCMediaConstraints( mandatoryConstraints: [ "OfferToReceiveAudio": "true", - "OfferToReceiveVideo": "true", + "OfferToReceiveVideo": receiveRemoteVideo ? "true" : "false", ], optionalConstraints: nil ) @@ -90,8 +159,7 @@ class WebRTCClient: NSObject { } self?.peerConnection?.setLocalDescription(sdp) { error in if let error { - NSLog( - "[WebRTC] Failed to set local description: %@", error.localizedDescription) + NSLog("[WebRTC] Failed to set local description: %@", error.localizedDescription) } else { completion(sdp) } @@ -109,11 +177,13 @@ class WebRTCClient: NSObject { func muteAudio(_ mute: Bool) { localAudioTrack?.isEnabled = !mute + NSLog("[WebRTC] Local mic %@", mute ? "muted" : "live") } func close() { localVideoTrack?.isEnabled = false localAudioTrack?.isEnabled = false + localVideoSender = nil remoteVideoTrack = nil peerConnection?.close() peerConnection = nil @@ -129,28 +199,31 @@ class WebRTCClient: NSObject { extension WebRTCClient: RTCPeerConnectionDelegate { func peerConnection( - _ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState + _ peerConnection: RTCPeerConnection, + didChange stateChanged: RTCSignalingState ) { NSLog("[WebRTC] Signaling state: %d", stateChanged.rawValue) } func peerConnection( - _ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState + _ peerConnection: RTCPeerConnection, + didChange newState: RTCIceConnectionState ) { NSLog("[WebRTC] ICE connection state: %d", newState.rawValue) delegate?.webRTCClient(self, didChangeConnectionState: newState) } func peerConnection( - _ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState + _ peerConnection: RTCPeerConnection, + didChange newState: RTCIceGatheringState ) { NSLog("[WebRTC] ICE gathering state: %d", newState.rawValue) } func peerConnection( - _ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate + _ peerConnection: RTCPeerConnection, + didGenerate candidate: RTCIceCandidate ) { - // Log candidate type for debugging NAT traversal let sdp = candidate.sdp if sdp.contains("relay") { NSLog("[WebRTC] ICE candidate: RELAY (TURN)") @@ -163,8 +236,11 @@ extension WebRTCClient: RTCPeerConnectionDelegate { } func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { - NSLog("[WebRTC] Remote stream added with %d audio tracks, %d video tracks", - stream.audioTracks.count, stream.videoTracks.count) + NSLog( + "[WebRTC] Remote stream added with %d audio tracks, %d video tracks", + stream.audioTracks.count, + stream.videoTracks.count + ) if let videoTrack = stream.videoTracks.first { remoteVideoTrack = videoTrack delegate?.webRTCClient(self, didReceiveRemoteVideoTrack: videoTrack) @@ -184,10 +260,12 @@ extension WebRTCClient: RTCPeerConnectionDelegate { } func peerConnection( - _ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate] + _ peerConnection: RTCPeerConnection, + didRemove candidates: [RTCIceCandidate] ) {} func peerConnection( - _ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel + _ peerConnection: RTCPeerConnection, + didOpen dataChannel: RTCDataChannel ) {} } diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift index 3d401c44..3720bcf3 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCConfig.swift @@ -1,27 +1,62 @@ import Foundation import WebRTC +struct WebRTCStreamProfile { + let maxBitrateBps: Int + let maxFramerate: Int + let maxWidth: Int + let maxHeight: Int +} + enum WebRTCConfig { - static let signalingServerURL = Secrets.webrtcSignalingURL + static var signalBaseURL: String { GeminiConfig.signalBaseURL } + + static var signalingServerURL: String { + normalizedWebSocketURL(from: signalBaseURL) + } static let stunServers = [ "stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", ] - static let maxBitrateBps = 2_500_000 // 2.5 Mbps - static let maxFramerate = 24 + static let supportModeGlassesProfile = WebRTCStreamProfile( + maxBitrateBps: 1_200_000, + maxFramerate: 15, + maxWidth: 1280, + maxHeight: 720 + ) + static let supportModePhoneProfile = WebRTCStreamProfile( + maxBitrateBps: 900_000, + maxFramerate: 20, + maxWidth: 960, + maxHeight: 540 + ) + static let supportModePhoneFallbackProfile = WebRTCStreamProfile( + maxBitrateBps: 550_000, + maxFramerate: 15, + maxWidth: 640, + maxHeight: 360 + ) + + static func supportProfile(for mode: StreamingMode) -> WebRTCStreamProfile { + switch mode { + case .iPhone: + return supportModePhoneProfile + case .glasses: + return supportModeGlassesProfile + } + } static var isConfigured: Bool { - return !signalingServerURL.isEmpty - && signalingServerURL != "ws://YOUR_MAC_IP:8080" + let trimmed = signalBaseURL.trimmingCharacters(in: .whitespacesAndNewlines) + return !trimmed.isEmpty + && !trimmed.contains("YOUR_") } /// Derive the HTTP base URL from the WebSocket signaling URL. static var httpBaseURL: String { - return signalingServerURL - .replacingOccurrences(of: "wss://", with: "https://") - .replacingOccurrences(of: "ws://", with: "http://") + normalizedHTTPURL(from: signalBaseURL) } /// Fetch TURN credentials from the signaling server. @@ -64,4 +99,34 @@ enum WebRTCConfig { return servers } + + private static func normalizedWebSocketURL(from raw: String) -> String { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return trimmed } + if trimmed.hasPrefix("wss://") || trimmed.hasPrefix("ws://") { + return trimmed + } + if trimmed.hasPrefix("https://") { + return "wss://" + String(trimmed.dropFirst("https://".count)) + } + if trimmed.hasPrefix("http://") { + return "ws://" + String(trimmed.dropFirst("http://".count)) + } + return "wss://\(trimmed)" + } + + private static func normalizedHTTPURL(from raw: String) -> String { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return trimmed } + if trimmed.hasPrefix("https://") || trimmed.hasPrefix("http://") { + return trimmed + } + if trimmed.hasPrefix("wss://") { + return "https://" + String(trimmed.dropFirst("wss://".count)) + } + if trimmed.hasPrefix("ws://") { + return "http://" + String(trimmed.dropFirst("ws://".count)) + } + return "https://\(trimmed)" + } } diff --git a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift index a2463ce8..91f19c47 100644 --- a/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift +++ b/samples/CameraAccess/CameraAccess/WebRTC/WebRTCSessionViewModel.swift @@ -1,7 +1,58 @@ import Foundation +import QuartzCore import SwiftUI import WebRTC +final class WebRTCRealtimeVideoForwarder: @unchecked Sendable { + private let queue = DispatchQueue( + label: "visionclaw.webrtc.realtime-forwarder", + qos: .userInteractive + ) + private var imageHandler: ((UIImage) -> Void)? + private var pixelBufferHandler: ((CVPixelBuffer, Int64) -> Void)? + private var pixelBufferForwardCount: Int64 = 0 + private var pixelBufferStatsWindowStart = CACurrentMediaTime() + + func updateHandlers( + imageHandler: ((UIImage) -> Void)?, + pixelBufferHandler: ((CVPixelBuffer, Int64) -> Void)? + ) { + queue.async { + self.imageHandler = imageHandler + self.pixelBufferHandler = pixelBufferHandler + } + } + + func enqueueImage(_ image: UIImage) { + queue.async { + self.imageHandler?(image) + } + } + + func enqueuePixelBuffer(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64) { + let queuedAt = CACurrentMediaTime() + queue.async { + self.pixelBufferHandler?(pixelBuffer, timeStampNs) + self.pixelBufferForwardCount += 1 + self.logPixelBufferForwardStatsIfNeeded(waitDurationMs: (CACurrentMediaTime() - queuedAt) * 1000) + } + } + + private func logPixelBufferForwardStatsIfNeeded(waitDurationMs: Double) { + guard pixelBufferForwardCount == 1 || pixelBufferForwardCount % 120 == 0 else { return } + let now = CACurrentMediaTime() + let elapsed = max(now - pixelBufferStatsWindowStart, 0.001) + let fps = Double(pixelBufferForwardCount) / elapsed + NSLog( + "[WebRTC] Realtime forwarder rate=%.1ffps last-wait=%.2fms", + fps, + waitDurationMs + ) + pixelBufferStatsWindowStart = now + pixelBufferForwardCount = 0 + } +} + enum WebRTCConnectionState: Equatable { case disconnected case connecting @@ -22,16 +73,23 @@ class WebRTCSessionViewModel: ObservableObject { @Published var errorMessage: String? @Published var remoteVideoTrack: RTCVideoTrack? @Published var hasRemoteVideo: Bool = false + @Published var incomingRemoteVideoEnabled: Bool = true + + nonisolated let realtimeVideoForwarder = WebRTCRealtimeVideoForwarder() private var webRTCClient: WebRTCClient? private var signalingClient: SignalingClient? private var delegateAdapter: WebRTCDelegateAdapter? + private var currentCaptureMode: StreamingMode = .glasses + private var wantsIncomingRemoteVideo = true + private var isUsingPhoneFallbackProfile = false + private var stablePhoneSenderWindows = 0 /// Saved room code for reconnecting after app backgrounding. private var savedRoomCode: String? private var foregroundObserver: Any? - func startSession() async { + func startSession(captureMode: StreamingMode = .glasses) async { guard !isActive else { return } guard WebRTCConfig.isConfigured else { errorMessage = "WebRTC signaling URL not configured." @@ -41,17 +99,23 @@ class WebRTCSessionViewModel: ObservableObject { isActive = true connectionState = .connecting savedRoomCode = nil + currentCaptureMode = captureMode + wantsIncomingRemoteVideo = captureMode != .iPhone + incomingRemoteVideoEnabled = wantsIncomingRemoteVideo + isUsingPhoneFallbackProfile = false + stablePhoneSenderWindows = 0 // Fetch TURN credentials for NAT traversal across networks let iceServers = await WebRTCConfig.fetchIceServers() - setupWebRTCClient(iceServers: iceServers) + setupWebRTCClient(iceServers: iceServers, captureMode: captureMode) connectSignaling(rejoinCode: nil) observeForeground() } func stopSession() { removeForegroundObserver() + realtimeVideoForwarder.updateHandlers(imageHandler: nil, pixelBufferHandler: nil) webRTCClient?.close() webRTCClient = nil delegateAdapter = nil @@ -64,6 +128,10 @@ class WebRTCSessionViewModel: ObservableObject { isMuted = false remoteVideoTrack = nil hasRemoteVideo = false + incomingRemoteVideoEnabled = true + wantsIncomingRemoteVideo = true + isUsingPhoneFallbackProfile = false + stablePhoneSenderWindows = 0 } func toggleMute() { @@ -77,15 +145,44 @@ class WebRTCSessionViewModel: ObservableObject { webRTCClient?.pushVideoFrame(image) } + func pushVideoPixelBuffer(_ pixelBuffer: CVPixelBuffer, timeStampNs: Int64) { + guard isActive, connectionState == .connected else { return } + webRTCClient?.pushPixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } + // MARK: - WebRTC + Signaling Setup - private func setupWebRTCClient(iceServers: [RTCIceServer]?) { + private func setupWebRTCClient( + iceServers: [RTCIceServer]?, + captureMode: StreamingMode + ) { let client = WebRTCClient() let adapter = WebRTCDelegateAdapter(viewModel: self) delegateAdapter = adapter client.delegate = adapter - client.setup(iceServers: iceServers) + let profile: WebRTCStreamProfile + switch captureMode { + case .iPhone: + profile = isUsingPhoneFallbackProfile + ? WebRTCConfig.supportModePhoneFallbackProfile + : WebRTCConfig.supportModePhoneProfile + case .glasses: + profile = WebRTCConfig.supportModeGlassesProfile + } + client.setup( + iceServers: iceServers, + profile: profile, + receiveRemoteVideo: wantsIncomingRemoteVideo + ) webRTCClient = client + realtimeVideoForwarder.updateHandlers( + imageHandler: { [weak client] image in + client?.pushVideoFrame(image) + }, + pixelBufferHandler: { [weak client] pixelBuffer, timeStampNs in + client?.pushPixelBuffer(pixelBuffer, timeStampNs: timeStampNs) + } + ) } private func connectSignaling(rejoinCode: String?) { @@ -159,6 +256,28 @@ class WebRTCSessionViewModel: ObservableObject { private func handleReturnToForeground() { guard isActive, let code = savedRoomCode else { return } NSLog("[WebRTC] App returned to foreground, reconnecting to room: %@", code) + reconnectCurrentRoom(reason: "app_foreground", roomCode: code) + } + + func setIncomingRemoteVideoEnabled(_ enabled: Bool) { + guard currentCaptureMode == .iPhone else { + incomingRemoteVideoEnabled = true + return + } + guard enabled != wantsIncomingRemoteVideo else { return } + wantsIncomingRemoteVideo = enabled + incomingRemoteVideoEnabled = enabled + remoteVideoTrack = nil + hasRemoteVideo = false + guard let code = savedRoomCode, isActive else { return } + NSLog( + "[WebRTC] Reconfiguring incoming supervisor video: %@", + enabled ? "enabled" : "disabled" + ) + reconnectCurrentRoom(reason: enabled ? "enable_remote_video" : "disable_remote_video", roomCode: code) + } + + private func reconnectCurrentRoom(reason: String, roomCode code: String) { connectionState = .connecting // Tear down old peer connection, set up fresh one @@ -168,9 +287,10 @@ class WebRTCSessionViewModel: ObservableObject { Task { let iceServers = await WebRTCConfig.fetchIceServers() - setupWebRTCClient(iceServers: iceServers) + setupWebRTCClient(iceServers: iceServers, captureMode: currentCaptureMode) connectSignaling(rejoinCode: code) } + NSLog("[WebRTC] Reconnecting room %@ (%@)", code, reason) } // MARK: - Signaling Message Handling @@ -261,6 +381,39 @@ class WebRTCSessionViewModel: ObservableObject { hasRemoteVideo = false NSLog("[WebRTC] Remote video track removed") } + + fileprivate func handleSenderStats(_ stats: WebRTCSenderStats) { + guard currentCaptureMode == .iPhone else { return } + + let enqueueMs = stats.lastEnqueueDurationMs ?? 0 + let isUnderPressure = enqueueMs > 20 + || stats.windowDroppedFrames >= 3 + || stats.windowFramesPerSecond < 14 + + if isUnderPressure { + stablePhoneSenderWindows = 0 + guard !isUsingPhoneFallbackProfile else { return } + isUsingPhoneFallbackProfile = true + webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneFallbackProfile) + NSLog( + "[WebRTC] Phone sender downgraded to fallback profile (fps=%.1f dropped=%lld enqueue=%@ms)", + stats.windowFramesPerSecond, + stats.windowDroppedFrames, + stats.lastEnqueueDurationMs.map { String(format: "%.1f", $0) } ?? "direct" + ) + return + } + + guard isUsingPhoneFallbackProfile else { return } + stablePhoneSenderWindows += 1 + + if stablePhoneSenderWindows >= 3 { + stablePhoneSenderWindows = 0 + isUsingPhoneFallbackProfile = false + webRTCClient?.updateStreamProfile(WebRTCConfig.supportModePhoneProfile) + NSLog("[WebRTC] Phone sender restored to default support profile") + } + } } // MARK: - Delegate Adapter (bridges nonisolated delegate to @MainActor ViewModel) @@ -295,4 +448,10 @@ private class WebRTCDelegateAdapter: WebRTCClientDelegate { self?.viewModel?.handleRemoteVideoTrackRemoved(track) } } + + func webRTCClient(_ client: WebRTCClient, didUpdateSenderStats stats: WebRTCSenderStats) { + Task { @MainActor [weak self] in + self?.viewModel?.handleSenderStats(stats) + } + } } diff --git a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift index 5587de8f..be7278f4 100644 --- a/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift +++ b/samples/CameraAccess/CameraAccess/iPhone/IPhoneCameraManager.swift @@ -1,35 +1,127 @@ import AVFoundation import UIKit -class IPhoneCameraManager: NSObject { +class IPhoneCameraManager: NSObject, @unchecked Sendable { private let captureSession = AVCaptureSession() private let videoOutput = AVCaptureVideoDataOutput() + private let movieOutput = AVCaptureMovieFileOutput() private let sessionQueue = DispatchQueue(label: "iphone-camera-session") private let context = CIContext() private var isRunning = false + private var isConfigured = false + private var recordingCompletion: ((URL?) -> Void)? + private var currentRecordingURL: URL? + private var lastAnalysisEmissionAt: CFTimeInterval = 0 + private var sampleFrameCount: Int64 = 0 + private var analysisFrameCount: Int64 = 0 + private var statsWindowStart = CACurrentMediaTime() + private var hasDeliveredFirstPreviewFrame = false var onFrameCaptured: ((UIImage) -> Void)? + var onSampleBufferCaptured: ((CMSampleBuffer) -> Void)? + var onFirstPreviewFrame: (() -> Void)? + var analysisFrameInterval: CFTimeInterval = 0.2 + var analysisMaxDimension: CGFloat = 720 + var previewSession: AVCaptureSession { captureSession } func start() { guard !isRunning else { return } sessionQueue.async { [weak self] in + NSLog("[iPhoneCamera] start() requested") + self?.sampleFrameCount = 0 + self?.analysisFrameCount = 0 + self?.statsWindowStart = CACurrentMediaTime() + self?.lastAnalysisEmissionAt = 0 + self?.hasDeliveredFirstPreviewFrame = false self?.configureSession() self?.captureSession.startRunning() self?.isRunning = true + NSLog("[iPhoneCamera] captureSession.startRunning() complete (isRunning=%@, sessionRunning=%@)", + self?.isRunning == true ? "true" : "false", + self?.captureSession.isRunning == true ? "true" : "false") + } + } + + func startRecording(sessionID: String) { + sessionQueue.async { [weak self] in + guard let self else { return } + self.configureSession() + + NSLog( + "[iPhoneCamera] startRecording requested (sessionConfigured=%@, sessionRunning=%@, alreadyRecording=%@)", + self.isConfigured ? "true" : "false", + self.captureSession.isRunning ? "true" : "false", + self.movieOutput.isRecording ? "true" : "false") + + if let attributes = try? FileManager.default.attributesOfFileSystem(forPath: FileManager.default.temporaryDirectory.path), + let freeSize = attributes[.systemFreeSize] as? NSNumber { + NSLog("[iPhoneCamera] Free disk space before recording: %@ bytes", freeSize) + } + + guard self.captureSession.isRunning else { + NSLog("[iPhoneCamera] Cannot start recording because capture session is not running") + return + } + guard !self.movieOutput.isRecording else { + NSLog("[iPhoneCamera] Ignoring startRecording because movie output is already recording") + return + } + + let fileURL = FileManager.default.temporaryDirectory + .appendingPathComponent("sop_\(sessionID)") + .appendingPathExtension("mp4") + try? FileManager.default.removeItem(at: fileURL) + self.currentRecordingURL = fileURL + + self.movieOutput.startRecording(to: fileURL, recordingDelegate: self) + NSLog("[iPhoneCamera] Started recording SOP video: %@", fileURL.path) + } + } + + func stopRecording() async -> URL? { + await withCheckedContinuation { continuation in + sessionQueue.async { [weak self] in + guard let self else { + continuation.resume(returning: nil) + return + } + + NSLog( + "[iPhoneCamera] stopRecording requested (isRecording=%@, currentRecordingURL=%@)", + self.movieOutput.isRecording ? "true" : "false", + self.currentRecordingURL?.path ?? "nil") + + guard self.movieOutput.isRecording else { + NSLog("[iPhoneCamera] stopRecording returning currentRecordingURL immediately because movieOutput.isRecording=false") + continuation.resume(returning: self.currentRecordingURL) + return + } + + self.recordingCompletion = { url in + NSLog("[iPhoneCamera] stopRecording completion fired with URL=%@", url?.path ?? "nil") + continuation.resume(returning: url) + } + self.movieOutput.stopRecording() + NSLog("[iPhoneCamera] movieOutput.stopRecording() called") + } } } func stop() { guard isRunning else { return } sessionQueue.async { [weak self] in + NSLog("[iPhoneCamera] stop() requested") self?.captureSession.stopRunning() self?.isRunning = false + NSLog("[iPhoneCamera] captureSession.stopRunning() complete") } } private func configureSession() { + guard !isConfigured else { return } + captureSession.beginConfiguration() - captureSession.sessionPreset = .medium + captureSession.sessionPreset = .iFrame960x540 // Add back camera input guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), @@ -43,6 +135,14 @@ class IPhoneCameraManager: NSObject { captureSession.addInput(input) } + if let microphone = AVCaptureDevice.default(for: .audio), + let audioInput = try? AVCaptureDeviceInput(device: microphone), + captureSession.canAddInput(audioInput) { + captureSession.addInput(audioInput) + } else { + NSLog("[iPhoneCamera] Microphone input unavailable for recording") + } + // Add video output videoOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA @@ -54,6 +154,11 @@ class IPhoneCameraManager: NSObject { captureSession.addOutput(videoOutput) } + // Add movie output for full-session recording. + if captureSession.canAddOutput(movieOutput) { + captureSession.addOutput(movieOutput) + } + // Force portrait-oriented frames from the sensor if let connection = videoOutput.connection(with: .video) { if connection.isVideoRotationAngleSupported(90) { @@ -61,8 +166,15 @@ class IPhoneCameraManager: NSObject { } } + if let movieConnection = movieOutput.connection(with: .video) { + if movieConnection.isVideoRotationAngleSupported(90) { + movieConnection.videoRotationAngle = 90 + } + } + captureSession.commitConfiguration() - NSLog("[iPhoneCamera] Session configured") + isConfigured = true + NSLog("[iPhoneCamera] Session configured successfully") } static func requestPermission() async -> Bool { @@ -86,12 +198,103 @@ extension IPhoneCameraManager: AVCaptureVideoDataOutputSampleBufferDelegate { didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection ) { - guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + sampleFrameCount += 1 + onSampleBufferCaptured?(sampleBuffer) + + if !hasDeliveredFirstPreviewFrame { + hasDeliveredFirstPreviewFrame = true + let onFirstPreviewFrame = onFirstPreviewFrame + DispatchQueue.main.async { + onFirstPreviewFrame?() + } + } + + logCaptureStatsIfNeeded() + + guard onFrameCaptured != nil else { return } - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return } - let image = UIImage(cgImage: cgImage) + let now = CACurrentMediaTime() + guard now - lastAnalysisEmissionAt >= analysisFrameInterval else { return } + lastAnalysisEmissionAt = now + guard let image = makeUIImage(from: sampleBuffer, maxDimension: analysisMaxDimension) else { return } + analysisFrameCount += 1 onFrameCaptured?(image) } } + +extension IPhoneCameraManager: AVCaptureFileOutputRecordingDelegate { + func fileOutput( + _ output: AVCaptureFileOutput, + didStartRecordingTo fileURL: URL, + from connections: [AVCaptureConnection] + ) { + NSLog("[iPhoneCamera] didStartRecordingTo fired for %@", fileURL.path) + } + + func fileOutput( + _ output: AVCaptureFileOutput, + didFinishRecordingTo outputFileURL: URL, + from connections: [AVCaptureConnection], + error: Error? + ) { + if let error { + NSLog("[iPhoneCamera] Recording failed: %@", error.localizedDescription) + } else { + NSLog("[iPhoneCamera] Recording finished: %@", outputFileURL.path) + } + + if let attributes = try? FileManager.default.attributesOfItem(atPath: outputFileURL.path), + let fileSize = attributes[.size] as? NSNumber { + NSLog("[iPhoneCamera] Recorded file size: %@ bytes", fileSize) + } else { + NSLog("[iPhoneCamera] Could not read recorded file size at %@", outputFileURL.path) + } + + let completion = recordingCompletion + recordingCompletion = nil + currentRecordingURL = outputFileURL + completion?(error == nil ? outputFileURL : nil) + } +} + +extension IPhoneCameraManager { + fileprivate func makeUIImage(from sampleBuffer: CMSampleBuffer, maxDimension: CGFloat? = nil) -> UIImage? { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } + + var ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let extent = ciImage.extent + + if let maxDimension, + maxDimension > 0 { + let largestDimension = max(extent.width, extent.height) + if largestDimension > maxDimension { + let scale = maxDimension / largestDimension + ciImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) + } + } + + guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil } + return UIImage(cgImage: cgImage) + } + + private func logCaptureStatsIfNeeded() { + guard sampleFrameCount == 1 || sampleFrameCount % 120 == 0 else { return } + + let now = CACurrentMediaTime() + let elapsed = max(now - statsWindowStart, 0.001) + let previewFPS = Double(sampleFrameCount) / elapsed + let analysisFPS = Double(analysisFrameCount) / elapsed + + NSLog( + "[iPhoneCamera] Preview stats preview=%.1ffps analysis=%.1ffps recording=%@", + previewFPS, + analysisFPS, + movieOutput.isRecording ? "true" : "false" + ) + + statsWindowStart = now + sampleFrameCount = 0 + analysisFrameCount = 0 + } +} diff --git a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift index 5e9a28d5..692bc3b5 100644 --- a/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift +++ b/samples/CameraAccess/CameraAccessTests/CameraAccessTests.swift @@ -8,12 +8,14 @@ import Foundation import MWDATCore -import MWDATMockDevice import SwiftUI import XCTest @testable import CameraAccess +#if canImport(MWDATMockDevice) +import MWDATMockDevice + @MainActor class ViewModelIntegrationTests: XCTestCase { @@ -156,3 +158,673 @@ class ViewModelIntegrationTests: XCTestCase { XCTAssertTrue([.stopped, .waiting].contains(viewModel.streamingStatus)) } } +#endif + +private final class RequestCaptureURLProtocol: URLProtocol { + static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))? + + override class func canInit(with request: URLRequest) -> Bool { + true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + request + } + + override func startLoading() { + guard let handler = Self.handler else { + client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse)) + return + } + + do { + let (response, data) = try handler(request) + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: data) + client?.urlProtocolDidFinishLoading(self) + } catch { + client?.urlProtocol(self, didFailWithError: error) + } + } + + override func stopLoading() {} +} + +private struct WorkerUploadTargetRequestCapture: Equatable { + let sessionID: String + let assetType: String + let filename: String + let contentType: String + let byteSize: Int + let source: String? +} + +private struct WorkerAdminAPISnapshot { + let heartbeats: [WorkerLiveHeartbeatRequest] + let uploadTargetRequests: [WorkerUploadTargetRequestCapture] + let uploadCalls: [(assetID: String, byteSize: Int, contentType: String)] + let finalizeRequests: [WorkerMediaFinalizeRequest] +} + +private func requestBodyData(from request: URLRequest) -> Data? { + if let body = request.httpBody { + return body + } + + guard let stream = request.httpBodyStream else { return nil } + stream.open() + defer { stream.close() } + + let bufferSize = 1024 + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { buffer.deallocate() } + + var data = Data() + while stream.hasBytesAvailable { + let bytesRead = stream.read(buffer, maxLength: bufferSize) + if bytesRead < 0 { + return nil + } + if bytesRead == 0 { + break + } + data.append(buffer, count: bytesRead) + } + + return data +} + +private final class WorkerAdminAPIMock: WorkerAdminAPI, @unchecked Sendable { + private let lock = NSLock() + + var heartbeatErrors: [Error] = [] + var uploadTargetErrors: [Error] = [] + var uploadErrors: [Error] = [] + var finalizeErrors: [Error] = [] + var uploadTargetResponses: [WorkerMediaUploadTarget] = [] + var onFinalizeAttempt: ((WorkerMediaFinalizeRequest) -> Void)? + + private var recordedHeartbeats: [WorkerLiveHeartbeatRequest] = [] + private var recordedUploadTargetRequests: [WorkerUploadTargetRequestCapture] = [] + private var recordedUploadCalls: [(assetID: String, byteSize: Int, contentType: String)] = [] + private var recordedFinalizeRequests: [WorkerMediaFinalizeRequest] = [] + + func sendWorkerLiveHeartbeat(_ heartbeat: WorkerLiveHeartbeatRequest) async throws { + let queuedError = lock.withLock { () -> Error? in + recordedHeartbeats.append(heartbeat) + return heartbeatErrors.isEmpty ? nil : heartbeatErrors.removeFirst() + } + + if let queuedError { + throw queuedError + } + } + + func requestWorkerMediaUploadTarget( + sessionID: String, + assetType: String, + filename: String, + contentType: String, + byteSize: Int, + source: String? + ) async throws -> WorkerMediaUploadTarget { + let (queuedError, response) = lock.withLock { () -> (Error?, WorkerMediaUploadTarget) in + recordedUploadTargetRequests.append( + WorkerUploadTargetRequestCapture( + sessionID: sessionID, + assetType: assetType, + filename: filename, + contentType: contentType, + byteSize: byteSize, + source: source + ) + ) + let queuedError = uploadTargetErrors.isEmpty ? nil : uploadTargetErrors.removeFirst() + let response: WorkerMediaUploadTarget + if uploadTargetResponses.isEmpty { + let index = recordedUploadTargetRequests.count + response = WorkerMediaUploadTarget( + assetID: "\(assetType)-asset-\(index)", + bucket: "\(assetType)-bucket", + path: "sessions/\(sessionID)/\(filename)", + uploadURL: "https://upload.example/\(assetType)-\(index)" + ) + } else { + response = uploadTargetResponses.removeFirst() + } + return (queuedError, response) + } + + if let queuedError { + throw queuedError + } + return response + } + + func finalizeWorkerMediaUpload(_ finalize: WorkerMediaFinalizeRequest) async throws { + let (queuedError, finalizeHandler) = lock.withLock { () -> (Error?, ((WorkerMediaFinalizeRequest) -> Void)?) in + recordedFinalizeRequests.append(finalize) + let queuedError = finalizeErrors.isEmpty ? nil : finalizeErrors.removeFirst() + return (queuedError, onFinalizeAttempt) + } + + finalizeHandler?(finalize) + + if let queuedError { + throw queuedError + } + } + + func uploadBinary( + to target: WorkerMediaUploadTarget, + data: Data, + contentType: String + ) async throws { + let queuedError = lock.withLock { () -> Error? in + recordedUploadCalls.append((assetID: target.assetID, byteSize: data.count, contentType: contentType)) + return uploadErrors.isEmpty ? nil : uploadErrors.removeFirst() + } + + if let queuedError { + throw queuedError + } + } + + func snapshot() -> WorkerAdminAPISnapshot { + lock.lock() + defer { lock.unlock() } + return WorkerAdminAPISnapshot( + heartbeats: recordedHeartbeats, + uploadTargetRequests: recordedUploadTargetRequests, + uploadCalls: recordedUploadCalls, + finalizeRequests: recordedFinalizeRequests + ) + } +} + +private actor SleepRecorder { + private(set) var values: [UInt64] = [] + + func record(_ value: UInt64) { + values.append(value) + } + + func snapshot() -> [UInt64] { + values + } +} + +private final class CallOrderRecorder: @unchecked Sendable { + private let lock = NSLock() + private var storedValues: [String] = [] + + func append(_ value: String) { + lock.lock() + storedValues.append(value) + lock.unlock() + } + + var values: [String] { + lock.lock() + defer { lock.unlock() } + return storedValues + } +} + +final class WorkerAdminLiveSessionCoordinatorTests: XCTestCase { + private func makeTempFile(data: Data, suffix: String = UUID().uuidString) throws -> URL { + let url = FileManager.default.temporaryDirectory + .appendingPathComponent("worker-admin-\(suffix)") + .appendingPathExtension("mp4") + try? FileManager.default.removeItem(at: url) + try data.write(to: url) + return url + } + + func testHeartbeatKeepsStickyRoomCodeAndLastFrameLocation() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "frame-asset-1", + bucket: "live-frames", + path: "sessions/session-1/last-frame.jpg", + uploadURL: "https://upload.example/frame-1" + ) + ] + + let frameFinalized = expectation(description: "frame finalized") + api.onFinalizeAttempt = { finalize in + if finalize.assetID == "frame-asset-1", finalize.status == "uploaded" { + frameFinalized.fulfill() + } + } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + await coordinator.start(sessionID: "session-1", currentStepIndex: 0, helpRequested: false) + await coordinator.updateRoomCode("ROOM42") + await coordinator.enqueueFrameUpload(data: Data([0x01, 0x02, 0x03])) + + await fulfillment(of: [frameFinalized], timeout: 1.0) + + await coordinator.updateRoomCode("") + await coordinator.updateHelpRequested(true) + + let snapshot = api.snapshot() + XCTAssertEqual(snapshot.heartbeats.first?.webrtcRoomCode, nil) + XCTAssertEqual(snapshot.heartbeats.last?.webrtcRoomCode, "ROOM42") + XCTAssertEqual(snapshot.heartbeats.last?.lastFrameBucket, "live-frames") + XCTAssertEqual(snapshot.heartbeats.last?.lastFramePath, "sessions/session-1/last-frame.jpg") + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "uploaded") + } + + func testFrameUploadFinalizesFailedWhenUploadFailsAfterRetries() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "frame-asset-2", + bucket: "live-frames", + path: "sessions/session-2/last-frame.jpg", + uploadURL: "https://upload.example/frame-2" + ) + ] + api.uploadErrors = [ + URLError(.networkConnectionLost), + URLError(.networkConnectionLost), + URLError(.networkConnectionLost), + URLError(.networkConnectionLost), + ] + + let sleepRecorder = SleepRecorder() + let frameFailed = expectation(description: "frame failed finalize") + api.onFinalizeAttempt = { finalize in + if finalize.assetID == "frame-asset-2", finalize.status == "failed" { + frameFailed.fulfill() + } + } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + heartbeatIntervalNanoseconds: 0, + sleeper: { value in + await sleepRecorder.record(value) + } + ) + + await coordinator.start(sessionID: "session-2", currentStepIndex: 0, helpRequested: false) + await coordinator.enqueueFrameUpload(data: Data([0x0A, 0x0B])) + + await fulfillment(of: [frameFailed], timeout: 1.0) + + let snapshot = api.snapshot() + let recordedSleeps = await sleepRecorder.snapshot() + XCTAssertEqual(snapshot.uploadCalls.count, 4) + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "failed") + XCTAssertEqual(recordedSleeps, [750_000_000, 1_500_000_000, 3_000_000_000]) + } + + func testVideoUploadFinalizesFailedWhenRecordingIsMissing() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-1", + bucket: "execution-videos", + path: "sessions/session-3/recording.mp4", + uploadURL: "https://upload.example/video-1" + ) + ] + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-3", + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + let result = await coordinator.uploadVideoRecording(from: nil) + let snapshot = api.snapshot() + + XCTAssertEqual(result.uploadState, "failed") + XCTAssertEqual(snapshot.uploadTargetRequests.last?.byteSize, 0) + XCTAssertEqual(snapshot.uploadTargetRequests.last?.source, "session-recording") + XCTAssertEqual(snapshot.finalizeRequests.last?.status, "failed") + XCTAssertTrue(snapshot.uploadCalls.isEmpty) + } + + func testCompleteSessionUploadsVideoBeforeEndCallback() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-2", + bucket: "execution-videos", + path: "sessions/session-4/recording.mp4", + uploadURL: "https://upload.example/video-2" + ) + ] + + let fileURL = try makeTempFile(data: Data([0x01, 0x02, 0x03]), suffix: "ordering") + defer { try? FileManager.default.removeItem(at: fileURL) } + + let callOrder = CallOrderRecorder() + api.onFinalizeAttempt = { finalize in + if finalize.assetID == "video-asset-2", finalize.status == "uploaded" { + callOrder.append("finalize") + } + } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + await coordinator.start(sessionID: "session-4", currentStepIndex: 0, helpRequested: false) + let result = await coordinator.completeSession(videoFileURL: fileURL) { + callOrder.append("end") + } + + XCTAssertTrue(result.succeeded) + XCTAssertEqual(callOrder.values, ["finalize", "end"]) + } + + func testVideoFinalizeRetriesTransientErrorsThenSucceeds() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-3", + bucket: "execution-videos", + path: "sessions/session-5/recording.mp4", + uploadURL: "https://upload.example/video-3" + ) + ] + api.finalizeErrors = [ + URLError(.timedOut), + URLError(.networkConnectionLost), + ] + + let sleepRecorder = SleepRecorder() + let fileURL = try makeTempFile(data: Data([0xAB, 0xCD, 0xEF]), suffix: "retry") + defer { try? FileManager.default.removeItem(at: fileURL) } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-5", + heartbeatIntervalNanoseconds: 0, + sleeper: { value in + await sleepRecorder.record(value) + } + ) + + let result = await coordinator.uploadVideoRecording(from: fileURL) + let snapshot = api.snapshot() + let recordedSleeps = await sleepRecorder.snapshot() + + XCTAssertTrue(result.succeeded) + XCTAssertEqual(snapshot.uploadTargetRequests.last?.source, "session-recording") + XCTAssertEqual( + snapshot.finalizeRequests.filter { $0.assetID == "video-asset-3" && $0.status == "uploaded" }.count, + 3 + ) + XCTAssertEqual(recordedSleeps, [750_000_000, 1_500_000_000]) + } + + func testVideoUploadIncludesExplicitRecordingSource() async throws { + let api = WorkerAdminAPIMock() + api.uploadTargetResponses = [ + WorkerMediaUploadTarget( + assetID: "video-asset-4", + bucket: "execution-videos", + path: "sessions/session-6/recording.mp4", + uploadURL: "https://upload.example/video-4" + ) + ] + + let fileURL = try makeTempFile(data: Data([0x11, 0x22, 0x33]), suffix: "phone-source") + defer { try? FileManager.default.removeItem(at: fileURL) } + + let coordinator = WorkerAdminLiveSessionCoordinator( + api: api, + sessionID: "session-6", + heartbeatIntervalNanoseconds: 0, + sleeper: { _ in } + ) + + let result = await coordinator.uploadVideoRecording(from: fileURL, source: "phone-recording") + let snapshot = api.snapshot() + + XCTAssertTrue(result.succeeded) + XCTAssertEqual(snapshot.uploadTargetRequests.last?.source, "phone-recording") + } +} + +@MainActor +final class GeminiInstructionSyncTests: XCTestCase { + override func setUp() async throws { + try await super.setUp() + try? Wearables.configure() + } + + func testGeminiInstructionPreviewIncludesActiveStepContext() async throws { + let viewModel = StreamSessionViewModel(wearables: Wearables.shared) + let sop = SOPTemplate( + name: "Cold Chain Verification SOP", + steps: [ + SOPStepTemplate( + id: "inspect_packaging_seal", + order: 1, + title: "Inspect packaging seal", + description: "Check the package seal before accepting the delivery.", + aiPrompt: "Confirm the seal is intact before intake.", + expectedObjects: ["seal", "package"] + ), + SOPStepTemplate( + id: "record_temperature_log", + order: 2, + title: "Record temperature log", + description: "Read the thermometer and capture the result in the log.", + aiPrompt: "Verify that the worker recorded the temperature reading.", + expectedObjects: ["thermometer", "clipboard"] + ), + ] + ) + + viewModel.checklistItems = [ + ChecklistItemState( + itemID: "inspect_packaging_seal", + name: "Inspect packaging seal", + description: "Check the package seal before accepting the delivery.", + aiPrompt: "Confirm the seal is intact before intake.", + expectedObjects: ["seal", "package"], + isChecked: true, + completionSource: .manual + ), + ChecklistItemState( + itemID: "record_temperature_log", + name: "Record temperature log", + description: "Read the thermometer and capture the result in the log.", + aiPrompt: "Verify that the worker recorded the temperature reading.", + expectedObjects: ["thermometer", "clipboard"] + ), + ] + + let instruction = try XCTUnwrap(viewModel.debugGeminiInstructionPreview(for: sop)) + + XCTAssertTrue(instruction.contains("SOP title: Cold Chain Verification SOP")) + XCTAssertTrue(instruction.contains("Step title: Record temperature log")) + XCTAssertTrue(instruction.contains("Step description: Read the thermometer and capture the result in the log.")) + XCTAssertTrue(instruction.contains("Vision completion prompt: Verify that the worker recorded the temperature reading.")) + XCTAssertTrue(instruction.contains("Expected objects to look for: thermometer, clipboard")) + XCTAssertTrue(instruction.contains("Direct next action: Guide the worker through this step now: Record temperature log.")) + XCTAssertTrue(viewModel.geminiInstructionSyncStatus.contains("Record temperature log")) + } + + func testSpotterTargetsOnlyCurrentIncompleteStep() async throws { + let viewModel = StreamSessionViewModel(wearables: Wearables.shared) + + viewModel.checklistItems = [ + ChecklistItemState( + itemID: "inspect_packaging_seal", + name: "Inspect packaging seal", + aiPrompt: "Confirm the seal is intact before intake.", + expectedObjects: ["seal", "package"], + isChecked: true, + completionSource: .manual + ), + ChecklistItemState( + itemID: "record_temperature_log", + name: "Record temperature log", + critical: true, + aiPrompt: "Verify that the worker recorded the temperature reading.", + expectedObjects: ["thermometer", "clipboard"] + ), + ChecklistItemState( + itemID: "stage_delivery", + name: "Stage delivery", + aiPrompt: "Confirm the package is staged for pickup.", + expectedObjects: ["package", "pickup shelf"] + ), + ] + + XCTAssertEqual(viewModel.debugSpotterTargetIDs(), ["record_temperature_log"]) + } +} + +final class OpsAPIClientRoutingTests: XCTestCase { + override func tearDown() { + RequestCaptureURLProtocol.handler = nil + super.tearDown() + } + + func testWorkerRoutesUseAdminBaseURL() async throws { + let settings = SettingsManager.shared + let originalOpsBaseURL = settings.opsBaseURL + let originalAdminBaseURL = settings.adminBaseURL + let originalBearerToken = settings.openClawBearerToken + + settings.opsBaseURL = "https://ops.example.test" + settings.adminBaseURL = "http://admin.example.test:3001" + settings.openClawBearerToken = "worker-bearer-token" + defer { + settings.opsBaseURL = originalOpsBaseURL + settings.adminBaseURL = originalAdminBaseURL + settings.openClawBearerToken = originalBearerToken + } + + let lock = NSLock() + var capturedRequests: [URLRequest] = [] + RequestCaptureURLProtocol.handler = { request in + lock.withLock { + capturedRequests.append(request) + } + + let body: Data + switch request.url?.path { + case "/health": + body = Data(#"{"status":"ok","service":"ops"}"#.utf8) + case "/api/worker/live/heartbeat": + body = Data("{}".utf8) + case "/api/worker/media/upload-target": + body = Data( + #"{"assetId":"video-asset-1","bucket":"execution-videos","path":"sessions/session-1/recording.mp4","uploadUrl":"https://upload.example/video-1"}"# + .utf8 + ) + default: + body = Data("{}".utf8) + } + + let response = HTTPURLResponse( + url: try XCTUnwrap(request.url), + statusCode: 200, + httpVersion: nil, + headerFields: ["Content-Type": "application/json"] + ) + return (try XCTUnwrap(response), body) + } + + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [RequestCaptureURLProtocol.self] + let client = OpsAPIClient(session: URLSession(configuration: configuration)) + + let health = try await client.health() + XCTAssertEqual(health, "ok:ops") + + try await client.sendWorkerLiveHeartbeat( + WorkerLiveHeartbeatRequest( + sessionID: "11111111-1111-1111-1111-111111111111", + webrtcRoomCode: "ROOM42", + currentStepIndex: 2, + helpRequested: true, + status: "active", + lastFrameBucket: "live-frames", + lastFramePath: "sessions/session-1/last-frame.jpg" + ) + ) + + _ = try await client.requestWorkerMediaUploadTarget( + sessionID: "11111111-1111-1111-1111-111111111111", + assetType: "video", + filename: "recording.mp4", + contentType: "video/mp4", + byteSize: 256, + source: "phone-recording" + ) + + let requests = lock.withLock { capturedRequests } + XCTAssertEqual(requests.map { $0.url?.host }, ["ops.example.test", "admin.example.test", "admin.example.test"]) + XCTAssertEqual(requests.map { $0.url?.path }, ["/health", "/api/worker/live/heartbeat", "/api/worker/media/upload-target"]) + XCTAssertNil(requests.first?.value(forHTTPHeaderField: "Authorization")) + XCTAssertEqual(requests.dropFirst().first?.value(forHTTPHeaderField: "Authorization"), "Bearer worker-bearer-token") + let uploadPayload = try XCTUnwrap( + JSONSerialization.jsonObject(with: try XCTUnwrap(requests.last.flatMap(requestBodyData(from:)))) as? [String: Any] + ) + XCTAssertEqual(uploadPayload["source"] as? String, "phone-recording") + } + + func testWorkerRouteErrorsMentionAdminIngest() async throws { + let settings = SettingsManager.shared + let originalAdminBaseURL = settings.adminBaseURL + let originalBearerToken = settings.openClawBearerToken + + settings.adminBaseURL = "http://admin.example.test:3001" + settings.openClawBearerToken = "worker-bearer-token" + defer { + settings.adminBaseURL = originalAdminBaseURL + settings.openClawBearerToken = originalBearerToken + } + + RequestCaptureURLProtocol.handler = { request in + let response = HTTPURLResponse( + url: try XCTUnwrap(request.url), + statusCode: 404, + httpVersion: nil, + headerFields: ["Content-Type": "text/html"] + ) + let body = Data("

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%0D2$J$CIwlJ@Ll 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⁣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