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
@@ -505,10 +505,8 @@ class _SkillsPanelState extends State<SkillsPanel>
505
505
 
506
506
  // Installed tab search & filter state
507
507
  String _installedQuery = '';
508
- String _installedStatusFilter =
509
- 'all'; // 'all' | 'active' | 'draft' | 'disabled'
510
- String _installedSourceFilter =
511
- 'all'; // 'all' | 'built-in' | 'learned' | 'user' | 'store'
508
+ String _installedStatusFilter = 'all'; // 'all' | 'active' | 'draft' | 'disabled'
509
+ String _installedSourceFilter = 'all'; // 'all' | 'built-in' | 'learned' | 'user' | 'store'
512
510
  late final TextEditingController _installedSearchController;
513
511
 
514
512
  @override
@@ -647,36 +645,18 @@ class _SkillsPanelState extends State<SkillsPanel>
647
645
  final q = _installedQuery;
648
646
  if (q.isNotEmpty &&
649
647
  !skill.name.toLowerCase().contains(q) &&
650
- !skill.description.toLowerCase().contains(q)) {
651
- return false;
652
- }
648
+ !skill.description.toLowerCase().contains(q)) return false;
653
649
  if (_installedStatusFilter != 'all') {
654
- if (_installedStatusFilter == 'active' &&
655
- (!skill.enabled || skill.draft)) {
656
- return false;
657
- }
658
- if (_installedStatusFilter == 'draft' && !skill.draft) {
659
- return false;
660
- }
661
- if (_installedStatusFilter == 'disabled' && skill.enabled) {
662
- return false;
663
- }
664
- }
665
- if (_installedSourceFilter != 'all' &&
666
- skill.source != _installedSourceFilter) {
667
- return false;
650
+ if (_installedStatusFilter == 'active' && (!skill.enabled || skill.draft)) return false;
651
+ if (_installedStatusFilter == 'draft' && !skill.draft) return false;
652
+ if (_installedStatusFilter == 'disabled' && skill.enabled) return false;
668
653
  }
654
+ if (_installedSourceFilter != 'all' && skill.source != _installedSourceFilter) return false;
669
655
  return true;
670
656
  }).toList();
671
657
 
672
658
  final statusFilters = <String>['all', 'active', 'draft', 'disabled'];
673
- final sourceFilters = <String>[
674
- 'all',
675
- 'built-in',
676
- 'learned',
677
- 'user',
678
- 'store',
679
- ];
659
+ final sourceFilters = <String>['all', 'built-in', 'learned', 'user', 'store'];
680
660
 
681
661
  return Card(
682
662
  child: Column(
@@ -777,174 +757,157 @@ class _SkillsPanelState extends State<SkillsPanel>
777
757
  separatorBuilder: (_, __) => const SizedBox(height: 10),
778
758
  itemBuilder: (context, index) {
779
759
  final skill = filteredSkills[index];
780
- return LayoutBuilder(
781
- builder: (context, constraints) {
782
- final compact = constraints.maxWidth < 760;
783
- return Container(
784
- padding: const EdgeInsets.all(14),
785
- decoration: BoxDecoration(
786
- color: _bgSecondary,
787
- borderRadius: BorderRadius.circular(14),
788
- border: Border.all(color: _border),
789
- ),
790
- child: compact
791
- ? Column(
792
- crossAxisAlignment: CrossAxisAlignment.start,
793
- children: <Widget>[
794
- Row(
795
- children: <Widget>[
796
- Expanded(
797
- child: Text(
798
- skill.name,
799
- style: TextStyle(
800
- fontWeight: FontWeight.w700,
801
- ),
802
- ),
803
- ),
804
- Switch(
805
- value: skill.enabled,
806
- onChanged: (value) => controller
807
- .setSkillEnabled(skill.name, value),
808
- ),
809
- ],
810
- ),
811
- Text(
812
- skill.description.ifEmpty('No description'),
813
- style: TextStyle(color: _textSecondary),
814
- ),
815
- const SizedBox(height: 10),
816
- Wrap(
817
- spacing: 8,
818
- runSpacing: 8,
819
- children: <Widget>[
820
- _MetaPill(
821
- label: skill.category,
822
- icon: Icons.folder_outlined,
823
- ),
824
- _MetaPill(
825
- label: skill.source,
826
- icon: Icons.source_outlined,
827
- ),
828
- if (skill.draft)
829
- const _MetaPill(
830
- label: 'Draft',
831
- icon: Icons.edit_note_outlined,
832
- ),
833
- ],
834
- ),
835
- const SizedBox(height: 10),
836
- Row(
837
- children: <Widget>[
838
- const Spacer(),
839
- OutlinedButton(
840
- onPressed: () => _openSkillEditor(
841
- context,
842
- skill.name,
843
- ),
844
- child: Text('Open'),
845
- ),
846
- const SizedBox(width: 8),
847
- TextButton.icon(
848
- onPressed: () => _confirmDeleteSkill(
849
- context,
850
- skill.name,
851
- ),
852
- icon: Icon(Icons.delete_outline),
853
- style: TextButton.styleFrom(
854
- foregroundColor: _danger,
855
- ),
856
- label: Text('Delete'),
857
- ),
858
- ],
859
- ),
860
- ],
861
- )
862
- : Row(
863
- crossAxisAlignment: CrossAxisAlignment.start,
864
- children: <Widget>[
865
- Expanded(
866
- child: Column(
867
- crossAxisAlignment:
868
- CrossAxisAlignment.start,
869
- children: <Widget>[
870
- Text(
871
- skill.name,
872
- style: TextStyle(
873
- fontWeight: FontWeight.w700,
874
- ),
875
- ),
876
- const SizedBox(height: 6),
877
- Text(
878
- skill.description.ifEmpty(
879
- 'No description',
880
- ),
881
- style: TextStyle(
882
- color: _textSecondary,
883
- ),
884
- ),
885
- const SizedBox(height: 10),
886
- Wrap(
887
- spacing: 8,
888
- runSpacing: 8,
889
- children: <Widget>[
890
- _MetaPill(
891
- label: skill.category,
892
- icon: Icons.folder_outlined,
893
- ),
894
- _MetaPill(
895
- label: skill.source,
896
- icon: Icons.source_outlined,
897
- ),
898
- if (skill.draft)
899
- const _MetaPill(
900
- label: 'Draft',
901
- icon: Icons.edit_note_outlined,
902
- ),
903
- ],
904
- ),
905
- ],
760
+ return LayoutBuilder(
761
+ builder: (context, constraints) {
762
+ final compact = constraints.maxWidth < 760;
763
+ return Container(
764
+ padding: const EdgeInsets.all(14),
765
+ decoration: BoxDecoration(
766
+ color: _bgSecondary,
767
+ borderRadius: BorderRadius.circular(14),
768
+ border: Border.all(color: _border),
769
+ ),
770
+ child: compact
771
+ ? Column(
772
+ crossAxisAlignment: CrossAxisAlignment.start,
773
+ children: <Widget>[
774
+ Row(
775
+ children: <Widget>[
776
+ Expanded(
777
+ child: Text(
778
+ skill.name,
779
+ style: TextStyle(fontWeight: FontWeight.w700),
780
+ ),
781
+ ),
782
+ Switch(
783
+ value: skill.enabled,
784
+ onChanged: (value) => controller
785
+ .setSkillEnabled(skill.name, value),
786
+ ),
787
+ ],
788
+ ),
789
+ Text(
790
+ skill.description.ifEmpty('No description'),
791
+ style: TextStyle(color: _textSecondary),
792
+ ),
793
+ const SizedBox(height: 10),
794
+ Wrap(
795
+ spacing: 8,
796
+ runSpacing: 8,
797
+ children: <Widget>[
798
+ _MetaPill(
799
+ label: skill.category,
800
+ icon: Icons.folder_outlined,
801
+ ),
802
+ _MetaPill(
803
+ label: skill.source,
804
+ icon: Icons.source_outlined,
805
+ ),
806
+ if (skill.draft)
807
+ const _MetaPill(
808
+ label: 'Draft',
809
+ icon: Icons.edit_note_outlined,
810
+ ),
811
+ ],
812
+ ),
813
+ const SizedBox(height: 10),
814
+ Row(
815
+ children: <Widget>[
816
+ const Spacer(),
817
+ OutlinedButton(
818
+ onPressed: () =>
819
+ _openSkillEditor(context, skill.name),
820
+ child: Text('Open'),
821
+ ),
822
+ const SizedBox(width: 8),
823
+ TextButton.icon(
824
+ onPressed: () =>
825
+ _confirmDeleteSkill(context, skill.name),
826
+ icon: Icon(Icons.delete_outline),
827
+ style: TextButton.styleFrom(
828
+ foregroundColor: _danger,
829
+ ),
830
+ label: Text('Delete'),
831
+ ),
832
+ ],
833
+ ),
834
+ ],
835
+ )
836
+ : Row(
837
+ crossAxisAlignment: CrossAxisAlignment.start,
838
+ children: <Widget>[
839
+ Expanded(
840
+ child: Column(
841
+ crossAxisAlignment: CrossAxisAlignment.start,
842
+ children: <Widget>[
843
+ Text(
844
+ skill.name,
845
+ style: TextStyle(fontWeight: FontWeight.w700),
846
+ ),
847
+ const SizedBox(height: 6),
848
+ Text(
849
+ skill.description.ifEmpty('No description'),
850
+ style: TextStyle(color: _textSecondary),
851
+ ),
852
+ const SizedBox(height: 10),
853
+ Wrap(
854
+ spacing: 8,
855
+ runSpacing: 8,
856
+ children: <Widget>[
857
+ _MetaPill(
858
+ label: skill.category,
859
+ icon: Icons.folder_outlined,
906
860
  ),
907
- ),
908
- const SizedBox(width: 10),
909
- Column(
910
- children: <Widget>[
911
- Switch(
912
- value: skill.enabled,
913
- onChanged: (value) => controller
914
- .setSkillEnabled(skill.name, value),
915
- ),
916
- OutlinedButton(
917
- onPressed: () => _openSkillEditor(
918
- context,
919
- skill.name,
920
- ),
921
- child: Text('Open'),
922
- ),
923
- const SizedBox(height: 6),
924
- TextButton.icon(
925
- onPressed: () => _confirmDeleteSkill(
926
- context,
927
- skill.name,
928
- ),
929
- icon: Icon(Icons.delete_outline),
930
- style: TextButton.styleFrom(
931
- foregroundColor: _danger,
932
- ),
933
- label: Text('Delete'),
861
+ _MetaPill(
862
+ label: skill.source,
863
+ icon: Icons.source_outlined,
864
+ ),
865
+ if (skill.draft)
866
+ const _MetaPill(
867
+ label: 'Draft',
868
+ icon: Icons.edit_note_outlined,
934
869
  ),
935
- ],
936
- ),
937
- ],
870
+ ],
871
+ ),
872
+ ],
873
+ ),
874
+ ),
875
+ const SizedBox(width: 10),
876
+ Column(
877
+ children: <Widget>[
878
+ Switch(
879
+ value: skill.enabled,
880
+ onChanged: (value) => controller
881
+ .setSkillEnabled(skill.name, value),
938
882
  ),
939
- );
940
- },
941
- );
883
+ OutlinedButton(
884
+ onPressed: () =>
885
+ _openSkillEditor(context, skill.name),
886
+ child: Text('Open'),
887
+ ),
888
+ const SizedBox(height: 6),
889
+ TextButton.icon(
890
+ onPressed: () =>
891
+ _confirmDeleteSkill(context, skill.name),
892
+ icon: Icon(Icons.delete_outline),
893
+ style: TextButton.styleFrom(
894
+ foregroundColor: _danger,
895
+ ),
896
+ label: Text('Delete'),
897
+ ),
898
+ ],
899
+ ),
900
+ ],
901
+ ),
902
+ );
903
+ },
904
+ );
942
905
  },
943
906
  ),
944
907
  ),
945
- ],
946
- ),
947
- );
908
+ ],
909
+ ),
910
+ );
948
911
  }
949
912
 
950
913
  Widget _buildStoreTab(
@@ -1502,9 +1465,8 @@ class _MemoryPanelState extends State<MemoryPanel>
1502
1465
  );
1503
1466
  if (!mounted) return;
1504
1467
  _llmImportController.clear();
1505
- final warningText = result.warnings.isEmpty
1506
- ? ''
1507
- : ' ${result.warnings.join(' ')}';
1468
+ final warningText =
1469
+ result.warnings.isEmpty ? '' : ' ${result.warnings.join(' ')}';
1508
1470
  ScaffoldMessenger.of(context).showSnackBar(
1509
1471
  SnackBar(
1510
1472
  content: Text(
@@ -1532,7 +1494,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1532
1494
  : controller.memories;
1533
1495
  if (_entityFilter == null) return base;
1534
1496
  return base
1535
- .where((m) => m.entities.any((e) => e.name == _entityFilter))
1497
+ .where(
1498
+ (m) => m.entities.any((e) => e.name == _entityFilter),
1499
+ )
1536
1500
  .toList();
1537
1501
  }
1538
1502
 
@@ -1624,10 +1588,7 @@ class _MemoryPanelState extends State<MemoryPanel>
1624
1588
  });
1625
1589
  }
1626
1590
 
1627
- void _openRetrievalInspector(
1628
- BuildContext context,
1629
- NeoAgentController controller,
1630
- ) {
1591
+ void _openRetrievalInspector(BuildContext context, NeoAgentController controller) {
1631
1592
  Navigator.of(context).push(
1632
1593
  MaterialPageRoute(
1633
1594
  builder: (context) => RetrievalInspectorView(controller: controller),
@@ -1642,8 +1603,7 @@ class _MemoryPanelState extends State<MemoryPanel>
1642
1603
  final memoriesToShow = _visibleMemories;
1643
1604
  final selectedIds = _selectedVisibleMemoryIds.toSet();
1644
1605
  final selectedCount = selectedIds.length;
1645
- final allVisibleSelected =
1646
- memoriesToShow.isNotEmpty &&
1606
+ final allVisibleSelected = memoriesToShow.isNotEmpty &&
1647
1607
  memoriesToShow.every((m) => selectedIds.contains(m.id));
1648
1608
  final showingSearchResults = controller.memoryRecallResults.isNotEmpty;
1649
1609
  final compact = MediaQuery.sizeOf(context).width < 760;
@@ -1684,7 +1644,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1684
1644
  padding: const EdgeInsets.all(18),
1685
1645
  child: Row(
1686
1646
  children: <Widget>[
1687
- _MemoryConfidenceGauge(confidence: stats.averageConfidence),
1647
+ _MemoryConfidenceGauge(
1648
+ confidence: stats.averageConfidence,
1649
+ ),
1688
1650
  const SizedBox(width: 18),
1689
1651
  Expanded(
1690
1652
  child: Wrap(
@@ -1743,7 +1705,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1743
1705
  children: <Widget>[
1744
1706
  Row(
1745
1707
  children: <Widget>[
1746
- Expanded(child: const _SectionTitle('Knowledge Graph')),
1708
+ Expanded(
1709
+ child: const _SectionTitle('Knowledge Graph'),
1710
+ ),
1747
1711
  if (_entityFilter != null)
1748
1712
  TextButton.icon(
1749
1713
  onPressed: () =>
@@ -1756,7 +1720,10 @@ class _MemoryPanelState extends State<MemoryPanel>
1756
1720
  const SizedBox(height: 4),
1757
1721
  Text(
1758
1722
  'Tap an entity to filter memories by it.',
1759
- style: TextStyle(color: _textSecondary, fontSize: 12),
1723
+ style: TextStyle(
1724
+ color: _textSecondary,
1725
+ fontSize: 12,
1726
+ ),
1760
1727
  ),
1761
1728
  const SizedBox(height: 14),
1762
1729
  SizedBox(
@@ -1890,16 +1857,15 @@ class _MemoryPanelState extends State<MemoryPanel>
1890
1857
  Wrap(
1891
1858
  spacing: 8,
1892
1859
  runSpacing: 8,
1893
- crossAxisAlignment: WrapCrossAlignment.center,
1860
+ crossAxisAlignment:
1861
+ WrapCrossAlignment.center,
1894
1862
  children: <Widget>[
1895
1863
  OutlinedButton.icon(
1896
- onPressed:
1897
- allVisibleSelected ||
1864
+ onPressed: allVisibleSelected ||
1898
1865
  _bulkActionInFlight
1899
1866
  ? null
1900
1867
  : () => _selectAllVisibleMemories(
1901
- memoriesToShow,
1902
- ),
1868
+ memoriesToShow),
1903
1869
  icon: Icon(
1904
1870
  Icons.done_all_outlined,
1905
1871
  size: 16,
@@ -1937,7 +1903,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1937
1903
  Icons.archive_outlined,
1938
1904
  size: 16,
1939
1905
  ),
1940
- label: Text('Archive ($selectedCount)'),
1906
+ label: Text(
1907
+ 'Archive ($selectedCount)',
1908
+ ),
1941
1909
  ),
1942
1910
  OutlinedButton.icon(
1943
1911
  onPressed: _bulkActionInFlight
@@ -1955,7 +1923,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1955
1923
  Icons.delete_sweep_outlined,
1956
1924
  size: 16,
1957
1925
  ),
1958
- label: Text('Delete ($selectedCount)'),
1926
+ label: Text(
1927
+ 'Delete ($selectedCount)',
1928
+ ),
1959
1929
  ),
1960
1930
  ],
1961
1931
  ],
@@ -1971,9 +1941,8 @@ class _MemoryPanelState extends State<MemoryPanel>
1971
1941
  )
1972
1942
  else
1973
1943
  ...memoriesToShow.map((memory) {
1974
- final isSelected = selectedIds.contains(
1975
- memory.id,
1976
- );
1944
+ final isSelected =
1945
+ selectedIds.contains(memory.id);
1977
1946
  return _MemoryRow(
1978
1947
  memory: memory,
1979
1948
  isSelected: isSelected,
@@ -1981,7 +1950,8 @@ class _MemoryPanelState extends State<MemoryPanel>
1981
1950
  memory.id,
1982
1951
  !isSelected,
1983
1952
  ),
1984
- onCheck: (value) => _toggleMemorySelection(
1953
+ onCheck: (value) =>
1954
+ _toggleMemorySelection(
1985
1955
  memory.id,
1986
1956
  value ?? false,
1987
1957
  ),
@@ -1994,9 +1964,9 @@ class _MemoryPanelState extends State<MemoryPanel>
1994
1964
  'This memory will be removed permanently.',
1995
1965
  onConfirm: () =>
1996
1966
  _deleteSingleMemory(
1997
- controller,
1998
- memory.id,
1999
- ),
1967
+ controller,
1968
+ memory.id,
1969
+ ),
2000
1970
  ),
2001
1971
  );
2002
1972
  }),
@@ -2015,7 +1985,9 @@ class _MemoryPanelState extends State<MemoryPanel>
2015
1985
  Expanded(
2016
1986
  child: Text(
2017
1987
  'Key-value pairs that persist across conversations.',
2018
- style: TextStyle(color: _textSecondary),
1988
+ style: TextStyle(
1989
+ color: _textSecondary,
1990
+ ),
2019
1991
  ),
2020
1992
  ),
2021
1993
  TextButton.icon(
@@ -2029,75 +2001,75 @@ class _MemoryPanelState extends State<MemoryPanel>
2029
2001
  ],
2030
2002
  ),
2031
2003
  const SizedBox(height: 10),
2032
- if (controller.memoryOverview.coreEntries.isEmpty)
2004
+ if (controller
2005
+ .memoryOverview.coreEntries.isEmpty)
2033
2006
  Text(
2034
2007
  'No core memory entries yet.',
2035
2008
  style: TextStyle(color: _textSecondary),
2036
2009
  )
2037
2010
  else
2038
- ...controller.memoryOverview.coreEntries.entries
2011
+ ...controller.memoryOverview.coreEntries
2012
+ .entries
2039
2013
  .map((entry) {
2040
- return Container(
2041
- width: double.infinity,
2042
- margin: const EdgeInsets.only(
2043
- bottom: 10,
2014
+ return Container(
2015
+ width: double.infinity,
2016
+ margin: const EdgeInsets.only(bottom: 10),
2017
+ padding: const EdgeInsets.all(12),
2018
+ decoration: BoxDecoration(
2019
+ color: _bgSecondary,
2020
+ borderRadius:
2021
+ BorderRadius.circular(12),
2022
+ border: Border.all(color: _border),
2023
+ ),
2024
+ child: Row(
2025
+ crossAxisAlignment:
2026
+ CrossAxisAlignment.start,
2027
+ children: <Widget>[
2028
+ Expanded(
2029
+ child: Column(
2030
+ crossAxisAlignment:
2031
+ CrossAxisAlignment.start,
2032
+ children: <Widget>[
2033
+ Text(
2034
+ entry.key,
2035
+ style: TextStyle(
2036
+ fontWeight: FontWeight.w700,
2037
+ ),
2038
+ ),
2039
+ const SizedBox(height: 6),
2040
+ Text(
2041
+ entry.value.toString(),
2042
+ ),
2043
+ ],
2044
+ ),
2044
2045
  ),
2045
- padding: const EdgeInsets.all(12),
2046
- decoration: BoxDecoration(
2047
- color: _bgSecondary,
2048
- borderRadius: BorderRadius.circular(
2049
- 12,
2046
+ IconButton(
2047
+ onPressed: () =>
2048
+ _openCoreMemoryEditor(
2049
+ context,
2050
+ controller,
2051
+ keyValue: entry,
2050
2052
  ),
2051
- border: Border.all(color: _border),
2053
+ icon: Icon(Icons.edit_outlined),
2052
2054
  ),
2053
- child: Row(
2054
- crossAxisAlignment:
2055
- CrossAxisAlignment.start,
2056
- children: <Widget>[
2057
- Expanded(
2058
- child: Column(
2059
- crossAxisAlignment:
2060
- CrossAxisAlignment.start,
2061
- children: <Widget>[
2062
- Text(
2063
- entry.key,
2064
- style: TextStyle(
2065
- fontWeight:
2066
- FontWeight.w700,
2067
- ),
2068
- ),
2069
- const SizedBox(height: 6),
2070
- Text(entry.value.toString()),
2071
- ],
2072
- ),
2073
- ),
2074
- IconButton(
2075
- onPressed: () =>
2076
- _openCoreMemoryEditor(
2077
- context,
2078
- controller,
2079
- keyValue: entry,
2080
- ),
2081
- icon: Icon(Icons.edit_outlined),
2082
- ),
2083
- IconButton(
2084
- onPressed: () => _confirmDelete(
2085
- context,
2086
- title:
2087
- 'Delete core memory entry?',
2088
- message:
2089
- 'Remove "${entry.key}" from core memory.',
2090
- onConfirm: () =>
2091
- controller.deleteCoreMemory(
2092
- entry.key,
2093
- ),
2094
- ),
2095
- icon: Icon(Icons.delete_outline),
2055
+ IconButton(
2056
+ onPressed: () => _confirmDelete(
2057
+ context,
2058
+ title:
2059
+ 'Delete core memory entry?',
2060
+ message:
2061
+ 'Remove "${entry.key}" from core memory.',
2062
+ onConfirm: () => controller
2063
+ .deleteCoreMemory(
2064
+ entry.key,
2096
2065
  ),
2097
- ],
2066
+ ),
2067
+ icon: Icon(Icons.delete_outline),
2098
2068
  ),
2099
- );
2100
- }),
2069
+ ],
2070
+ ),
2071
+ );
2072
+ }),
2101
2073
  ],
2102
2074
  ),
2103
2075
  ),
@@ -2120,8 +2092,11 @@ class _MemoryPanelState extends State<MemoryPanel>
2120
2092
  FilledButton.icon(
2121
2093
  onPressed: _llmPromptLoading
2122
2094
  ? null
2123
- : () => _loadLlmPrompt(controller),
2124
- icon: Icon(Icons.auto_awesome_outlined),
2095
+ : () =>
2096
+ _loadLlmPrompt(controller),
2097
+ icon: Icon(
2098
+ Icons.auto_awesome_outlined,
2099
+ ),
2125
2100
  label: Text(
2126
2101
  _llmPromptLoading
2127
2102
  ? 'Generating...'
@@ -2130,9 +2105,11 @@ class _MemoryPanelState extends State<MemoryPanel>
2130
2105
  ),
2131
2106
  OutlinedButton.icon(
2132
2107
  onPressed:
2133
- _llmPromptController.text.trim().isEmpty
2134
- ? null
2135
- : _copyLlmPrompt,
2108
+ _llmPromptController.text
2109
+ .trim()
2110
+ .isEmpty
2111
+ ? null
2112
+ : _copyLlmPrompt,
2136
2113
  icon: Icon(Icons.copy_all_outlined),
2137
2114
  label: Text('Copy Prompt'),
2138
2115
  ),
@@ -2145,7 +2122,8 @@ class _MemoryPanelState extends State<MemoryPanel>
2145
2122
  maxLines: 8,
2146
2123
  readOnly: true,
2147
2124
  decoration: const InputDecoration(
2148
- labelText: 'Prompt to paste into another AI',
2125
+ labelText:
2126
+ 'Prompt to paste into another AI',
2149
2127
  ),
2150
2128
  ),
2151
2129
  const SizedBox(height: 16),
@@ -2155,7 +2133,8 @@ class _MemoryPanelState extends State<MemoryPanel>
2155
2133
  onChanged: _llmImporting
2156
2134
  ? null
2157
2135
  : (value) => setState(
2158
- () => _llmApplyBehaviorNotes = value,
2136
+ () =>
2137
+ _llmApplyBehaviorNotes = value,
2159
2138
  ),
2160
2139
  title: Text('Apply behavior notes'),
2161
2140
  subtitle: Text(
@@ -2168,7 +2147,8 @@ class _MemoryPanelState extends State<MemoryPanel>
2168
2147
  onChanged: _llmImporting
2169
2148
  ? null
2170
2149
  : (value) => setState(
2171
- () => _llmApplyCoreMemory = value,
2150
+ () =>
2151
+ _llmApplyCoreMemory = value,
2172
2152
  ),
2173
2153
  title: Text('Apply core memory'),
2174
2154
  subtitle: Text(
@@ -2188,10 +2168,13 @@ class _MemoryPanelState extends State<MemoryPanel>
2188
2168
  FilledButton.icon(
2189
2169
  onPressed: _llmImporting
2190
2170
  ? null
2191
- : () => _importLlmMemories(controller),
2171
+ : () =>
2172
+ _importLlmMemories(controller),
2192
2173
  icon: Icon(Icons.file_download_outlined),
2193
2174
  label: Text(
2194
- _llmImporting ? 'Importing...' : 'Import',
2175
+ _llmImporting
2176
+ ? 'Importing...'
2177
+ : 'Import',
2195
2178
  ),
2196
2179
  ),
2197
2180
  ],
@@ -2470,10 +2453,9 @@ class _MemoryConfidenceGaugeState extends State<_MemoryConfidenceGauge>
2470
2453
  vsync: this,
2471
2454
  duration: const Duration(milliseconds: 1200),
2472
2455
  );
2473
- _progress = Tween<double>(
2474
- begin: 0,
2475
- end: widget.confidence,
2476
- ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic));
2456
+ _progress = Tween<double>(begin: 0, end: widget.confidence).animate(
2457
+ CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
2458
+ );
2477
2459
  _controller.forward();
2478
2460
  }
2479
2461
 
@@ -2481,10 +2463,12 @@ class _MemoryConfidenceGaugeState extends State<_MemoryConfidenceGauge>
2481
2463
  void didUpdateWidget(_MemoryConfidenceGauge oldWidget) {
2482
2464
  super.didUpdateWidget(oldWidget);
2483
2465
  if (oldWidget.confidence != widget.confidence) {
2484
- _progress = Tween<double>(begin: _progress.value, end: widget.confidence)
2485
- .animate(
2486
- CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
2487
- );
2466
+ _progress = Tween<double>(
2467
+ begin: _progress.value,
2468
+ end: widget.confidence,
2469
+ ).animate(
2470
+ CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
2471
+ );
2488
2472
  _controller
2489
2473
  ..reset()
2490
2474
  ..forward();
@@ -2674,7 +2658,10 @@ class _MemoryRow extends StatelessWidget {
2674
2658
  if (onDelete != null)
2675
2659
  IconButton(
2676
2660
  onPressed: onDelete,
2677
- icon: Icon(Icons.delete_outline, size: 18),
2661
+ icon: Icon(
2662
+ Icons.delete_outline,
2663
+ size: 18,
2664
+ ),
2678
2665
  ),
2679
2666
  ],
2680
2667
  ),
@@ -2699,7 +2686,10 @@ class _MemoryRow extends StatelessWidget {
2699
2686
  const SizedBox(height: 6),
2700
2687
  Text(
2701
2688
  memory.createdAtLabel,
2702
- style: TextStyle(fontSize: 11, color: _textMuted),
2689
+ style: TextStyle(
2690
+ fontSize: 11,
2691
+ color: _textMuted,
2692
+ ),
2703
2693
  ),
2704
2694
  ],
2705
2695
  ),
@@ -2788,31 +2778,27 @@ class _EntityGraphViewState extends State<_EntityGraphView>
2788
2778
  for (int i = 0; i < entities.length; i++) {
2789
2779
  final entity = entities[i];
2790
2780
  final sizeFactor = 0.4 + 0.6 * (entity.mentionCount / maxMention);
2791
- _nodes.add(
2792
- _GraphNode(
2793
- id: entity.name,
2794
- label: entity.name,
2795
- radius: 18 + 20 * sizeFactor,
2796
- color: _kindColors[entity.kind] ?? _kindColors['concept']!,
2797
- kind: entity.kind,
2798
- isReflection: false,
2799
- offsetPhase: i * 0.7,
2800
- ),
2801
- );
2781
+ _nodes.add(_GraphNode(
2782
+ id: entity.name,
2783
+ label: entity.name,
2784
+ radius: 18 + 20 * sizeFactor,
2785
+ color: _kindColors[entity.kind] ?? _kindColors['concept']!,
2786
+ kind: entity.kind,
2787
+ isReflection: false,
2788
+ offsetPhase: i * 0.7,
2789
+ ));
2802
2790
  }
2803
2791
 
2804
2792
  for (int i = 0; i < views.length && i < 6; i++) {
2805
- _nodes.add(
2806
- _GraphNode(
2807
- id: 'kv_${views[i].title}',
2808
- label: views[i].title,
2809
- radius: 14,
2810
- color: const Color(0xFF8B7EC8),
2811
- kind: views[i].viewType,
2812
- isReflection: true,
2813
- offsetPhase: (entities.length + i) * 0.9,
2814
- ),
2815
- );
2793
+ _nodes.add(_GraphNode(
2794
+ id: 'kv_${views[i].title}',
2795
+ label: views[i].title,
2796
+ radius: 14,
2797
+ color: const Color(0xFF8B7EC8),
2798
+ kind: views[i].viewType,
2799
+ isReflection: true,
2800
+ offsetPhase: (entities.length + i) * 0.9,
2801
+ ));
2816
2802
  }
2817
2803
 
2818
2804
  _layoutDone = false;
@@ -2953,12 +2939,10 @@ class _EntityGraphPainter extends CustomPainter {
2953
2939
  void paint(Canvas canvas, Size size) {
2954
2940
  if (nodes.isEmpty) return;
2955
2941
 
2956
- final entityNodes = nodes
2957
- .where((n) => !n.isReflection)
2958
- .toList(growable: false);
2959
- final reflectionNodes = nodes
2960
- .where((n) => n.isReflection)
2961
- .toList(growable: false);
2942
+ final entityNodes =
2943
+ nodes.where((n) => !n.isReflection).toList(growable: false);
2944
+ final reflectionNodes =
2945
+ nodes.where((n) => n.isReflection).toList(growable: false);
2962
2946
 
2963
2947
  // Draw connections between entity nodes (subtle web)
2964
2948
  final linePaint = Paint()
@@ -2990,8 +2974,8 @@ class _EntityGraphPainter extends CustomPainter {
2990
2974
  var closest = entityNodes.first;
2991
2975
  var minDist = double.infinity;
2992
2976
  for (final en in entityNodes) {
2993
- final d =
2994
- (en.x - rn.x) * (en.x - rn.x) + (en.y - rn.y) * (en.y - rn.y);
2977
+ final d = (en.x - rn.x) * (en.x - rn.x) +
2978
+ (en.y - rn.y) * (en.y - rn.y);
2995
2979
  if (d < minDist) {
2996
2980
  minDist = d;
2997
2981
  closest = en;
@@ -3018,9 +3002,8 @@ class _EntityGraphPainter extends CustomPainter {
3018
3002
  // Glow
3019
3003
  if (isSelected || isHovered) {
3020
3004
  final glowPaint = Paint()
3021
- ..color = (isSelected ? accentColor : node.color).withValues(
3022
- alpha: 0.22,
3023
- )
3005
+ ..color = (isSelected ? accentColor : node.color)
3006
+ .withValues(alpha: 0.22)
3024
3007
  ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);
3025
3008
  canvas.drawCircle(Offset(cx, cy), r + 6, glowPaint);
3026
3009
  }
@@ -3040,8 +3023,8 @@ class _EntityGraphPainter extends CustomPainter {
3040
3023
  ..color = isSelected
3041
3024
  ? accentColor
3042
3025
  : (isHovered
3043
- ? node.color.withValues(alpha: 0.8)
3044
- : node.color.withValues(alpha: 0.35))
3026
+ ? node.color.withValues(alpha: 0.8)
3027
+ : node.color.withValues(alpha: 0.35))
3045
3028
  ..style = PaintingStyle.stroke
3046
3029
  ..strokeWidth = isSelected ? 2.5 : 1.5;
3047
3030
  canvas.drawCircle(Offset(cx, cy), r, borderPaint);
@@ -3058,7 +3041,10 @@ class _EntityGraphPainter extends CustomPainter {
3058
3041
  maxLines: 1,
3059
3042
  ellipsis: '…',
3060
3043
  )..layout(maxWidth: r * 3);
3061
- tp.paint(canvas, Offset(cx - tp.width / 2, cy + r + 5));
3044
+ tp.paint(
3045
+ canvas,
3046
+ Offset(cx - tp.width / 2, cy + r + 5),
3047
+ );
3062
3048
  }
3063
3049
  }
3064
3050
 
@@ -5039,15 +5025,6 @@ const List<_TaskTriggerOption> _taskTriggerOptions = <_TaskTriggerOption>[
5039
5025
  providerKey: 'google_workspace',
5040
5026
  appKey: 'gmail',
5041
5027
  ),
5042
- _TaskTriggerOption(
5043
- type: 'neomail_email_received',
5044
- section: 'Email',
5045
- label: 'NeoMail Email Received',
5046
- description: 'Run when a matching message is synced into NeoMail.',
5047
- icon: Icons.mark_email_unread_rounded,
5048
- providerKey: 'neomail',
5049
- appKey: 'mailbox',
5050
- ),
5051
5028
  _TaskTriggerOption(
5052
5029
  type: 'outlook_email_received',
5053
5030
  section: 'Email',
@@ -5110,6 +5087,7 @@ _TaskTriggerOption _taskTriggerOptionForType(String type) {
5110
5087
  );
5111
5088
  }
5112
5089
 
5090
+
5113
5091
  Future<String?> _pickTaskTriggerType(
5114
5092
  BuildContext context,
5115
5093
  String selectedType,
@@ -6289,7 +6267,9 @@ class _TasksPanelState extends State<TasksPanel> {
6289
6267
  final selectedConnectionId = ValueNotifier<int?>(
6290
6268
  task?.triggerConfig['connectionId'] is int
6291
6269
  ? task!.triggerConfig['connectionId'] as int
6292
- : int.tryParse(task?.triggerConfig['connectionId']?.toString() ?? ''),
6270
+ : int.tryParse(
6271
+ task?.triggerConfig['connectionId']?.toString() ?? '',
6272
+ ),
6293
6273
  );
6294
6274
  final selectedDeliveryTarget = ValueNotifier<TaskDeliveryTarget?>(
6295
6275
  _taskDeliveryTargetFromTask(task),
@@ -6312,8 +6292,6 @@ class _TasksPanelState extends State<TasksPanel> {
6312
6292
  );
6313
6293
  final channelController = TextEditingController(
6314
6294
  text:
6315
- task?.triggerConfig['folder']?.toString() ??
6316
- task?.triggerConfig['folderId']?.toString() ??
6317
6295
  task?.triggerConfig['channel']?.toString() ??
6318
6296
  task?.triggerConfig['chatId']?.toString() ??
6319
6297
  '',
@@ -6534,8 +6512,6 @@ class _TasksPanelState extends State<TasksPanel> {
6534
6512
  ],
6535
6513
  if (selectedTriggerType ==
6536
6514
  'gmail_message_received' ||
6537
- selectedTriggerType ==
6538
- 'neomail_email_received' ||
6539
6515
  selectedTriggerType ==
6540
6516
  'outlook_email_received') ...<Widget>[
6541
6517
  TextField(
@@ -6553,18 +6529,6 @@ class _TasksPanelState extends State<TasksPanel> {
6553
6529
  setLocalState(() => unreadOnly = value),
6554
6530
  ),
6555
6531
  ],
6556
- if (selectedTriggerType ==
6557
- 'neomail_email_received') ...<Widget>[
6558
- TextField(
6559
- controller: channelController,
6560
- decoration: const InputDecoration(
6561
- labelText: 'Folder (optional)',
6562
- helperText:
6563
- 'Example: INBOX, archive, or a custom folder path.',
6564
- ),
6565
- ),
6566
- const SizedBox(height: 12),
6567
- ],
6568
6532
  if (selectedTriggerType ==
6569
6533
  'outlook_email_received') ...<Widget>[
6570
6534
  TextField(
@@ -6918,17 +6882,11 @@ class _TasksPanelState extends State<TasksPanel> {
6918
6882
  triggerConfig['eventTypes'] = eventTypes;
6919
6883
  }
6920
6884
  if (selectedTriggerType == 'gmail_message_received' ||
6921
- selectedTriggerType == 'neomail_email_received' ||
6922
6885
  selectedTriggerType == 'outlook_email_received') {
6923
6886
  if (queryController.text.trim().isNotEmpty) {
6924
6887
  triggerConfig['query'] = queryController.text.trim();
6925
6888
  }
6926
6889
  triggerConfig['unreadOnly'] = unreadOnly;
6927
- if (selectedTriggerType == 'neomail_email_received' &&
6928
- channelController.text.trim().isNotEmpty) {
6929
- triggerConfig['folder'] = channelController.text
6930
- .trim();
6931
- }
6932
6890
  if (selectedTriggerType == 'outlook_email_received' &&
6933
6891
  channelController.text.trim().isNotEmpty) {
6934
6892
  triggerConfig['folderId'] = channelController.text