neoagent 3.1.1-beta.9 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.env.example +2 -2
  2. package/README.md +5 -1
  3. package/docs/architecture.md +1 -1
  4. package/docs/benchmarking.md +68 -87
  5. package/docs/capabilities.md +1 -1
  6. package/docs/clients-and-devices.md +3 -4
  7. package/docs/configuration.md +1 -1
  8. package/docs/devices.md +1 -2
  9. package/docs/health.md +24 -0
  10. package/docs/index.md +2 -2
  11. package/docs/integrations-architecture.md +6 -0
  12. package/docs/integrations.md +15 -1
  13. package/docs/models.md +1 -1
  14. package/docs/persistence.md +1 -1
  15. package/docs/why-neoagent.md +1 -1
  16. package/flutter_app/README.md +0 -5
  17. package/flutter_app/android/app/src/main/AndroidManifest.xml +0 -14
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +0 -98
  19. package/flutter_app/lib/main.dart +0 -6
  20. package/flutter_app/lib/main_app_shell.dart +0 -2
  21. package/flutter_app/lib/main_controller.dart +2 -867
  22. package/flutter_app/lib/main_devices.dart +0 -73
  23. package/flutter_app/lib/main_integrations.dart +339 -187
  24. package/flutter_app/lib/main_launcher.dart +2 -212
  25. package/flutter_app/lib/main_models.dart +0 -259
  26. package/flutter_app/lib/main_navigation.dart +2 -14
  27. package/flutter_app/lib/main_operations.dart +322 -364
  28. package/flutter_app/lib/main_runtime.dart +9 -208
  29. package/flutter_app/lib/main_settings.dart +17 -199
  30. package/flutter_app/lib/main_shared.dart +20 -591
  31. package/flutter_app/lib/main_timeline.dart +26 -122
  32. package/flutter_app/lib/main_voice_assistant.dart +0 -5
  33. package/flutter_app/lib/src/backend_client.dart +0 -102
  34. package/flutter_app/lib/src/desktop_companion_io.dart +1 -65
  35. package/flutter_app/lib/src/desktop_companion_stub.dart +0 -12
  36. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +0 -4
  37. package/flutter_app/linux/flutter/generated_plugins.cmake +0 -1
  38. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
  39. package/flutter_app/macos/Podfile.lock +56 -5
  40. package/flutter_app/pubspec.lock +0 -7
  41. package/flutter_app/pubspec.yaml +0 -2
  42. package/flutter_app/web/index.html +174 -0
  43. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +0 -3
  44. package/flutter_app/windows/flutter/generated_plugins.cmake +0 -1
  45. package/landing/index.html +0 -2
  46. package/lib/manager.js +1 -1
  47. package/lib/schema_migrations.js +75 -0
  48. package/package.json +1 -4
  49. package/server/db/database.js +1 -133
  50. package/server/http/middleware.js +2 -16
  51. package/server/http/routes.js +0 -2
  52. package/server/public/.last_build_id +1 -1
  53. package/server/public/assets/NOTICES +0 -25
  54. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  55. package/server/public/flutter_bootstrap.js +1 -1
  56. package/server/public/index.html +174 -0
  57. package/server/public/main.dart.js +88081 -90036
  58. package/server/routes/auth.js +11 -2
  59. package/server/routes/settings.js +0 -5
  60. package/server/routes/voice_assistant.js +1 -128
  61. package/server/services/account/erasure.js +0 -3
  62. package/server/services/ai/deliverables/artifact_helpers.js +1 -1
  63. package/server/services/ai/loop/conversation_loop.js +6 -2
  64. package/server/services/ai/loop/tool_dispatch.js +0 -3
  65. package/server/services/ai/messagingFallback.js +1 -1
  66. package/server/services/ai/settings.js +0 -6
  67. package/server/services/ai/toolEvidence.js +1 -4
  68. package/server/services/ai/tools.js +0 -234
  69. package/server/services/desktop/auth.js +0 -3
  70. package/server/services/desktop/registry.js +2 -50
  71. package/server/services/integrations/neoarchive/provider.js +616 -0
  72. package/server/services/integrations/neorecall/client.js +140 -0
  73. package/server/services/integrations/neorecall/constants.js +87 -0
  74. package/server/services/integrations/neorecall/provider.js +163 -0
  75. package/server/services/integrations/registry.js +4 -2
  76. package/server/services/manager.js +0 -22
  77. package/server/services/memory/manager.js +44 -19
  78. package/server/services/memory/retrieval_reasoning.js +36 -14
  79. package/server/services/messaging/whatsapp.js +18 -0
  80. package/server/services/security/tool_categories.js +0 -5
  81. package/server/services/social_video/service.js +2 -20
  82. package/server/services/tasks/adapters/index.js +1 -1
  83. package/server/services/tasks/integration_runtime.js +3 -67
  84. package/server/services/tasks/runtime.js +1 -6
  85. package/server/services/tasks/task_repository.js +6 -13
  86. package/server/services/timeline/service.js +0 -224
  87. package/server/services/voice/message.js +0 -24
  88. package/server/services/voice/providers.js +42 -1
  89. package/server/services/voice/runtimeManager.js +9 -5
  90. package/server/services/voice/turnRunner.js +0 -22
  91. package/server/services/wearable/service.js +0 -5
  92. package/docs/recordings-and-health.md +0 -41
  93. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +0 -21
  94. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +0 -586
  95. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +0 -78
  96. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +0 -104
  97. package/flutter_app/lib/main_recordings.dart +0 -920
  98. package/flutter_app/lib/src/desktop_ocr_bridge.dart +0 -2
  99. package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +0 -125
  100. package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +0 -30
  101. package/flutter_app/lib/src/desktop_passive_history.dart +0 -332
  102. package/flutter_app/lib/src/recording_bridge.dart +0 -232
  103. package/flutter_app/lib/src/recording_bridge_io.dart +0 -1027
  104. package/flutter_app/lib/src/recording_bridge_stub.dart +0 -120
  105. package/flutter_app/lib/src/recording_bridge_web.dart +0 -702
  106. package/flutter_app/lib/src/recording_chunk_queue.dart +0 -149
  107. package/flutter_app/lib/src/recording_chunk_queue_io.dart +0 -182
  108. package/flutter_app/lib/src/recording_payloads.dart +0 -95
  109. package/flutter_app/third_party/desktop_audio_capture/LICENSE +0 -21
  110. package/flutter_app/third_party/desktop_audio_capture/README.md +0 -262
  111. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +0 -65
  112. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +0 -153
  113. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +0 -110
  114. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +0 -461
  115. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +0 -91
  116. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +0 -106
  117. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +0 -219
  118. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +0 -336
  119. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +0 -101
  120. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +0 -692
  121. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +0 -35
  122. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +0 -36
  123. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +0 -32
  124. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +0 -32
  125. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +0 -878
  126. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +0 -27
  127. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +0 -1172
  128. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +0 -655
  129. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +0 -12
  130. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +0 -30
  131. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +0 -87
  132. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +0 -105
  133. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +0 -80
  134. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +0 -31
  135. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +0 -12
  136. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +0 -23
  137. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +0 -25
  138. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +0 -1117
  139. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +0 -115
  140. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +0 -777
  141. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +0 -87
  142. package/server/routes/recordings.js +0 -335
  143. package/server/routes/screenHistory.js +0 -190
  144. package/server/services/ai/recordingInsights.js +0 -105
  145. package/server/services/integrations/neomail/provider.js +0 -993
  146. package/server/services/recordings/deepgram.js +0 -58
  147. package/server/services/recordings/manager.js +0 -1273
  148. package/server/services/tasks/adapters/neomail_email_received.js +0 -46
  149. package/server/services/voice/screenshotContext.js +0 -73
  150. package/server/services/voice/turnCoordinator.js +0 -51
@@ -6,15 +6,11 @@ class NeoAgentController extends ChangeNotifier {
6
6
  required BackendClient backendClient,
7
7
  required HealthBridge healthBridge,
8
8
  required WidgetBridge widgetBridge,
9
- required RecordingBridge recordingBridge,
10
9
  OAuthLauncher? oauthLauncher,
11
10
  }) : _backendClient = backendClient,
12
11
  _healthBridge = healthBridge,
13
12
  _widgetBridge = widgetBridge,
14
- _recordingBridge = recordingBridge,
15
13
  _oauthLauncher = oauthLauncher ?? createOAuthLauncher() {
16
- _recordingBridge.onRecordingStopped = _handleRecordingStopped;
17
- _recordingBridge.addListener(_handleRecordingBridgeChanged);
18
14
  _desktopCompanion.addListener(notifyListeners);
19
15
 
20
16
  AndroidAutoBridge.instance.onStartVoiceMode = startLiveVoiceCapture;
@@ -33,13 +29,10 @@ class NeoAgentController extends ChangeNotifier {
33
29
  final BackendClient _backendClient;
34
30
  final HealthBridge _healthBridge;
35
31
  final WidgetBridge _widgetBridge;
36
- final RecordingBridge _recordingBridge;
37
32
  final OAuthLauncher _oauthLauncher;
38
33
  final app_release_updater.AppReleaseUpdater _appReleaseUpdater =
39
34
  app_release_updater.AppReleaseUpdater();
40
35
  final LiveVoiceCapture _liveVoiceCapture = LiveVoiceCapture();
41
- final DesktopScreenCapture _desktopScreenCapture =
42
- createDesktopScreenCapture();
43
36
  final DesktopCompanionManager _desktopCompanion = DesktopCompanionManager(
44
37
  screenCapture: createDesktopScreenCapture(),
45
38
  );
@@ -106,8 +99,6 @@ class NeoAgentController extends ChangeNotifier {
106
99
  bool socketConnected = false;
107
100
  bool hasNetworkConnection = true;
108
101
  bool networkStatusKnown = false;
109
- bool _desktopFloatingToolbarPopupRequested = false;
110
- bool _voiceAssistantIncludeScreenContext = false;
111
102
 
112
103
  io.Socket? get streamSocket => socketConnected ? _socket : null;
113
104
 
@@ -144,7 +135,6 @@ class NeoAgentController extends ChangeNotifier {
144
135
  Map<String, dynamic>? versionInfo;
145
136
  Map<String, dynamic>? backendHealthStatus;
146
137
  HealthBridgeStatus? deviceHealthStatus;
147
- List<RecordingSessionItem> recordingSessions = const <RecordingSessionItem>[];
148
138
 
149
139
  List<ChatEntry> chatMessages = const <ChatEntry>[];
150
140
  bool chatHistoryHasMore = false;
@@ -220,8 +210,6 @@ class NeoAgentController extends ChangeNotifier {
220
210
  ActiveRunState? activeRun;
221
211
  List<ToolEventItem> toolEvents = const <ToolEventItem>[];
222
212
  String streamingAssistant = '';
223
- bool isStartingRecording = false;
224
- bool isStoppingRecording = false;
225
213
  bool _isStartingLiveVoice = false;
226
214
  bool _isStoppingLiveVoice = false;
227
215
  bool _liveVoiceCaptureActive = false;
@@ -242,24 +230,17 @@ class NeoAgentController extends ChangeNotifier {
242
230
  VoiceAssistantLiveState voiceAssistantLiveState = VoiceAssistantLiveState();
243
231
  bool _desktopAskOnClose = true;
244
232
  bool _desktopKeepRunningOnClose = true;
245
- bool _desktopAutoShowFloatingToolbar = true;
246
233
  bool _desktopAssistantHotkeyEnabled = true;
247
234
  bool isRefreshingTimeline = false;
248
- Set<String> selectedTimelineSources = <String>{'screen', 'tasks', 'runs'};
235
+ Set<String> selectedTimelineSources = <String>{'tasks', 'runs'};
249
236
 
250
237
  bool get desktopCompanionEnabled => _desktopCompanion.enabled;
251
- bool get desktopPassiveHistoryEnabled =>
252
- _desktopCompanion.passiveHistoryEnabled;
253
238
  bool get isLauncherMode => appMode == NeoAgentAppMode.launcher;
254
239
  bool get desktopCompanionConnected => _desktopCompanion.connected;
255
240
  bool get desktopCompanionConnecting => _desktopCompanion.connecting;
256
241
  bool get desktopCompanionPaused => _desktopCompanion.paused;
257
242
  String get desktopCompanionLabel => _desktopCompanion.label;
258
243
  String? get desktopCompanionErrorMessage => _desktopCompanion.errorMessage;
259
- String? get desktopPassiveHistoryLastUploadedAt =>
260
- _desktopCompanion.passiveHistoryLastUploadedAt;
261
- String? get desktopPassiveHistoryLastError =>
262
- _desktopCompanion.passiveHistoryLastError;
263
244
  String? get requestedRunFocusId => _requestedRunFocusId;
264
245
  Map<String, Object?> get desktopCompanionStatus => _desktopCompanion.status;
265
246
 
@@ -367,21 +348,15 @@ class NeoAgentController extends ChangeNotifier {
367
348
  _appReleaseUpdater.dispose();
368
349
  _desktopCompanion.removeListener(notifyListeners);
369
350
  unawaited(_desktopCompanion.disconnect());
370
- _recordingBridge.removeListener(_handleRecordingBridgeChanged);
371
- _recordingBridge.dispose();
372
351
  unawaited(_liveVoiceCapture.dispose());
373
352
  _oauthLauncher.dispose();
374
353
  super.dispose();
375
354
  }
376
355
 
377
- RecordingRuntimeStatus get recordingRuntime => _recordingBridge.status;
378
-
379
356
  bool get desktopAskOnClose => _desktopAskOnClose;
380
357
 
381
358
  bool get desktopKeepRunningOnClose => _desktopKeepRunningOnClose;
382
359
 
383
- bool get desktopAutoShowFloatingToolbar => _desktopAutoShowFloatingToolbar;
384
-
385
360
  bool get desktopAssistantHotkeyEnabled => _desktopAssistantHotkeyEnabled;
386
361
 
387
362
  String? get sessionCookie => _backendClient.sessionCookie;
@@ -402,15 +377,6 @@ class NeoAgentController extends ChangeNotifier {
402
377
  notifyListeners();
403
378
  }
404
379
 
405
- bool get desktopFloatingToolbarPopupRequested =>
406
- _desktopFloatingToolbarPopupRequested;
407
-
408
- bool get voiceAssistantIncludeScreenContext =>
409
- _voiceAssistantIncludeScreenContext;
410
-
411
- bool get canCaptureVoiceAssistantScreenContext =>
412
- _desktopScreenCapture.isSupported;
413
-
414
380
  bool get isLiveVoiceCaptureEngaged =>
415
381
  _isStartingLiveVoice || _liveVoiceCaptureActive;
416
382
 
@@ -438,53 +404,6 @@ class NeoAgentController extends ChangeNotifier {
438
404
  return '${local.year}-${local.month.toString().padLeft(2, '0')}-${local.day.toString().padLeft(2, '0')} ${local.hour.toString().padLeft(2, '0')}:$minute';
439
405
  }
440
406
 
441
- bool get canStartDesktopRecording =>
442
- _supportsDesktopShell &&
443
- isAuthenticated &&
444
- !isBooting &&
445
- !requiresBackendUrlSetup &&
446
- backendUrl.trim().isNotEmpty &&
447
- !isStartingRecording &&
448
- !isLiveVoiceCaptureActive &&
449
- !isLiveVoiceCaptureStarting &&
450
- !recordingRuntime.active &&
451
- recordingRuntime.supportsSystemAudio;
452
-
453
- Map<String, Object?> _recordingRuntimeSnapshot() {
454
- final runtime = recordingRuntime;
455
- return <String, Object?>{
456
- 'runtimeActive': runtime.active,
457
- 'runtimePaused': runtime.paused,
458
- 'runtimeSessionId': runtime.sessionId,
459
- 'runtimeStartedAt': runtime.startedAt?.toIso8601String(),
460
- 'runtimeError': runtime.errorMessage,
461
- 'runtimeSupportsSystemAudio': runtime.supportsSystemAudio,
462
- 'runtimeFloatingToolbarVisible': runtime.floatingToolbarVisible,
463
- 'sessionsLoaded': recordingSessions.length,
464
- };
465
- }
466
-
467
- void _upsertRecordingSession(RecordingSessionItem session) {
468
- final existingIndex = recordingSessions.indexWhere(
469
- (item) => item.id == session.id,
470
- );
471
- if (existingIndex >= 0) {
472
- recordingSessions = <RecordingSessionItem>[
473
- ...recordingSessions.sublist(0, existingIndex),
474
- session,
475
- ...recordingSessions.sublist(existingIndex + 1),
476
- ];
477
- return;
478
- }
479
- recordingSessions = <RecordingSessionItem>[session, ...recordingSessions];
480
- }
481
-
482
- void _removeRecordingSession(String sessionId) {
483
- recordingSessions = recordingSessions
484
- .where((item) => item.id != sessionId)
485
- .toList();
486
- }
487
-
488
407
  void _appendChatMessage(
489
408
  String content, {
490
409
  required String role,
@@ -625,142 +544,11 @@ class NeoAgentController extends ChangeNotifier {
625
544
  return lowercase ? value.toLowerCase() : value;
626
545
  }
627
546
 
628
- Future<void> _finalizeRecordingSessionQuietly(String? sessionId) async {
629
- final trimmed = sessionId?.trim() ?? '';
630
- if (trimmed.isEmpty) {
631
- return;
632
- }
633
- try {
634
- await _backendClient.finalizeRecordingSession(
635
- backendUrl,
636
- trimmed,
637
- stopReason: 'cancelled',
638
- );
639
- } catch (_) {}
640
- }
641
-
642
- Future<void> _startRecordingCapture({
643
- required String logKey,
644
- required Map<String, dynamic> payload,
645
- required Future<void> Function(String sessionId) startCapture,
646
- }) async {
647
- if (isStartingRecording || recordingRuntime.active) {
648
- return;
649
- }
650
- isStartingRecording = true;
651
- errorMessage = null;
652
- notifyListeners();
653
-
654
- String? sessionId;
655
- try {
656
- _logRecording('$logKey.request');
657
- final response = await _backendClient.createRecordingSession(
658
- backendUrl,
659
- payload,
660
- );
661
- final session = RecordingSessionItem.fromJson(
662
- _jsonMap(response['session']),
663
- );
664
- sessionId = session.id;
665
- _upsertRecordingSession(session);
666
- await startCapture(session.id);
667
- _logRecording(
668
- '$logKey.done',
669
- data: <String, Object?>{'sessionId': session.id},
670
- );
671
- notifyListeners();
672
- } catch (error) {
673
- _logRecording(
674
- '$logKey.failed',
675
- data: <String, Object?>{'sessionId': sessionId},
676
- error: error,
677
- );
678
- await _finalizeRecordingSessionQuietly(sessionId);
679
- errorMessage = _friendlyErrorMessage(error);
680
- await refreshRecordings();
681
- } finally {
682
- isStartingRecording = false;
683
- notifyListeners();
684
- }
685
- }
686
-
687
- void _logRecording(
688
- String event, {
689
- Map<String, Object?> data = const <String, Object?>{},
690
- Object? error,
691
- StackTrace? stackTrace,
692
- }) {
693
- AppDiagnostics.log(
694
- 'recording.controller',
695
- event,
696
- data: <String, Object?>{..._recordingRuntimeSnapshot(), ...data},
697
- error: error,
698
- stackTrace: stackTrace,
699
- );
700
- }
701
-
702
- void _logRecordingConsistency(String reason) {
703
- final activeSessionId = recordingRuntime.sessionId;
704
- RecordingSessionItem? activeSession;
705
- if (activeSessionId != null) {
706
- for (final session in recordingSessions) {
707
- if (session.id == activeSessionId) {
708
- activeSession = session;
709
- break;
710
- }
711
- }
712
- }
713
-
714
- final serverRecordingCount = recordingSessions
715
- .where((session) => session.status == 'recording')
716
- .length;
717
-
718
- _logRecording(
719
- 'consistency.snapshot',
720
- data: <String, Object?>{
721
- 'reason': reason,
722
- 'activeSessionId': activeSessionId,
723
- 'activeSessionStatus': activeSession?.status,
724
- 'serverRecordingCount': serverRecordingCount,
725
- },
726
- );
727
-
728
- if (!recordingRuntime.active &&
729
- activeSession != null &&
730
- activeSession.status == 'recording') {
731
- _logRecording(
732
- 'consistency.mismatch_runtime_inactive_server_recording',
733
- data: <String, Object?>{
734
- 'reason': reason,
735
- 'sessionId': activeSession.id,
736
- },
737
- );
738
- }
739
-
740
- if (recordingRuntime.active &&
741
- activeSession != null &&
742
- activeSession.status != 'recording') {
743
- _logRecording(
744
- 'consistency.mismatch_runtime_active_server_not_recording',
745
- data: <String, Object?>{
746
- 'reason': reason,
747
- 'sessionId': activeSession.id,
748
- 'serverStatus': activeSession.status,
749
- },
750
- );
751
- }
752
- }
753
-
754
547
  BlockedSenderNotice? get pendingBlockedSenderNotice =>
755
548
  _blockedSenderQueue.isEmpty ? null : _blockedSenderQueue.first;
756
549
 
757
550
  List<String> get ignoredChats => _ignoredChats.toList();
758
551
 
759
- void _handleRecordingBridgeChanged() {
760
- _logRecording('bridge.changed');
761
- notifyListeners();
762
- }
763
-
764
552
  static LogEntry _logEntryFromDiagnostic(AppDiagnosticEntry entry) {
765
553
  final buffer = StringBuffer('[${entry.area}] ${entry.event}');
766
554
  if (entry.data.isNotEmpty) {
@@ -812,33 +600,6 @@ class NeoAgentController extends ChangeNotifier {
812
600
  : merged;
813
601
  }
814
602
 
815
- Future<void> _handleRecordingStopped(String sessionId) async {
816
- _logRecording(
817
- 'bridge.on_recording_stopped',
818
- data: <String, Object?>{'sessionId': sessionId},
819
- );
820
- try {
821
- await _backendClient.finalizeRecordingSession(
822
- backendUrl,
823
- sessionId,
824
- stopReason: 'ended',
825
- );
826
- _logRecording(
827
- 'finalize.ok',
828
- data: <String, Object?>{'sessionId': sessionId, 'stopReason': 'ended'},
829
- );
830
- await refreshRecordings();
831
- } catch (error) {
832
- _logRecording(
833
- 'finalize.failed',
834
- data: <String, Object?>{'sessionId': sessionId, 'stopReason': 'ended'},
835
- error: error,
836
- );
837
- errorMessage = _friendlyErrorMessage(error);
838
- notifyListeners();
839
- }
840
- }
841
-
842
603
  Future<void> bootstrap() async {
843
604
  _prefs = await SharedPreferences.getInstance();
844
605
  _ignoredChats.addAll(
@@ -853,14 +614,9 @@ class NeoAgentController extends ChangeNotifier {
853
614
  _desktopAskOnClose = _prefs?.getBool('desktop.askOnClose') ?? true;
854
615
  _desktopKeepRunningOnClose =
855
616
  _prefs?.getBool('desktop.keepRunningOnClose') ?? true;
856
- _desktopAutoShowFloatingToolbar =
857
- _prefs?.getBool('desktop.autoShowFloatingToolbar') ?? true;
858
617
  _desktopAssistantHotkeyEnabled =
859
618
  _prefs?.getBool('desktop.assistantHotkeyEnabled') ?? true;
860
619
  _restoreSelectedSectionFromPrefs();
861
- _voiceAssistantIncludeScreenContext =
862
- (_prefs?.getBool('voiceAssistant.includeScreenContext') ?? false) &&
863
- canCaptureVoiceAssistantScreenContext;
864
620
  appUpdateChannel =
865
621
  _prefs?.getString('app.update.channel')?.trim().toLowerCase() == 'beta'
866
622
  ? 'beta'
@@ -917,7 +673,6 @@ class NeoAgentController extends ChangeNotifier {
917
673
  _backendClient.clearSessionCookie();
918
674
  }
919
675
 
920
- await _recordingBridge.refreshStatus();
921
676
  notifyListeners();
922
677
 
923
678
  if (appUpdaterConfigured &&
@@ -1598,20 +1353,6 @@ class NeoAgentController extends ChangeNotifier {
1598
1353
  }
1599
1354
 
1600
1355
  Future<void> logout() async {
1601
- final recordingSessionId = recordingRuntime.sessionId;
1602
- if (recordingRuntime.active && recordingSessionId != null) {
1603
- try {
1604
- await _recordingBridge.stopActiveRecording();
1605
- if (!recordingRuntime.supportsBackgroundMic) {
1606
- await _backendClient.finalizeRecordingSession(
1607
- backendUrl,
1608
- recordingSessionId,
1609
- stopReason: 'ended',
1610
- );
1611
- }
1612
- } catch (_) {}
1613
- }
1614
-
1615
1356
  final logoutFuture = _backendClient.logout(backendUrl);
1616
1357
  _authCycle += 1;
1617
1358
  _clearAuthenticatedState();
@@ -1728,7 +1469,6 @@ class NeoAgentController extends ChangeNotifier {
1728
1469
  androidUiDumpPath = null;
1729
1470
  versionInfo = null;
1730
1471
  backendHealthStatus = null;
1731
- recordingSessions = const <RecordingSessionItem>[];
1732
1472
  activeRun = null;
1733
1473
  toolEvents = const <ToolEventItem>[];
1734
1474
  streamingAssistant = '';
@@ -2310,9 +2050,6 @@ class NeoAgentController extends ChangeNotifier {
2310
2050
  _backendClient.fetchMcpServers(backendUrl, agentId: agentId),
2311
2051
  const <Map<String, dynamic>>[],
2312
2052
  );
2313
- final recordingsFuture = _backendClient
2314
- .fetchRecordingSessions(backendUrl)
2315
- .catchError((_) => const <Map<String, dynamic>>[]);
2316
2053
  unawaited(checkBillingEnabled());
2317
2054
  final browserFuture = _backendClient
2318
2055
  .fetchBrowserStatus(backendUrl)
@@ -2373,7 +2110,6 @@ class NeoAgentController extends ChangeNotifier {
2373
2110
  final tasksResponse = await tasksFuture;
2374
2111
  final widgetsResponse = await widgetsFuture;
2375
2112
  final mcpResponse = await mcpFuture;
2376
- final recordingsResponse = await recordingsFuture;
2377
2113
  final browserResponse = await browserFuture;
2378
2114
  final socialReachResponse = await socialReachFuture;
2379
2115
  final browserExtensionResponse = await browserExtensionFuture;
@@ -2467,11 +2203,6 @@ class NeoAgentController extends ChangeNotifier {
2467
2203
  mcpResponse,
2468
2204
  McpServerItem.fromJson,
2469
2205
  );
2470
- recordingSessions = _decodeModelList(
2471
- 'recordings',
2472
- recordingsResponse,
2473
- RecordingSessionItem.fromJson,
2474
- );
2475
2206
  browserRuntime = Map<String, dynamic>.from(browserResponse);
2476
2207
  socialReachStatus = Map<String, dynamic>.from(socialReachResponse);
2477
2208
  browserExtensionStatus = Map<String, dynamic>.from(
@@ -2494,10 +2225,6 @@ class NeoAgentController extends ChangeNotifier {
2494
2225
  desktopRuntime['devices'],
2495
2226
  fallbackToMapValues: true,
2496
2227
  );
2497
- await _recordingBridge.refreshStatus();
2498
- if (!_isCurrentAuthCycle(authCycle)) {
2499
- return;
2500
- }
2501
2228
  deviceHealthStatus = await _healthBridge.getStatus();
2502
2229
  if (!_isCurrentAuthCycle(authCycle)) {
2503
2230
  return;
@@ -2847,69 +2574,6 @@ class NeoAgentController extends ChangeNotifier {
2847
2574
  notifyListeners();
2848
2575
  }
2849
2576
 
2850
- Future<void> refreshRecordings() async {
2851
- _logRecording('refresh.request');
2852
- recordingSessions = _decodeModelList(
2853
- 'recordings',
2854
- await _backendClient.fetchRecordingSessions(backendUrl),
2855
- RecordingSessionItem.fromJson,
2856
- );
2857
- await _recordingBridge.refreshStatus();
2858
- _logRecording(
2859
- 'refresh.done',
2860
- data: <String, Object?>{
2861
- 'sessionStatuses': recordingSessions
2862
- .take(5)
2863
- .map((item) => '${item.id}:${item.status}')
2864
- .join(','),
2865
- },
2866
- );
2867
- _logRecordingConsistency('refreshRecordings');
2868
- notifyListeners();
2869
- }
2870
-
2871
- Future<void> _refreshRecordingSessionById(String sessionId) async {
2872
- final trimmed = sessionId.trim();
2873
- if (trimmed.isEmpty || !isAuthenticated) {
2874
- return;
2875
- }
2876
-
2877
- try {
2878
- _logRecording(
2879
- 'refresh_by_id.request',
2880
- data: <String, Object?>{'sessionId': trimmed},
2881
- );
2882
- final response = await _backendClient.fetchRecordingSession(
2883
- backendUrl,
2884
- trimmed,
2885
- );
2886
- final session = RecordingSessionItem.fromJson(
2887
- _jsonMap(response['session']),
2888
- );
2889
- _upsertRecordingSession(session);
2890
-
2891
- await _recordingBridge.refreshStatus();
2892
- _logRecording(
2893
- 'refresh_by_id.done',
2894
- data: <String, Object?>{
2895
- 'sessionId': trimmed,
2896
- 'status': session.status,
2897
- 'endedAt': session.endedAt?.toIso8601String(),
2898
- },
2899
- );
2900
- _logRecordingConsistency('refreshRecordingSessionById');
2901
- notifyListeners();
2902
- } catch (error) {
2903
- _logRecording(
2904
- 'refresh_by_id.fallback_full_refresh',
2905
- data: <String, Object?>{'sessionId': trimmed},
2906
- error: error,
2907
- );
2908
- // Session may have been pruned or unavailable; fall back to full refresh.
2909
- await refreshRecordings();
2910
- }
2911
- }
2912
-
2913
2577
  Future<void> refreshDevices() async {
2914
2578
  if (!isAuthenticated || isRefreshingDevices) {
2915
2579
  return;
@@ -3769,25 +3433,6 @@ class NeoAgentController extends ChangeNotifier {
3769
3433
  );
3770
3434
  }
3771
3435
 
3772
- Uri resolveRecordingSourceAudioUri(String sessionId, String sourceKey) {
3773
- final encodedSessionId = Uri.encodeComponent(sessionId);
3774
- final encodedSourceKey = Uri.encodeComponent(sourceKey);
3775
- return resolveRuntimeAsset(
3776
- '/api/recordings/$encodedSessionId/audio/$encodedSourceKey',
3777
- );
3778
- }
3779
-
3780
- Future<Uint8List> fetchRecordingSourceAudioBytes(
3781
- String sessionId,
3782
- String sourceKey,
3783
- ) {
3784
- final encodedSessionId = Uri.encodeComponent(sessionId);
3785
- final encodedSourceKey = Uri.encodeComponent(sourceKey);
3786
- return fetchRuntimeAssetBytes(
3787
- '/api/recordings/$encodedSessionId/audio/$encodedSourceKey',
3788
- );
3789
- }
3790
-
3791
3436
  Future<Uint8List> fetchRuntimeAssetBytes(String path) {
3792
3437
  final separator = path.contains('?') ? '&' : '?';
3793
3438
  return _backendClient.fetchBinary(
@@ -3804,153 +3449,6 @@ class NeoAgentController extends ChangeNotifier {
3804
3449
  return <String, String>{'Cookie': cookie};
3805
3450
  }
3806
3451
 
3807
- Future<void> startWebRecording() async {
3808
- await _startRecordingCapture(
3809
- logKey: 'start_web',
3810
- payload: buildWebScreenAndMicRecordingPayload(),
3811
- startCapture: (sessionId) => _recordingBridge.startWebRecording(
3812
- baseUrl: backendUrl,
3813
- sessionId: sessionId,
3814
- ),
3815
- );
3816
- }
3817
-
3818
- Future<void> startWebMicrophoneRecording() async {
3819
- await _startRecordingCapture(
3820
- logKey: 'start_web_mic_only',
3821
- payload: buildWebMicrophoneRecordingPayload(),
3822
- startCapture: (sessionId) => _recordingBridge.startWebMicrophoneRecording(
3823
- baseUrl: backendUrl,
3824
- sessionId: sessionId,
3825
- ),
3826
- );
3827
- }
3828
-
3829
- Future<void> startBackgroundRecording() async {
3830
- await _startRecordingCapture(
3831
- logKey: 'start_background',
3832
- payload: buildAndroidBackgroundRecordingPayload(),
3833
- startCapture: (sessionId) => _recordingBridge.startBackgroundRecording(
3834
- baseUrl: backendUrl,
3835
- sessionCookie: _backendClient.sessionCookie ?? '',
3836
- sessionId: sessionId,
3837
- ),
3838
- );
3839
- }
3840
-
3841
- Future<void> startDesktopRecording() async {
3842
- if (isLiveVoiceCaptureActive || isLiveVoiceCaptureStarting) {
3843
- errorMessage =
3844
- 'Finish assistant push-to-talk before starting desktop recording.';
3845
- notifyListeners();
3846
- return;
3847
- }
3848
- if (!canStartDesktopRecording) {
3849
- if (!isAuthenticated || requiresBackendUrlSetup) {
3850
- errorMessage =
3851
- 'Sign in and finish backend setup before starting desktop recording.';
3852
- notifyListeners();
3853
- }
3854
- return;
3855
- }
3856
- await _startRecordingCapture(
3857
- logKey: 'start_desktop',
3858
- payload: buildDesktopRecordingPayload(),
3859
- startCapture: (sessionId) => _recordingBridge.startDesktopAudioRecording(
3860
- baseUrl: backendUrl,
3861
- sessionCookie: _backendClient.sessionCookie ?? '',
3862
- sessionId: sessionId,
3863
- autoShowToolbar: _desktopAutoShowFloatingToolbar,
3864
- ),
3865
- );
3866
- }
3867
-
3868
- Future<void> pauseBackgroundRecording() async {
3869
- try {
3870
- _logRecording('pause_background.request');
3871
- await _recordingBridge.pauseBackgroundRecording();
3872
- _logRecording('pause_background.done');
3873
- } catch (error) {
3874
- _logRecording('pause_background.failed', error: error);
3875
- errorMessage = _friendlyErrorMessage(error);
3876
- notifyListeners();
3877
- }
3878
- }
3879
-
3880
- Future<void> resumeBackgroundRecording() async {
3881
- try {
3882
- _logRecording('resume_background.request');
3883
- await _recordingBridge.resumeBackgroundRecording();
3884
- _logRecording('resume_background.done');
3885
- } catch (error) {
3886
- _logRecording('resume_background.failed', error: error);
3887
- errorMessage = _friendlyErrorMessage(error);
3888
- notifyListeners();
3889
- }
3890
- }
3891
-
3892
- Future<void> pauseDesktopRecording() async {
3893
- try {
3894
- _logRecording('pause_desktop.request');
3895
- await _recordingBridge.pauseDesktopRecording();
3896
- _logRecording('pause_desktop.done');
3897
- } catch (error) {
3898
- _logRecording('pause_desktop.failed', error: error);
3899
- errorMessage = _friendlyErrorMessage(error);
3900
- notifyListeners();
3901
- }
3902
- }
3903
-
3904
- Future<void> resumeDesktopRecording() async {
3905
- try {
3906
- _logRecording('resume_desktop.request');
3907
- await _recordingBridge.resumeDesktopRecording();
3908
- _logRecording('resume_desktop.done');
3909
- } catch (error) {
3910
- _logRecording('resume_desktop.failed', error: error);
3911
- errorMessage = _friendlyErrorMessage(error);
3912
- notifyListeners();
3913
- }
3914
- }
3915
-
3916
- Future<void> showDesktopFloatingToolbar() async {
3917
- try {
3918
- _desktopFloatingToolbarPopupRequested = true;
3919
- await _recordingBridge.showFloatingToolbar();
3920
- } catch (error) {
3921
- errorMessage = _friendlyErrorMessage(error);
3922
- notifyListeners();
3923
- }
3924
- }
3925
-
3926
- Future<void> hideDesktopFloatingToolbar() async {
3927
- try {
3928
- _desktopFloatingToolbarPopupRequested = false;
3929
- await _recordingBridge.hideFloatingToolbar();
3930
- } catch (error) {
3931
- errorMessage = _friendlyErrorMessage(error);
3932
- notifyListeners();
3933
- }
3934
- }
3935
-
3936
- Future<void> openDesktopMicrophoneSettings() async {
3937
- try {
3938
- await _recordingBridge.openMicrophoneSettings();
3939
- } catch (error) {
3940
- errorMessage = _friendlyErrorMessage(error);
3941
- notifyListeners();
3942
- }
3943
- }
3944
-
3945
- Future<void> openDesktopSystemAudioSettings() async {
3946
- try {
3947
- await _recordingBridge.openSystemAudioSettings();
3948
- } catch (error) {
3949
- errorMessage = _friendlyErrorMessage(error);
3950
- notifyListeners();
3951
- }
3952
- }
3953
-
3954
3452
  Future<void> setDesktopClosePreference({
3955
3453
  required bool askOnClose,
3956
3454
  required bool keepRunningOnClose,
@@ -3962,12 +3460,6 @@ class NeoAgentController extends ChangeNotifier {
3962
3460
  notifyListeners();
3963
3461
  }
3964
3462
 
3965
- Future<void> setDesktopAutoShowFloatingToolbar(bool value) async {
3966
- _desktopAutoShowFloatingToolbar = value;
3967
- await _prefs?.setBool('desktop.autoShowFloatingToolbar', value);
3968
- notifyListeners();
3969
- }
3970
-
3971
3463
  Future<void> setDesktopAssistantHotkeyEnabled(bool value) async {
3972
3464
  _desktopAssistantHotkeyEnabled = value;
3973
3465
  await _prefs?.setBool('desktop.assistantHotkeyEnabled', value);
@@ -4014,19 +3506,6 @@ class NeoAgentController extends ChangeNotifier {
4014
3506
  }
4015
3507
  }
4016
3508
 
4017
- Future<void> setDesktopPassiveHistoryEnabled(bool value) async {
4018
- final prefs = _prefs;
4019
- if (prefs == null) {
4020
- return;
4021
- }
4022
- try {
4023
- await _desktopCompanion.setPassiveHistoryEnabled(value, prefs);
4024
- } catch (error) {
4025
- errorMessage = _friendlyErrorMessage(error);
4026
- notifyListeners();
4027
- }
4028
- }
4029
-
4030
3509
  Future<void> rotateDesktopCompanionIdentity() async {
4031
3510
  final prefs = _prefs;
4032
3511
  if (prefs == null) {
@@ -4061,302 +3540,6 @@ class NeoAgentController extends ChangeNotifier {
4061
3540
  }
4062
3541
  }
4063
3542
 
4064
- Future<void> setVoiceAssistantIncludeScreenContext(bool value) async {
4065
- _voiceAssistantIncludeScreenContext =
4066
- value && canCaptureVoiceAssistantScreenContext;
4067
- await _prefs?.setBool(
4068
- 'voiceAssistant.includeScreenContext',
4069
- _voiceAssistantIncludeScreenContext,
4070
- );
4071
- notifyListeners();
4072
- }
4073
-
4074
- Future<void> toggleVoiceAssistantScreenContext() {
4075
- return setVoiceAssistantIncludeScreenContext(
4076
- !voiceAssistantIncludeScreenContext,
4077
- );
4078
- }
4079
-
4080
- void acknowledgeDesktopFloatingToolbarPopupRequest() {
4081
- _desktopFloatingToolbarPopupRequested = false;
4082
- }
4083
-
4084
- Future<Map<String, String>> _captureVoiceAssistantScreenshotPayload() async {
4085
- if (!_voiceAssistantIncludeScreenContext ||
4086
- !canCaptureVoiceAssistantScreenContext) {
4087
- return const <String, String>{};
4088
- }
4089
-
4090
- try {
4091
- final capture = await _desktopScreenCapture.captureCurrentScreen();
4092
- if (capture == null || capture.bytes.isEmpty) {
4093
- return const <String, String>{};
4094
- }
4095
- final optimized = _optimizeVoiceAssistantScreenshotPayload(capture);
4096
- if (optimized == null) {
4097
- AppDiagnostics.log(
4098
- 'desktop.assistant',
4099
- 'screen_capture.optimize_failed',
4100
- data: <String, Object?>{
4101
- 'mimeType': capture.mimeType,
4102
- 'originalByteLength': capture.bytes.length,
4103
- },
4104
- );
4105
- return const <String, String>{};
4106
- }
4107
- AppDiagnostics.log(
4108
- 'desktop.assistant',
4109
- 'screen_capture.success',
4110
- data: <String, Object?>{
4111
- 'mimeType': optimized.mimeType,
4112
- 'byteLength': optimized.bytes.length,
4113
- 'originalByteLength': capture.bytes.length,
4114
- 'resizedOrReencoded': optimized.resizedOrReencoded,
4115
- },
4116
- );
4117
- return <String, String>{
4118
- 'screenshotMimeType': optimized.mimeType,
4119
- 'screenshotBase64': base64Encode(optimized.bytes),
4120
- };
4121
- } catch (error, stackTrace) {
4122
- AppDiagnostics.log(
4123
- 'desktop.assistant',
4124
- 'screen_capture.failed',
4125
- error: error,
4126
- stackTrace: stackTrace,
4127
- );
4128
- return const <String, String>{};
4129
- }
4130
- }
4131
-
4132
- Future<Map<String, String>> buildVoiceAssistantContextPayload() async {
4133
- return _captureVoiceAssistantScreenshotPayload();
4134
- }
4135
-
4136
- _OptimizedScreenshotPayload? _optimizeVoiceAssistantScreenshotPayload(
4137
- DesktopScreenCaptureResult capture,
4138
- ) {
4139
- final sourceBytes = Uint8List.fromList(capture.bytes);
4140
- final sourceMime = _normalizeScreenshotMimeType(capture.mimeType);
4141
- final decoded = img.decodeImage(sourceBytes);
4142
- if (decoded == null) {
4143
- if (sourceBytes.length <= _voiceAssistantScreenshotMaxBytes) {
4144
- return _OptimizedScreenshotPayload(
4145
- bytes: sourceBytes,
4146
- mimeType: sourceMime,
4147
- resizedOrReencoded: false,
4148
- );
4149
- }
4150
- return null;
4151
- }
4152
-
4153
- var workingImage = decoded;
4154
- var transformed = false;
4155
- if (workingImage.width > _voiceAssistantScreenshotMaxDimension ||
4156
- workingImage.height > _voiceAssistantScreenshotMaxDimension) {
4157
- final scale = math.min(
4158
- _voiceAssistantScreenshotMaxDimension / workingImage.width,
4159
- _voiceAssistantScreenshotMaxDimension / workingImage.height,
4160
- );
4161
- final nextWidth = math.max(1, (workingImage.width * scale).round());
4162
- final nextHeight = math.max(1, (workingImage.height * scale).round());
4163
- workingImage = img.copyResize(
4164
- workingImage,
4165
- width: nextWidth,
4166
- height: nextHeight,
4167
- interpolation: img.Interpolation.average,
4168
- );
4169
- transformed = true;
4170
- }
4171
-
4172
- if (!transformed &&
4173
- sourceBytes.length <= _voiceAssistantScreenshotMaxBytes) {
4174
- return _OptimizedScreenshotPayload(
4175
- bytes: sourceBytes,
4176
- mimeType: sourceMime,
4177
- resizedOrReencoded: false,
4178
- );
4179
- }
4180
-
4181
- final qualitySteps = <int>[84, 74, 64, 54, 46, 40];
4182
- var bestBytes = Uint8List(0);
4183
- for (var pass = 0; pass < qualitySteps.length; pass += 1) {
4184
- final encoded = Uint8List.fromList(
4185
- img.encodeJpg(workingImage, quality: qualitySteps[pass]),
4186
- );
4187
- if (bestBytes.isEmpty || encoded.length < bestBytes.length) {
4188
- bestBytes = encoded;
4189
- }
4190
- if (encoded.length <= _voiceAssistantScreenshotMaxBytes) {
4191
- return _OptimizedScreenshotPayload(
4192
- bytes: encoded,
4193
- mimeType: 'image/jpeg',
4194
- resizedOrReencoded: true,
4195
- );
4196
- }
4197
- if (pass % 2 == 1) {
4198
- final nextWidth = math.max(1, (workingImage.width * 0.85).round());
4199
- final nextHeight = math.max(1, (workingImage.height * 0.85).round());
4200
- if (nextWidth == workingImage.width &&
4201
- nextHeight == workingImage.height) {
4202
- continue;
4203
- }
4204
- workingImage = img.copyResize(
4205
- workingImage,
4206
- width: nextWidth,
4207
- height: nextHeight,
4208
- interpolation: img.Interpolation.average,
4209
- );
4210
- }
4211
- }
4212
-
4213
- if (bestBytes.isNotEmpty &&
4214
- bestBytes.length <= _voiceAssistantScreenshotMaxBytes) {
4215
- return _OptimizedScreenshotPayload(
4216
- bytes: bestBytes,
4217
- mimeType: 'image/jpeg',
4218
- resizedOrReencoded: true,
4219
- );
4220
- }
4221
- return null;
4222
- }
4223
-
4224
- String _normalizeScreenshotMimeType(String mimeType) {
4225
- final normalized = mimeType.trim().toLowerCase();
4226
- if (normalized.startsWith('image/')) {
4227
- return normalized;
4228
- }
4229
- return 'image/png';
4230
- }
4231
-
4232
- Future<void> stopRecording({String stopReason = 'stopped'}) async {
4233
- final sessionId = recordingRuntime.sessionId;
4234
- if (sessionId == null || isStoppingRecording) {
4235
- return;
4236
- }
4237
- final isAndroidBackgroundStop =
4238
- recordingRuntime.supportsBackgroundMic &&
4239
- !recordingRuntime.supportsScreenAndMic;
4240
- isStoppingRecording = true;
4241
- errorMessage = null;
4242
- _logRecording(
4243
- 'stop.request',
4244
- data: <String, Object?>{
4245
- 'sessionId': sessionId,
4246
- 'isAndroidBackgroundStop': isAndroidBackgroundStop,
4247
- },
4248
- );
4249
- notifyListeners();
4250
- try {
4251
- _desktopFloatingToolbarPopupRequested = false;
4252
- await _recordingBridge.stopActiveRecording();
4253
- if (isAndroidBackgroundStop) {
4254
- await Future<void>.delayed(const Duration(milliseconds: 600));
4255
- } else {
4256
- await _backendClient.finalizeRecordingSession(
4257
- backendUrl,
4258
- sessionId,
4259
- stopReason: stopReason,
4260
- );
4261
- }
4262
- await refreshRecordings();
4263
- _logRecording(
4264
- 'stop.done',
4265
- data: <String, Object?>{'sessionId': sessionId},
4266
- );
4267
- } catch (error) {
4268
- _logRecording(
4269
- 'stop.failed',
4270
- data: <String, Object?>{
4271
- 'sessionId': sessionId,
4272
- 'isAndroidBackgroundStop': isAndroidBackgroundStop,
4273
- },
4274
- error: error,
4275
- );
4276
- errorMessage = _friendlyErrorMessage(error);
4277
- notifyListeners();
4278
- } finally {
4279
- isStoppingRecording = false;
4280
- notifyListeners();
4281
- }
4282
- }
4283
-
4284
- Future<void> retryRecording(String sessionId) async {
4285
- try {
4286
- await _backendClient.retryRecordingSession(backendUrl, sessionId);
4287
- await refreshRecordings();
4288
- } catch (error) {
4289
- errorMessage = _friendlyErrorMessage(error);
4290
- notifyListeners();
4291
- }
4292
- }
4293
-
4294
- Future<void> deleteRecordingSegment(String sessionId, int segmentId) async {
4295
- try {
4296
- errorMessage = null;
4297
- final response = await _backendClient.deleteRecordingTranscriptSegment(
4298
- backendUrl,
4299
- sessionId,
4300
- segmentId,
4301
- );
4302
- final session = RecordingSessionItem.fromJson(
4303
- _jsonMap(response['session']),
4304
- );
4305
- _upsertRecordingSession(session);
4306
- notifyListeners();
4307
- } catch (error) {
4308
- errorMessage = _friendlyErrorMessage(error);
4309
- notifyListeners();
4310
- }
4311
- }
4312
-
4313
- Future<void> deleteRecordingSession(String sessionId) async {
4314
- try {
4315
- errorMessage = null;
4316
- await _backendClient.deleteRecordingSession(backendUrl, sessionId);
4317
- _removeRecordingSession(sessionId);
4318
- notifyListeners();
4319
- } catch (error) {
4320
- errorMessage = _friendlyErrorMessage(error);
4321
- notifyListeners();
4322
- }
4323
- }
4324
-
4325
- Future<VoiceAssistantTurnResult> runVoiceAssistantTurn({
4326
- required String sessionId,
4327
- String? ttsProvider,
4328
- String? ttsVoice,
4329
- String? ttsModel,
4330
- }) async {
4331
- final screenshotPayload = await buildVoiceAssistantContextPayload();
4332
- final response = await _backendClient.runVoiceAssistantTurn(
4333
- backendUrl,
4334
- sessionId: sessionId,
4335
- ttsProvider:
4336
- ttsProvider?.trim().ifEmpty(voiceTtsProvider) ?? voiceTtsProvider,
4337
- ttsVoice: ttsVoice?.trim().ifEmpty(voiceTtsVoice) ?? voiceTtsVoice,
4338
- ttsModel: ttsModel?.trim().ifEmpty(voiceTtsModel) ?? voiceTtsModel,
4339
- agentId: _scopedAgentId,
4340
- screenshotBase64: screenshotPayload['screenshotBase64'],
4341
- screenshotMimeType: screenshotPayload['screenshotMimeType'],
4342
- );
4343
-
4344
- final result = VoiceAssistantTurnResult.fromJson(response);
4345
- _upsertRecordingSession(result.session);
4346
-
4347
- if (result.transcript.trim().isNotEmpty) {
4348
- _appendUserChatMessage(result.transcript, platform: 'voice_assistant');
4349
- }
4350
- if (result.replyText.trim().isNotEmpty) {
4351
- _appendAssistantChatMessage(
4352
- result.replyText,
4353
- platform: 'voice_assistant',
4354
- );
4355
- }
4356
- notifyListeners();
4357
- return result;
4358
- }
4359
-
4360
3543
  Future<bool> _ensureSocketReady({
4361
3544
  Duration timeout = const Duration(seconds: 5),
4362
3545
  }) async {
@@ -4556,11 +3739,6 @@ class NeoAgentController extends ChangeNotifier {
4556
3739
  }
4557
3740
 
4558
3741
  Future<void> startLiveVoiceCapture() async {
4559
- if (recordingRuntime.active || isStartingRecording || isStoppingRecording) {
4560
- throw StateError(
4561
- 'Stop recording before starting the assistant push-to-talk flow.',
4562
- );
4563
- }
4564
3742
  if (_isStartingLiveVoice || _isStoppingLiveVoice) {
4565
3743
  return;
4566
3744
  }
@@ -4726,7 +3904,6 @@ class NeoAgentController extends ChangeNotifier {
4726
3904
  );
4727
3905
  return;
4728
3906
  }
4729
- final screenshotPayload = await buildVoiceAssistantContextPayload();
4730
3907
  AppDiagnostics.log(
4731
3908
  'desktop.assistant',
4732
3909
  'ptt.capture_committing',
@@ -4737,12 +3914,7 @@ class NeoAgentController extends ChangeNotifier {
4737
3914
  );
4738
3915
  _liveVoiceFinalSequence = _liveVoiceBufferedChunks.length - 1;
4739
3916
  _liveVoiceCommitPending = true;
4740
- _liveVoicePendingCommitPayload = <String, dynamic>{
4741
- if ((screenshotPayload['screenshotBase64'] ?? '').isNotEmpty)
4742
- 'screenshotBase64': screenshotPayload['screenshotBase64'],
4743
- if ((screenshotPayload['screenshotMimeType'] ?? '').isNotEmpty)
4744
- 'screenshotMimeType': screenshotPayload['screenshotMimeType'],
4745
- };
3917
+ _liveVoicePendingCommitPayload = <String, dynamic>{};
4746
3918
  _setLiveVoiceRecoveryWindow();
4747
3919
  voiceAssistantLiveState = voiceAssistantLiveState.copyWith(
4748
3920
  state: 'transcribing',
@@ -4992,10 +4164,6 @@ class NeoAgentController extends ChangeNotifier {
4992
4164
  required String defaultChatModel,
4993
4165
  required String defaultSubagentModel,
4994
4166
  required String defaultSpeechModel,
4995
- required String defaultRecordingTranscriptionProvider,
4996
- required String defaultRecordingTranscriptionModel,
4997
- required String defaultRecordingSummaryProvider,
4998
- required String defaultRecordingSummaryModel,
4999
4167
  required String fallbackModel,
5000
4168
  required String voiceSttProvider,
5001
4169
  required String voiceSttModel,
@@ -5025,12 +4193,6 @@ class NeoAgentController extends ChangeNotifier {
5025
4193
  'default_chat_model': defaultChatModel,
5026
4194
  'default_subagent_model': defaultSubagentModel,
5027
4195
  'default_speech_model': defaultSpeechModel,
5028
- 'default_recording_transcription_provider':
5029
- defaultRecordingTranscriptionProvider,
5030
- 'default_recording_transcription_model':
5031
- defaultRecordingTranscriptionModel,
5032
- 'default_recording_summary_provider': defaultRecordingSummaryProvider,
5033
- 'default_recording_summary_model': defaultRecordingSummaryModel,
5034
4196
  'fallback_model_id': fallbackModel,
5035
4197
  'voice_stt_provider': voiceSttProvider,
5036
4198
  'voice_stt_model': voiceSttModel,
@@ -6812,24 +5974,6 @@ class NeoAgentController extends ChangeNotifier {
6812
5974
  String get defaultSpeechModel =>
6813
5975
  settings['default_speech_model']?.toString() ?? 'auto';
6814
5976
 
6815
- String get defaultRecordingTranscriptionModel =>
6816
- settings['default_recording_transcription_model']?.toString() ?? 'nova-3';
6817
-
6818
- String get defaultRecordingTranscriptionProvider => _settingString(
6819
- 'default_recording_transcription_provider',
6820
- 'deepgram',
6821
- lowercase: true,
6822
- );
6823
-
6824
- String get defaultRecordingSummaryModel =>
6825
- settings['default_recording_summary_model']?.toString() ?? 'auto';
6826
-
6827
- String get defaultRecordingSummaryProvider => _settingString(
6828
- 'default_recording_summary_provider',
6829
- 'auto',
6830
- lowercase: true,
6831
- );
6832
-
6833
5977
  String get fallbackModel =>
6834
5978
  settings['fallback_model_id']?.toString() ??
6835
5979
  _firstAvailableModelId(supportedModels);
@@ -7173,15 +6317,6 @@ class NeoAgentController extends ChangeNotifier {
7173
6317
  payload['error']?.toString() ?? 'Messaging error. Please try again.';
7174
6318
  notifyListeners();
7175
6319
  });
7176
- socket.on('recordings:updated', (dynamic data) {
7177
- final payload = _jsonMap(data);
7178
- final sessionId = payload['sessionId']?.toString() ?? '';
7179
- if (sessionId.isEmpty) {
7180
- unawaited(refreshRecordings());
7181
- return;
7182
- }
7183
- unawaited(_refreshRecordingSessionById(sessionId));
7184
- });
7185
6320
  socket.on('timeline:updated', (dynamic _) {
7186
6321
  unawaited(refreshTimeline());
7187
6322
  });