neoagent 2.4.4-beta.0 → 2.4.4-beta.4

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 (47) hide show
  1. package/README.md +5 -3
  2. package/docs/capabilities.md +16 -7
  3. package/docs/index.md +1 -0
  4. package/docs/security-boundaries.md +122 -0
  5. package/docs/supermemory-memory-review.md +852 -0
  6. package/flutter_app/lib/features/memory/views/retrieval_inspector_view.dart +128 -0
  7. package/flutter_app/lib/main.dart +3 -0
  8. package/flutter_app/lib/main_app_shell.dart +22 -0
  9. package/flutter_app/lib/main_controller.dart +36 -1
  10. package/flutter_app/lib/main_operations.dart +13 -0
  11. package/flutter_app/lib/main_security.dart +971 -0
  12. package/flutter_app/lib/main_settings.dart +61 -0
  13. package/flutter_app/lib/src/backend_client.dart +60 -3
  14. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
  15. package/flutter_app/pubspec.lock +32 -0
  16. package/flutter_app/pubspec.yaml +1 -0
  17. package/lib/schema_migrations.js +237 -0
  18. package/package.json +4 -2
  19. package/server/db/database.js +3 -0
  20. package/server/http/routes.js +2 -1
  21. package/server/public/.last_build_id +1 -1
  22. package/server/public/assets/NOTICES +86 -0
  23. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  24. package/server/public/flutter_bootstrap.js +1 -1
  25. package/server/public/main.dart.js +80911 -79117
  26. package/server/routes/memory.js +39 -2
  27. package/server/routes/security.js +112 -0
  28. package/server/services/ai/engine.js +267 -10
  29. package/server/services/ai/systemPrompt.js +13 -2
  30. package/server/services/cli/shell_worker.js +135 -0
  31. package/server/services/cli/shell_worker_pool.js +125 -0
  32. package/server/services/manager.js +20 -1
  33. package/server/services/memory/consolidation.js +111 -0
  34. package/server/services/memory/embedding_index.js +175 -0
  35. package/server/services/memory/embeddings.js +22 -2
  36. package/server/services/memory/evaluation.js +187 -0
  37. package/server/services/memory/ingestion_chunking.js +191 -0
  38. package/server/services/memory/ingestion_documents.js +96 -26
  39. package/server/services/memory/intelligence.js +3 -1
  40. package/server/services/memory/manager.js +855 -40
  41. package/server/services/memory/policy.js +0 -40
  42. package/server/services/memory/retrieval_reasoning.js +191 -0
  43. package/server/services/runtime/manager.js +7 -0
  44. package/server/services/security/approval_gate_service.js +93 -0
  45. package/server/services/security/tool_categories.js +105 -0
  46. package/server/services/security/tool_policy_service.js +92 -0
  47. package/server/services/security/tool_security_hook.js +77 -0
@@ -0,0 +1,128 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../../../main.dart'; // To access NeoAgentController
3
+
4
+ class RetrievalInspectorView extends StatefulWidget {
5
+ final NeoAgentController controller;
6
+
7
+ const RetrievalInspectorView({super.key, required this.controller});
8
+
9
+ @override
10
+ State<RetrievalInspectorView> createState() => _RetrievalInspectorViewState();
11
+ }
12
+
13
+ class _RetrievalInspectorViewState extends State<RetrievalInspectorView> {
14
+ final _queryController = TextEditingController();
15
+ bool _isLoading = false;
16
+ Map<String, dynamic>? _results;
17
+ String? _error;
18
+
19
+ @override
20
+ void dispose() {
21
+ _queryController.dispose();
22
+ super.dispose();
23
+ }
24
+
25
+ Future<void> _inspect() async {
26
+ final query = _queryController.text.trim();
27
+ if (query.isEmpty) return;
28
+
29
+ setState(() {
30
+ _isLoading = true;
31
+ _error = null;
32
+ });
33
+
34
+ try {
35
+ final res = await widget.controller.inspectMemory(query);
36
+ if (!mounted) return;
37
+ setState(() {
38
+ _results = res;
39
+ });
40
+ } catch (e) {
41
+ if (!mounted) return;
42
+ setState(() {
43
+ _error = e.toString();
44
+ });
45
+ } finally {
46
+ if (mounted) {
47
+ setState(() {
48
+ _isLoading = false;
49
+ });
50
+ }
51
+ }
52
+ }
53
+
54
+ @override
55
+ Widget build(BuildContext context) {
56
+ return Scaffold(
57
+ appBar: AppBar(
58
+ title: const Text('Retrieval Inspector'),
59
+ ),
60
+ body: Padding(
61
+ padding: const EdgeInsets.all(16.0),
62
+ child: Column(
63
+ children: [
64
+ Row(
65
+ children: [
66
+ Expanded(
67
+ child: TextField(
68
+ controller: _queryController,
69
+ decoration: const InputDecoration(
70
+ labelText: 'Query',
71
+ border: OutlineInputBorder(),
72
+ ),
73
+ onSubmitted: (_) => _inspect(),
74
+ ),
75
+ ),
76
+ const SizedBox(width: 8),
77
+ ElevatedButton(
78
+ onPressed: _isLoading ? null : _inspect,
79
+ child: const Text('Inspect'),
80
+ ),
81
+ ],
82
+ ),
83
+ const SizedBox(height: 16),
84
+ if (_isLoading)
85
+ const CircularProgressIndicator()
86
+ else if (_error != null)
87
+ Text('Error: $_error', style: const TextStyle(color: Colors.red))
88
+ else if (_results != null)
89
+ Expanded(
90
+ child: ListView.builder(
91
+ itemCount: (_results!['results'] as List?)?.length ?? 0,
92
+ itemBuilder: (context, index) {
93
+ final item = _results!['results'][index];
94
+ final breakdown = item['scoreBreakdown'] ?? {};
95
+ return Card(
96
+ margin: const EdgeInsets.only(bottom: 8),
97
+ child: Padding(
98
+ padding: const EdgeInsets.all(12.0),
99
+ child: Column(
100
+ crossAxisAlignment: CrossAxisAlignment.start,
101
+ children: [
102
+ Text(
103
+ 'Score: ${item['score']?.toStringAsFixed(3) ?? '?'}',
104
+ style: const TextStyle(fontWeight: FontWeight.bold),
105
+ ),
106
+ Text('Content: ${item['content']}'),
107
+ const SizedBox(height: 8),
108
+ const Text('Breakdown:', style: TextStyle(fontWeight: FontWeight.bold)),
109
+ Text('Semantic: ${breakdown['semantic']?.toStringAsFixed(3)}'),
110
+ Text('Lexical: ${breakdown['lexical']?.toStringAsFixed(3)}'),
111
+ Text('Full Text (FTS): ${breakdown['fullText']?.toStringAsFixed(3)}'),
112
+ Text('Entity: ${breakdown['entity']?.toStringAsFixed(3)}'),
113
+ Text('Relation: ${breakdown['relation']?.toStringAsFixed(3)}'),
114
+ Text('Candidate Count: ${breakdown['candidateCount']}'),
115
+ Text('Vector Rank: ${breakdown['vectorCandidateRank']}'),
116
+ ],
117
+ ),
118
+ ),
119
+ );
120
+ },
121
+ ),
122
+ ),
123
+ ],
124
+ ),
125
+ ),
126
+ );
127
+ }
128
+ }
@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
11
11
  import 'package:flutter/gestures.dart';
12
12
  import 'package:flutter/material.dart';
13
13
  import 'package:flutter/services.dart';
14
+ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
14
15
  import 'package:flutter_markdown/flutter_markdown.dart';
15
16
  import 'package:file_picker/file_picker.dart';
16
17
  import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@@ -48,6 +49,7 @@ import 'src/android_auto_bridge.dart';
48
49
  import 'features/location/location_service.dart';
49
50
  import 'features/notifications/notification_interceptor.dart';
50
51
  import 'features/onboarding/onboarding_shell.dart';
52
+ import 'features/memory/views/retrieval_inspector_view.dart';
51
53
 
52
54
  part 'main_spacing.dart';
53
55
  part 'main_theme.dart';
@@ -65,6 +67,7 @@ part 'main_recordings.dart';
65
67
  part 'main_chat.dart';
66
68
  part 'main_account_settings.dart';
67
69
  part 'main_settings.dart';
70
+ part 'main_security.dart';
68
71
  part 'main_model_picker.dart';
69
72
  part 'main_operations.dart';
70
73
  part 'main_admin.dart';
@@ -1086,6 +1086,7 @@ class HomeView extends StatefulWidget {
1086
1086
 
1087
1087
  class _HomeViewState extends State<HomeView> {
1088
1088
  bool _blockedDialogOpen = false;
1089
+ bool _approvalSheetOpen = false;
1089
1090
  SidebarGroup? _expandedSidebarGroup;
1090
1091
  AppSection? _lastSelectedSection;
1091
1092
  final GlobalKey _devicesPanelKey = GlobalKey();
@@ -1198,6 +1199,27 @@ class _HomeViewState extends State<HomeView> {
1198
1199
  _showBlockedSenderDialog(pendingBlockedSender);
1199
1200
  });
1200
1201
  }
1202
+ final pendingApproval = controller.pendingApproval;
1203
+ if (!_approvalSheetOpen && pendingApproval != null) {
1204
+ _approvalSheetOpen = true;
1205
+ WidgetsBinding.instance.addPostFrameCallback((_) {
1206
+ if (!mounted) return;
1207
+ showModalBottomSheet<void>(
1208
+ context: context,
1209
+ isDismissible: false,
1210
+ enableDrag: false,
1211
+ shape: const RoundedRectangleBorder(
1212
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
1213
+ ),
1214
+ builder: (_) => ToolApprovalSheet(
1215
+ request: pendingApproval,
1216
+ controller: controller,
1217
+ ),
1218
+ ).whenComplete(() {
1219
+ if (mounted) setState(() => _approvalSheetOpen = false);
1220
+ });
1221
+ });
1222
+ }
1201
1223
 
1202
1224
  final wide = MediaQuery.sizeOf(context).width >= 1080;
1203
1225
 
@@ -156,6 +156,7 @@ class NeoAgentController extends ChangeNotifier {
156
156
  Map<String, MessagingAccessCatalog> messagingAccessCatalogs =
157
157
  const <String, MessagingAccessCatalog>{};
158
158
  MessagingQrState? pendingMessagingQr;
159
+ ToolApprovalRequest? pendingApproval;
159
160
  final List<BlockedSenderNotice> _blockedSenderQueue = <BlockedSenderNotice>[];
160
161
  List<SkillItem> skills = const <SkillItem>[];
161
162
  List<StoreSkillItem> storeSkills = const <StoreSkillItem>[];
@@ -359,6 +360,13 @@ class NeoAgentController extends ChangeNotifier {
359
360
 
360
361
  String? get sessionCookie => _backendClient.sessionCookie;
361
362
 
363
+ BackendClient get backendClient => _backendClient;
364
+
365
+ void clearPendingApproval() {
366
+ pendingApproval = null;
367
+ notifyListeners();
368
+ }
369
+
362
370
  bool get desktopFloatingToolbarPopupRequested =>
363
371
  _desktopFloatingToolbarPopupRequested;
364
372
 
@@ -5618,7 +5626,7 @@ class NeoAgentController extends ChangeNotifier {
5618
5626
  }
5619
5627
 
5620
5628
  Future<void> searchMemories(String query) async {
5621
- memoryRecallResults = (await _backendClient.recallMemories(
5629
+ memoryRecallResults = (await _backendClient.recallMemory(
5622
5630
  backendUrl,
5623
5631
  query,
5624
5632
  agentId: _scopedAgentId,
@@ -5631,6 +5639,14 @@ class NeoAgentController extends ChangeNotifier {
5631
5639
  notifyListeners();
5632
5640
  }
5633
5641
 
5642
+ Future<Map<String, dynamic>> inspectMemory(String query) async {
5643
+ return _backendClient.inspectMemory(
5644
+ backendUrl,
5645
+ query,
5646
+ agentId: _scopedAgentId,
5647
+ );
5648
+ }
5649
+
5634
5650
  Future<void> updateAssistantBehaviorNotes(String content) async {
5635
5651
  await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5636
5652
  'assistant_behavior_notes': content,
@@ -7146,6 +7162,25 @@ class NeoAgentController extends ChangeNotifier {
7146
7162
  }
7147
7163
  notifyListeners();
7148
7164
  });
7165
+ socket.on('tool:approval_required', (dynamic data) {
7166
+ final payload = _jsonMap(data);
7167
+ final req = ToolApprovalRequest.fromJson(payload);
7168
+ pendingApproval = req;
7169
+ notifyListeners();
7170
+ // Show interactive push notification when app is backgrounded
7171
+ if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
7172
+ _SecurityNotificationService.showApprovalNotification(req);
7173
+ }
7174
+ });
7175
+ socket.on('tool:approval_resolved', (dynamic data) {
7176
+ final payload = _jsonMap(data);
7177
+ final resolvedId = payload['approvalId']?.toString() ?? '';
7178
+ if (pendingApproval?.approvalId == resolvedId) {
7179
+ _SecurityNotificationService.cancelApprovalNotification(resolvedId);
7180
+ pendingApproval = null;
7181
+ notifyListeners();
7182
+ }
7183
+ });
7149
7184
  socket.on('run:steer_queued', (dynamic data) {
7150
7185
  final payload = _jsonMap(data);
7151
7186
  final runId = payload['runId']?.toString() ?? '';
@@ -1463,6 +1463,14 @@ class _MemoryPanelState extends State<MemoryPanel>
1463
1463
  });
1464
1464
  }
1465
1465
 
1466
+ void _openRetrievalInspector(BuildContext context, NeoAgentController controller) {
1467
+ Navigator.of(context).push(
1468
+ MaterialPageRoute(
1469
+ builder: (context) => RetrievalInspectorView(controller: controller),
1470
+ ),
1471
+ );
1472
+ }
1473
+
1466
1474
  @override
1467
1475
  Widget build(BuildContext context) {
1468
1476
  final controller = widget.controller;
@@ -1485,6 +1493,11 @@ class _MemoryPanelState extends State<MemoryPanel>
1485
1493
  spacing: 10,
1486
1494
  runSpacing: 10,
1487
1495
  children: <Widget>[
1496
+ OutlinedButton.icon(
1497
+ onPressed: () => _openRetrievalInspector(context, controller),
1498
+ icon: Icon(Icons.bug_report_outlined),
1499
+ label: Text('Inspect'),
1500
+ ),
1488
1501
  OutlinedButton.icon(
1489
1502
  onPressed: () => _openBehaviorNotesEditor(context, controller),
1490
1503
  icon: Icon(Icons.edit_outlined),