neoagent 3.1.1-beta.8 → 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 +149 -438
  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 +88124 -90164
  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
@@ -1,6 +1,6 @@
1
1
  part of 'main.dart';
2
2
 
3
- enum _LauncherPage { assistant, widgets, recordings, settings }
3
+ enum _LauncherPage { assistant, widgets, settings }
4
4
 
5
5
  class LauncherHomeView extends StatefulWidget {
6
6
  const LauncherHomeView({super.key, required this.controller});
@@ -13,7 +13,6 @@ class LauncherHomeView extends StatefulWidget {
13
13
 
14
14
  class _LauncherHomeViewState extends State<LauncherHomeView> {
15
15
  static const int _assistantButtonKeyCode = 131;
16
- static const int _recordingButtonKeyCode = 132;
17
16
 
18
17
  final AndroidLauncherBridge _launcherBridge = AndroidLauncherBridge.instance;
19
18
  StreamSubscription<LauncherHardwareButtonEvent>? _buttonSubscription;
@@ -22,7 +21,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
22
21
  LauncherVolumeState? _volumeState;
23
22
  LauncherDeviceStatus? _deviceStatus;
24
23
  bool _assistantHardwareCaptureActive = false;
25
- bool _recordingConfirmOpen = false;
26
24
  DateTime _now = DateTime.now();
27
25
  Timer? _statusTimer;
28
26
 
@@ -223,59 +221,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
223
221
  });
224
222
  }
225
223
 
226
- Future<void> _toggleRecordingFromHardware() async {
227
- final controller = widget.controller;
228
- if (controller.recordingRuntime.active) {
229
- await controller.stopRecording(stopReason: 'hardware_button');
230
- return;
231
- }
232
- if (!await _confirmRecordingStart(sourceLabel: 'hardware button')) {
233
- return;
234
- }
235
- await controller.startBackgroundRecording();
236
- }
237
-
238
- Future<void> _startRecordingFromUi() async {
239
- final controller = widget.controller;
240
- if (controller.recordingRuntime.active || controller.isStartingRecording) {
241
- return;
242
- }
243
- if (!await _confirmRecordingStart(sourceLabel: 'Start button')) {
244
- return;
245
- }
246
- await controller.startBackgroundRecording();
247
- }
248
-
249
- Future<bool> _confirmRecordingStart({required String sourceLabel}) async {
250
- if (!mounted || _recordingConfirmOpen) {
251
- return false;
252
- }
253
- _recordingConfirmOpen = true;
254
- final confirmed = await showDialog<bool>(
255
- context: context,
256
- builder: (context) {
257
- return AlertDialog(
258
- title: const Text('Start recording?'),
259
- content: Text(
260
- 'Recording was requested from $sourceLabel. Start a new background recording session now?',
261
- ),
262
- actions: <Widget>[
263
- TextButton(
264
- onPressed: () => Navigator.of(context).pop(false),
265
- child: const Text('Cancel'),
266
- ),
267
- FilledButton(
268
- onPressed: () => Navigator.of(context).pop(true),
269
- child: const Text('Start'),
270
- ),
271
- ],
272
- );
273
- },
274
- );
275
- _recordingConfirmOpen = false;
276
- return confirmed == true;
277
- }
278
-
279
224
  Future<void> _startAssistantFromHardware() async {
280
225
  if (_assistantHardwareCaptureActive) {
281
226
  return;
@@ -308,11 +253,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
308
253
  } else if (event.isUp) {
309
254
  unawaited(_stopAssistantFromHardware());
310
255
  }
311
- return;
312
- }
313
-
314
- if (event.keyCode == _recordingButtonKeyCode && event.isUp) {
315
- unawaited(_toggleRecordingFromHardware());
316
256
  }
317
257
  }
318
258
 
@@ -320,147 +260,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
320
260
  return VoiceAssistantPanel(controller: widget.controller);
321
261
  }
322
262
 
323
- Widget _buildRecordingsPage() {
324
- final controller = widget.controller;
325
- final runtime = controller.recordingRuntime;
326
- final recentSessions = controller.recordingSessions.take(4).toList();
327
- return ListView(
328
- padding: _pagePadding(context),
329
- children: <Widget>[
330
- _PageTitle(
331
- title: 'Recorder',
332
- subtitle:
333
- 'Record audio and keep it running while you use the device.',
334
- trailing: Wrap(
335
- spacing: 10,
336
- runSpacing: 10,
337
- children: <Widget>[
338
- FilledButton.icon(
339
- onPressed: controller.isStartingRecording || runtime.active
340
- ? null
341
- : _startRecordingFromUi,
342
- icon: const Icon(Icons.mic_none_outlined),
343
- label: const Text('Start'),
344
- ),
345
- OutlinedButton.icon(
346
- onPressed: runtime.active
347
- ? (runtime.paused
348
- ? controller.resumeBackgroundRecording
349
- : controller.pauseBackgroundRecording)
350
- : null,
351
- icon: Icon(
352
- runtime.paused
353
- ? Icons.play_circle_outline
354
- : Icons.pause_circle_outline,
355
- ),
356
- label: Text(runtime.paused ? 'Resume' : 'Pause'),
357
- ),
358
- OutlinedButton.icon(
359
- onPressed: runtime.active && !controller.isStoppingRecording
360
- ? controller.stopRecording
361
- : null,
362
- icon: const Icon(Icons.stop_circle_outlined),
363
- label: const Text('Stop'),
364
- ),
365
- ],
366
- ),
367
- ),
368
- Card(
369
- child: Padding(
370
- padding: const EdgeInsets.all(18),
371
- child: Column(
372
- crossAxisAlignment: CrossAxisAlignment.start,
373
- children: <Widget>[
374
- Wrap(
375
- spacing: 10,
376
- runSpacing: 10,
377
- children: <Widget>[
378
- _DotStatus(
379
- label: runtime.active
380
- ? (runtime.paused ? 'Paused' : 'Recording')
381
- : 'Idle',
382
- color: runtime.active
383
- ? (runtime.paused ? _warning : _danger)
384
- : _success,
385
- ),
386
- _MetaPill(
387
- icon: Icons.mic_outlined,
388
- label: runtime.supportsBackgroundMic
389
- ? 'Background recording ready'
390
- : 'Recording unavailable',
391
- ),
392
- ],
393
- ),
394
- if (runtime.errorMessage != null &&
395
- runtime.errorMessage!.trim().isNotEmpty) ...<Widget>[
396
- const SizedBox(height: 14),
397
- _InlineError(message: runtime.errorMessage!),
398
- ],
399
- ],
400
- ),
401
- ),
402
- ),
403
- const SizedBox(height: 16),
404
- Card(
405
- child: Padding(
406
- padding: const EdgeInsets.all(18),
407
- child: Column(
408
- crossAxisAlignment: CrossAxisAlignment.start,
409
- children: <Widget>[
410
- const _SectionTitle('Recent Sessions'),
411
- const SizedBox(height: 10),
412
- if (recentSessions.isEmpty)
413
- Text(
414
- 'No recordings yet.',
415
- style: TextStyle(color: _textSecondary),
416
- )
417
- else
418
- ...recentSessions.map(
419
- (session) => Padding(
420
- padding: const EdgeInsets.only(bottom: 12),
421
- child: Row(
422
- crossAxisAlignment: CrossAxisAlignment.start,
423
- children: <Widget>[
424
- Icon(Icons.audio_file_outlined, color: _accent),
425
- const SizedBox(width: 10),
426
- Expanded(
427
- child: Column(
428
- crossAxisAlignment: CrossAxisAlignment.start,
429
- children: <Widget>[
430
- Text(
431
- session.title,
432
- style: const TextStyle(
433
- fontWeight: FontWeight.w700,
434
- ),
435
- ),
436
- const SizedBox(height: 4),
437
- Text(
438
- '${session.statusLabel} • ${session.startedAtLabel}',
439
- style: TextStyle(color: _textSecondary),
440
- ),
441
- ],
442
- ),
443
- ),
444
- ],
445
- ),
446
- ),
447
- ),
448
- Align(
449
- alignment: Alignment.centerLeft,
450
- child: OutlinedButton.icon(
451
- onPressed: controller.refreshRecordings,
452
- icon: const Icon(Icons.refresh),
453
- label: const Text('Refresh'),
454
- ),
455
- ),
456
- ],
457
- ),
458
- ),
459
- ),
460
- ],
461
- );
462
- }
463
-
464
263
  Widget _buildWidgetsPage() {
465
264
  final controller = widget.controller;
466
265
  return ListView(
@@ -637,7 +436,7 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
637
436
  const _SectionTitle('Extra Buttons'),
638
437
  const SizedBox(height: 10),
639
438
  Text(
640
- 'Hold button 131 for the assistant. Press button 132 to start or stop recording.',
439
+ 'Hold button 131 for the assistant.',
641
440
  style: TextStyle(color: _textSecondary, height: 1.45),
642
441
  ),
643
442
  const SizedBox(height: 16),
@@ -649,10 +448,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
649
448
  icon: Icons.keyboard_voice_outlined,
650
449
  label: 'Assistant key 131',
651
450
  ),
652
- const _MetaPill(
653
- icon: Icons.fiber_smart_record_outlined,
654
- label: 'Recording key 132',
655
- ),
656
451
  ],
657
452
  ),
658
453
  ],
@@ -911,7 +706,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
911
706
  final pages = <Widget>[
912
707
  _buildAssistantPage(),
913
708
  _buildWidgetsPage(),
914
- _buildRecordingsPage(),
915
709
  _buildSettingsPage(),
916
710
  ];
917
711
  return IndexedStack(
@@ -938,10 +732,6 @@ class _LauncherHomeViewState extends State<LauncherHomeView> {
938
732
  icon: Icon(Icons.dashboard_customize_outlined),
939
733
  label: 'Widgets',
940
734
  ),
941
- NavigationDestination(
942
- icon: Icon(Icons.fiber_smart_record_outlined),
943
- label: 'Record',
944
- ),
945
735
  NavigationDestination(
946
736
  icon: Icon(Icons.tune_outlined),
947
737
  label: 'Settings',
@@ -1143,239 +1143,6 @@ MessagingAccessRule? _ruleFromPrefixedEntry(String platform, String entry) {
1143
1143
  return MessagingAccessRule(scope: 'chat', value: normalized);
1144
1144
  }
1145
1145
 
1146
- class RecordingSessionItem {
1147
- const RecordingSessionItem({
1148
- required this.id,
1149
- required this.title,
1150
- required this.platform,
1151
- required this.status,
1152
- required this.startedAt,
1153
- required this.endedAt,
1154
- required this.durationMs,
1155
- required this.transcriptText,
1156
- required this.lastError,
1157
- required this.sources,
1158
- required this.transcriptSegments,
1159
- this.structuredContent = const <String, dynamic>{},
1160
- });
1161
-
1162
- factory RecordingSessionItem.fromJson(Map<dynamic, dynamic> json) {
1163
- final rawSources = json['sources'];
1164
- final sourceRows = rawSources is List
1165
- ? rawSources
1166
- : rawSources is Map
1167
- ? rawSources.values.toList(growable: false)
1168
- : const <dynamic>[];
1169
- final rawSegments = json['transcriptSegments'];
1170
- final transcriptSegmentRows = rawSegments is List
1171
- ? rawSegments
1172
- : rawSegments is Map
1173
- ? rawSegments.values.toList(growable: false)
1174
- : const <dynamic>[];
1175
- return RecordingSessionItem(
1176
- id: json['id']?.toString() ?? '',
1177
- title: json['title']?.toString().ifEmpty('Recording') ?? 'Recording',
1178
- platform: json['platform']?.toString() ?? 'unknown',
1179
- status: json['status']?.toString() ?? 'recording',
1180
- startedAt: _parseTimestamp(json['startedAt']?.toString()),
1181
- endedAt: _parseOptionalTimestamp(json['endedAt']?.toString()),
1182
- durationMs: _asInt(json['durationMs']),
1183
- transcriptText: json['transcriptText']?.toString() ?? '',
1184
- lastError: json['lastError']?.toString(),
1185
- sources: sourceRows
1186
- .whereType<Map<dynamic, dynamic>>()
1187
- .map(RecordingSourceItem.fromJson)
1188
- .toList(),
1189
- transcriptSegments: transcriptSegmentRows
1190
- .whereType<Map<dynamic, dynamic>>()
1191
- .map(RecordingTranscriptSegment.fromJson)
1192
- .toList(),
1193
- structuredContent: _jsonMap(json['structuredContent']),
1194
- );
1195
- }
1196
-
1197
- final String id;
1198
- final String title;
1199
- final String platform;
1200
- final String status;
1201
- final DateTime startedAt;
1202
- final DateTime? endedAt;
1203
- final int durationMs;
1204
- final String transcriptText;
1205
- final String? lastError;
1206
- final List<RecordingSourceItem> sources;
1207
- final List<RecordingTranscriptSegment> transcriptSegments;
1208
- final Map<String, dynamic> structuredContent;
1209
-
1210
- String get startedAtLabel => _formatTimestamp(startedAt);
1211
-
1212
- String get platformLabel {
1213
- switch (platform) {
1214
- case 'web':
1215
- return 'Web';
1216
- case 'android':
1217
- return 'Android';
1218
- default:
1219
- return platform.ifEmpty('Unknown');
1220
- }
1221
- }
1222
-
1223
- String get durationLabel => _formatDuration(durationMs);
1224
-
1225
- String get statusLabel {
1226
- switch (status) {
1227
- case 'recording':
1228
- return 'Recording';
1229
- case 'processing':
1230
- return 'Processing';
1231
- case 'completed':
1232
- return 'Completed';
1233
- case 'failed':
1234
- return 'Failed';
1235
- case 'cancelled':
1236
- return 'Cancelled';
1237
- default:
1238
- return status;
1239
- }
1240
- }
1241
-
1242
- Color get statusColor {
1243
- switch (status) {
1244
- case 'recording':
1245
- return _danger;
1246
- case 'processing':
1247
- return _warning;
1248
- case 'completed':
1249
- return _success;
1250
- case 'failed':
1251
- return _danger;
1252
- case 'cancelled':
1253
- return _textSecondary;
1254
- default:
1255
- return _textSecondary;
1256
- }
1257
- }
1258
- }
1259
-
1260
- class RecordingSourceItem {
1261
- const RecordingSourceItem({
1262
- required this.sourceKey,
1263
- required this.sourceKind,
1264
- required this.mediaKind,
1265
- required this.mimeType,
1266
- required this.durationMs,
1267
- required this.chunkCount,
1268
- });
1269
-
1270
- factory RecordingSourceItem.fromJson(Map<dynamic, dynamic> json) {
1271
- return RecordingSourceItem(
1272
- sourceKey: json['sourceKey']?.toString() ?? '',
1273
- sourceKind: json['sourceKind']?.toString() ?? '',
1274
- mediaKind: json['mediaKind']?.toString() ?? '',
1275
- mimeType: json['mimeType']?.toString() ?? '',
1276
- durationMs: _asInt(json['durationMs']),
1277
- chunkCount: _asInt(json['chunkCount']),
1278
- );
1279
- }
1280
-
1281
- final String sourceKey;
1282
- final String sourceKind;
1283
- final String mediaKind;
1284
- final String mimeType;
1285
- final int durationMs;
1286
- final int chunkCount;
1287
-
1288
- String get label {
1289
- switch (sourceKind) {
1290
- case 'screen-share':
1291
- return 'Screen';
1292
- case 'microphone':
1293
- return 'Microphone';
1294
- default:
1295
- return sourceKind.ifEmpty(sourceKey);
1296
- }
1297
- }
1298
-
1299
- String get durationLabel => _formatDuration(durationMs);
1300
- }
1301
-
1302
- class RecordingTranscriptSegment {
1303
- const RecordingTranscriptSegment({
1304
- required this.id,
1305
- required this.speaker,
1306
- required this.text,
1307
- required this.startMs,
1308
- });
1309
-
1310
- factory RecordingTranscriptSegment.fromJson(Map<dynamic, dynamic> json) {
1311
- return RecordingTranscriptSegment(
1312
- id: _asInt(json['id']),
1313
- speaker:
1314
- json['speaker']?.toString() ?? json['sourceKey']?.toString() ?? '',
1315
- text: json['text']?.toString() ?? '',
1316
- startMs: _asInt(json['startMs']),
1317
- );
1318
- }
1319
-
1320
- final int id;
1321
- final String speaker;
1322
- final String text;
1323
- final int startMs;
1324
-
1325
- String get timestampLabel => _formatDuration(startMs);
1326
-
1327
- String get displayText {
1328
- if (speaker.trim().isEmpty) {
1329
- return text;
1330
- }
1331
- return '${speaker.replaceAll('-', ' ')}: $text';
1332
- }
1333
- }
1334
-
1335
- class VoiceAssistantTurnResult {
1336
- const VoiceAssistantTurnResult({
1337
- required this.session,
1338
- required this.transcript,
1339
- required this.replyText,
1340
- required this.audioMimeType,
1341
- required this.audioBytes,
1342
- this.runId,
1343
- this.ttsProvider,
1344
- this.ttsModel,
1345
- this.ttsVoice,
1346
- this.ttsError,
1347
- });
1348
-
1349
- factory VoiceAssistantTurnResult.fromJson(Map<dynamic, dynamic> json) {
1350
- final audioBase64 = json['audioBase64']?.toString() ?? '';
1351
- return VoiceAssistantTurnResult(
1352
- session: RecordingSessionItem.fromJson(_jsonMap(json['session'])),
1353
- transcript: json['transcript']?.toString() ?? '',
1354
- replyText: json['replyText']?.toString() ?? '',
1355
- audioMimeType: json['audioMimeType']?.toString() ?? 'audio/mpeg',
1356
- audioBytes: audioBase64.trim().isEmpty
1357
- ? Uint8List(0)
1358
- : base64Decode(audioBase64),
1359
- runId: json['runId']?.toString(),
1360
- ttsProvider: json['ttsProvider']?.toString(),
1361
- ttsModel: json['ttsModel']?.toString(),
1362
- ttsVoice: json['ttsVoice']?.toString(),
1363
- ttsError: json['ttsError']?.toString(),
1364
- );
1365
- }
1366
-
1367
- final RecordingSessionItem session;
1368
- final String transcript;
1369
- final String replyText;
1370
- final String audioMimeType;
1371
- final Uint8List audioBytes;
1372
- final String? runId;
1373
- final String? ttsProvider;
1374
- final String? ttsModel;
1375
- final String? ttsVoice;
1376
- final String? ttsError;
1377
- }
1378
-
1379
1146
  class MemoryTransferImportResult {
1380
1147
  const MemoryTransferImportResult({
1381
1148
  required this.importedCount,
@@ -2399,52 +2166,26 @@ class TimelineEventItem {
2399
2166
  String get occurredAtLabel => _formatTimestamp(occurredAt);
2400
2167
 
2401
2168
  String get sourceLabel => switch (sourceKind) {
2402
- 'screen' => 'Screen',
2403
2169
  'tasks' => 'Task',
2404
2170
  'runs' => 'Run',
2405
2171
  _ => _titleCase(sourceKind.replaceAll('_', ' ')),
2406
2172
  };
2407
2173
 
2408
2174
  Color get sourceColor => switch (sourceKind) {
2409
- 'screen' => _accent,
2410
2175
  'tasks' => _warning,
2411
2176
  'runs' => _success,
2412
2177
  _ => _textSecondary,
2413
2178
  };
2414
2179
 
2415
- String get appName => metadata['appName']?.toString() ?? '';
2416
- String get windowTitle => metadata['windowTitle']?.toString() ?? '';
2417
2180
  String get deviceLabel =>
2418
2181
  metadata['deviceLabel']?.toString() ??
2419
2182
  metadata['deviceId']?.toString() ??
2420
2183
  '';
2421
- String get previewText => metadata['previewText']?.toString() ?? summary;
2422
2184
  String get runId => metadata['runId']?.toString() ?? sourceId ?? '';
2423
2185
  String get taskName =>
2424
2186
  metadata['taskName']?.toString().trim().isNotEmpty == true
2425
2187
  ? metadata['taskName'].toString()
2426
2188
  : title;
2427
- DateTime? get startedAt =>
2428
- _parseOptionalTimestamp(metadata['startedAt']?.toString());
2429
- DateTime? get endedAt =>
2430
- _parseOptionalTimestamp(metadata['endedAt']?.toString());
2431
-
2432
- String get screenSpanLabel {
2433
- final start = startedAt;
2434
- final end = endedAt;
2435
- if (start == null) {
2436
- return occurredAtLabel;
2437
- }
2438
- final localStart = start.toLocal();
2439
- final startMinute = localStart.minute.toString().padLeft(2, '0');
2440
- if (end == null) {
2441
- return '${localStart.hour.toString().padLeft(2, '0')}:$startMinute';
2442
- }
2443
- final localEnd = end.toLocal();
2444
- final endMinute = localEnd.minute.toString().padLeft(2, '0');
2445
- return '${localStart.hour.toString().padLeft(2, '0')}:$startMinute'
2446
- ' - ${localEnd.hour.toString().padLeft(2, '0')}:$endMinute';
2447
- }
2448
2189
  }
2449
2190
 
2450
2191
  class TokenUsageSnapshot {
@@ -17,7 +17,6 @@ enum AppSection {
17
17
  timeline,
18
18
  voiceAssistant,
19
19
  devices,
20
- recordings,
21
20
  messaging,
22
21
  runs,
23
22
  settings,
@@ -34,7 +33,7 @@ enum AppSection {
34
33
  billing,
35
34
  }
36
35
 
37
- enum SidebarGroup { chat, timeline, recordings, automation, settings }
36
+ enum SidebarGroup { chat, timeline, automation, settings }
38
37
 
39
38
  extension SidebarGroupX on SidebarGroup {
40
39
  String get label {
@@ -43,8 +42,6 @@ extension SidebarGroupX on SidebarGroup {
43
42
  return 'Chat';
44
43
  case SidebarGroup.timeline:
45
44
  return 'Timeline';
46
- case SidebarGroup.recordings:
47
- return 'Recordings';
48
45
  case SidebarGroup.automation:
49
46
  return 'Automation';
50
47
  case SidebarGroup.settings:
@@ -58,8 +55,6 @@ extension SidebarGroupX on SidebarGroup {
58
55
  return Icons.chat_bubble_outline;
59
56
  case SidebarGroup.timeline:
60
57
  return Icons.timeline_rounded;
61
- case SidebarGroup.recordings:
62
- return Icons.fiber_smart_record_outlined;
63
58
  case SidebarGroup.automation:
64
59
  return Icons.auto_awesome_outlined;
65
60
  case SidebarGroup.settings:
@@ -79,8 +74,6 @@ extension AppSectionX on AppSection {
79
74
  return 'Voice assistant';
80
75
  case AppSection.devices:
81
76
  return 'Devices';
82
- case AppSection.recordings:
83
- return 'Recordings';
84
77
  case AppSection.messaging:
85
78
  return 'Messaging';
86
79
  case AppSection.runs:
@@ -122,8 +115,6 @@ extension AppSectionX on AppSection {
122
115
  return Icons.keyboard_voice_outlined;
123
116
  case AppSection.devices:
124
117
  return Icons.devices_other_outlined;
125
- case AppSection.recordings:
126
- return Icons.fiber_smart_record_outlined;
127
118
  case AppSection.messaging:
128
119
  return Icons.forum_outlined;
129
120
  case AppSection.runs:
@@ -162,8 +153,6 @@ extension AppSectionX on AppSection {
162
153
  return SidebarGroup.chat;
163
154
  case AppSection.timeline:
164
155
  return SidebarGroup.timeline;
165
- case AppSection.recordings:
166
- return SidebarGroup.recordings;
167
156
  case AppSection.devices:
168
157
  case AppSection.skills:
169
158
  case AppSection.integrations:
@@ -210,8 +199,7 @@ extension AppSectionX on AppSection {
210
199
  return effectiveSection.label;
211
200
  }
212
201
  if (effectiveSection.group == SidebarGroup.chat ||
213
- effectiveSection.group == SidebarGroup.timeline ||
214
- effectiveSection.group == SidebarGroup.recordings) {
202
+ effectiveSection.group == SidebarGroup.timeline) {
215
203
  return groupLabel;
216
204
  }
217
205
  if (groupLabel == effectiveSection.label) {