neoagent 2.4.4-beta.0 → 2.4.4-beta.11

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 (66) hide show
  1. package/.env.example +17 -0
  2. package/README.md +9 -3
  3. package/docs/capabilities.md +16 -7
  4. package/docs/configuration.md +1 -0
  5. package/docs/getting-started.md +6 -0
  6. package/docs/index.md +1 -0
  7. package/docs/security-boundaries.md +122 -0
  8. package/docs/supermemory-memory-review.md +852 -0
  9. package/flutter_app/lib/features/memory/views/retrieval_inspector_view.dart +128 -0
  10. package/flutter_app/lib/main.dart +3 -0
  11. package/flutter_app/lib/main_account_settings.dart +79 -15
  12. package/flutter_app/lib/main_app_shell.dart +22 -0
  13. package/flutter_app/lib/main_chat.dart +155 -8
  14. package/flutter_app/lib/main_controller.dart +74 -5
  15. package/flutter_app/lib/main_devices.dart +9 -3
  16. package/flutter_app/lib/main_models.dart +32 -0
  17. package/flutter_app/lib/main_operations.dart +13 -0
  18. package/flutter_app/lib/main_security.dart +967 -0
  19. package/flutter_app/lib/main_settings.dart +56 -0
  20. package/flutter_app/lib/src/backend_client.dart +60 -3
  21. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
  22. package/flutter_app/pubspec.lock +32 -0
  23. package/flutter_app/pubspec.yaml +1 -0
  24. package/lib/install_helpers.js +1 -0
  25. package/lib/manager.js +63 -1
  26. package/lib/schema_migrations.js +262 -0
  27. package/package.json +4 -2
  28. package/server/admin/admin.js +151 -0
  29. package/server/admin/index.html +55 -3
  30. package/server/db/database.js +3 -0
  31. package/server/http/routes.js +2 -1
  32. package/server/public/.last_build_id +1 -1
  33. package/server/public/assets/NOTICES +86 -0
  34. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  35. package/server/public/flutter_bootstrap.js +1 -1
  36. package/server/public/main.dart.js +82243 -80308
  37. package/server/routes/account.js +2 -23
  38. package/server/routes/admin.js +18 -2
  39. package/server/routes/agents.js +5 -1
  40. package/server/routes/memory.js +41 -4
  41. package/server/routes/security.js +112 -0
  42. package/server/services/account/service_email_settings.js +167 -0
  43. package/server/services/ai/engine.js +269 -27
  44. package/server/services/ai/rate_limits.js +150 -0
  45. package/server/services/ai/systemPrompt.js +26 -3
  46. package/server/services/ai/tools.js +11 -8
  47. package/server/services/cli/shell_worker.js +135 -0
  48. package/server/services/cli/shell_worker_pool.js +125 -0
  49. package/server/services/integrations/google/gmail.js +7 -7
  50. package/server/services/manager.js +20 -1
  51. package/server/services/memory/consolidation.js +111 -0
  52. package/server/services/memory/embedding_index.js +175 -0
  53. package/server/services/memory/embeddings.js +22 -2
  54. package/server/services/memory/evaluation.js +187 -0
  55. package/server/services/memory/ingestion_chunking.js +191 -0
  56. package/server/services/memory/ingestion_documents.js +96 -26
  57. package/server/services/memory/intelligence.js +3 -1
  58. package/server/services/memory/manager.js +855 -43
  59. package/server/services/memory/policy.js +0 -40
  60. package/server/services/memory/retrieval_reasoning.js +191 -0
  61. package/server/services/runtime/manager.js +7 -0
  62. package/server/services/security/approval_gate_service.js +93 -0
  63. package/server/services/security/tool_categories.js +105 -0
  64. package/server/services/security/tool_policy_service.js +92 -0
  65. package/server/services/security/tool_security_hook.js +77 -0
  66. package/server/services/websocket.js +5 -1
@@ -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
 
@@ -2025,6 +2033,11 @@ class NeoAgentController extends ChangeNotifier {
2025
2033
  _backendClient.fetchTokenUsageSummary(backendUrl, agentId: agentId),
2026
2034
  const <String, dynamic>{},
2027
2035
  );
2036
+ final rateLimitFuture = _softRefreshLoad<Map<String, dynamic>>(
2037
+ 'rate_limits',
2038
+ _backendClient.fetchAccountUsage(backendUrl),
2039
+ const <String, dynamic>{},
2040
+ );
2028
2041
  final updateFuture = _backendClient
2029
2042
  .fetchUpdateStatus(backendUrl)
2030
2043
  .catchError((_) => const <String, dynamic>{});
@@ -2134,6 +2147,7 @@ class NeoAgentController extends ChangeNotifier {
2134
2147
  final runsResponse = await runsFuture;
2135
2148
  final versionResponse = await versionFuture;
2136
2149
  final tokenResponse = await tokenFuture;
2150
+ final rateLimitResponse = await rateLimitFuture;
2137
2151
  final updateResponse = await updateFuture;
2138
2152
  final messagingResponse = await messagingFuture;
2139
2153
  final messagingMessagesResponse = await messagingMessagesFuture;
@@ -2185,6 +2199,7 @@ class NeoAgentController extends ChangeNotifier {
2185
2199
  versionInfo = versionResponse;
2186
2200
  backendHealthStatus = healthResponse;
2187
2201
  tokenUsage = TokenUsageSnapshot.fromJson(tokenResponse);
2202
+ usageAndLimits = AccountUsageAndLimits.fromJson(rateLimitResponse);
2188
2203
  updateStatus = UpdateStatusSnapshot.fromJson(updateResponse);
2189
2204
  messagingStatuses = messagingResponse.map(
2190
2205
  (key, value) => MapEntry(
@@ -4632,20 +4647,24 @@ class NeoAgentController extends ChangeNotifier {
4632
4647
  }
4633
4648
  activeRun = null;
4634
4649
  await refreshRunsOnly();
4650
+ await refreshRateLimitUsage();
4635
4651
  } catch (error) {
4652
+ final friendlyError = _friendlyErrorMessage(error);
4636
4653
  chatMessages = <ChatEntry>[
4637
4654
  ...chatMessages,
4638
4655
  ChatEntry(
4639
4656
  id: '',
4640
4657
  role: 'assistant',
4641
- content:
4642
- 'I could not complete that request right now. Please try again in a moment.',
4658
+ content: friendlyError,
4643
4659
  platform: 'flutter',
4644
4660
  createdAt: DateTime.now(),
4645
4661
  ),
4646
4662
  ];
4647
4663
  activeRun = null;
4648
- errorMessage = _friendlyErrorMessage(error);
4664
+ errorMessage = friendlyError;
4665
+ if (error is BackendException && error.statusCode == 429) {
4666
+ await refreshRateLimitUsage();
4667
+ }
4649
4668
  } finally {
4650
4669
  if (_socket == null || !socketConnected) {
4651
4670
  isSendingMessage = false;
@@ -4778,6 +4797,16 @@ class NeoAgentController extends ChangeNotifier {
4778
4797
  }
4779
4798
  }
4780
4799
 
4800
+ Future<void> refreshRateLimitUsage() async {
4801
+ if (!isAuthenticated) return;
4802
+ try {
4803
+ usageAndLimits = AccountUsageAndLimits.fromJson(
4804
+ await _backendClient.fetchAccountUsage(backendUrl),
4805
+ );
4806
+ notifyListeners();
4807
+ } catch (_) {}
4808
+ }
4809
+
4781
4810
  Future<bool> updateAccountEmail({
4782
4811
  required String email,
4783
4812
  required String currentPassword,
@@ -5618,7 +5647,7 @@ class NeoAgentController extends ChangeNotifier {
5618
5647
  }
5619
5648
 
5620
5649
  Future<void> searchMemories(String query) async {
5621
- memoryRecallResults = (await _backendClient.recallMemories(
5650
+ memoryRecallResults = (await _backendClient.recallMemory(
5622
5651
  backendUrl,
5623
5652
  query,
5624
5653
  agentId: _scopedAgentId,
@@ -5631,6 +5660,14 @@ class NeoAgentController extends ChangeNotifier {
5631
5660
  notifyListeners();
5632
5661
  }
5633
5662
 
5663
+ Future<Map<String, dynamic>> inspectMemory(String query) async {
5664
+ return _backendClient.inspectMemory(
5665
+ backendUrl,
5666
+ query,
5667
+ agentId: _scopedAgentId,
5668
+ );
5669
+ }
5670
+
5634
5671
  Future<void> updateAssistantBehaviorNotes(String content) async {
5635
5672
  await _backendClient.saveSettings(backendUrl, <String, dynamic>{
5636
5673
  'assistant_behavior_notes': content,
@@ -7146,6 +7183,25 @@ class NeoAgentController extends ChangeNotifier {
7146
7183
  }
7147
7184
  notifyListeners();
7148
7185
  });
7186
+ socket.on('tool:approval_required', (dynamic data) {
7187
+ final payload = _jsonMap(data);
7188
+ final req = ToolApprovalRequest.fromJson(payload);
7189
+ pendingApproval = req;
7190
+ notifyListeners();
7191
+ // Show interactive push notification when app is backgrounded
7192
+ if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
7193
+ _SecurityNotificationService.showApprovalNotification(req);
7194
+ }
7195
+ });
7196
+ socket.on('tool:approval_resolved', (dynamic data) {
7197
+ final payload = _jsonMap(data);
7198
+ final resolvedId = payload['approvalId']?.toString() ?? '';
7199
+ if (pendingApproval?.approvalId == resolvedId) {
7200
+ _SecurityNotificationService.cancelApprovalNotification(resolvedId);
7201
+ pendingApproval = null;
7202
+ notifyListeners();
7203
+ }
7204
+ });
7149
7205
  socket.on('run:steer_queued', (dynamic data) {
7150
7206
  final payload = _jsonMap(data);
7151
7207
  final runId = payload['runId']?.toString() ?? '';
@@ -7257,11 +7313,13 @@ class NeoAgentController extends ChangeNotifier {
7257
7313
  final payload = _jsonMap(data);
7258
7314
  final runId = payload['runId']?.toString() ?? '';
7259
7315
  if (_voiceRunIds.remove(runId)) {
7316
+ unawaited(refreshRateLimitUsage());
7260
7317
  return;
7261
7318
  }
7262
7319
  if (_backgroundRunIds.remove(runId)) {
7263
7320
  unawaited(refreshRunsOnly());
7264
7321
  unawaited(refreshMemory());
7322
+ unawaited(refreshRateLimitUsage());
7265
7323
  notifyListeners();
7266
7324
  return;
7267
7325
  }
@@ -7285,6 +7343,7 @@ class NeoAgentController extends ChangeNotifier {
7285
7343
  );
7286
7344
  }
7287
7345
  unawaited(refreshRunsOnly());
7346
+ unawaited(refreshRateLimitUsage());
7288
7347
  notifyListeners();
7289
7348
  });
7290
7349
  socket.on('run:stopped', (dynamic data) {
@@ -7335,8 +7394,18 @@ class NeoAgentController extends ChangeNotifier {
7335
7394
  streamingAssistant = '';
7336
7395
  activeRun = null;
7337
7396
  isSendingMessage = false;
7338
- errorMessage =
7397
+ final message =
7398
+ payload['error']?.toString().trim() ??
7339
7399
  'I could not complete that request right now. Please try again in a moment.';
7400
+ errorMessage = _friendlyErrorMessage(
7401
+ BackendException(
7402
+ message,
7403
+ statusCode: payload['code'] == 'RATE_LIMIT_EXCEEDED' ? 429 : null,
7404
+ ),
7405
+ );
7406
+ if (payload['code'] == 'RATE_LIMIT_EXCEEDED') {
7407
+ unawaited(refreshRateLimitUsage());
7408
+ }
7340
7409
  notifyListeners();
7341
7410
  });
7342
7411
  socket.on('tasks:task_complete', (dynamic _) {
@@ -1734,6 +1734,14 @@ class _InteractiveSurfacePreviewState
1734
1734
  Widget build(BuildContext context) {
1735
1735
  final path = widget.screenshotPath;
1736
1736
  final hasImage = path != null && path.isNotEmpty;
1737
+ final viewportHeight = MediaQuery.sizeOf(context).height;
1738
+ final surfaceHeightCap = widget.surface == _DeviceSurface.android
1739
+ ? 640.0
1740
+ : 560.0;
1741
+ final previewMaxHeight = math.min(
1742
+ surfaceHeightCap,
1743
+ viewportHeight * 0.48,
1744
+ );
1737
1745
  final aspectRatio = switch (widget.surface) {
1738
1746
  _DeviceSurface.browser => 16 / 10,
1739
1747
  _DeviceSurface.android => 10 / 16,
@@ -1755,9 +1763,7 @@ class _InteractiveSurfacePreviewState
1755
1763
  child: Column(
1756
1764
  children: <Widget>[
1757
1765
  ConstrainedBox(
1758
- constraints: BoxConstraints(
1759
- maxHeight: widget.surface == _DeviceSurface.android ? 640 : 560,
1760
- ),
1766
+ constraints: BoxConstraints(maxHeight: previewMaxHeight),
1761
1767
  child: AspectRatio(
1762
1768
  aspectRatio: aspectRatio,
1763
1769
  child: ClipRRect(
@@ -4111,6 +4111,12 @@ class AccountUsageAndLimits {
4111
4111
  this.weeklyLimit,
4112
4112
  this.fourHourUsage = 0,
4113
4113
  this.weeklyUsage = 0,
4114
+ this.fourHourRemaining = 0,
4115
+ this.weeklyRemaining = 0,
4116
+ this.fourHourReached = false,
4117
+ this.weeklyReached = false,
4118
+ this.fourHourNextDecreaseAt,
4119
+ this.weeklyNextDecreaseAt,
4114
4120
  this.fourHourIsCustom = false,
4115
4121
  this.weeklyIsCustom = false,
4116
4122
  });
@@ -4118,11 +4124,28 @@ class AccountUsageAndLimits {
4118
4124
  factory AccountUsageAndLimits.fromJson(Map<String, dynamic> json) {
4119
4125
  final limits = json['limits'] is Map ? json['limits'] as Map : const {};
4120
4126
  final usage = json['usage'] is Map ? json['usage'] as Map : const {};
4127
+ final remaining = json['remaining'] is Map
4128
+ ? json['remaining'] as Map
4129
+ : const {};
4130
+ final reached = json['reached'] is Map ? json['reached'] as Map : const {};
4131
+ final nextDecreaseAt = json['nextDecreaseAt'] is Map
4132
+ ? json['nextDecreaseAt'] as Map
4133
+ : const {};
4121
4134
  return AccountUsageAndLimits(
4122
4135
  fourHourLimit: int.tryParse(limits['fourHour']?.toString() ?? ''),
4123
4136
  weeklyLimit: int.tryParse(limits['weekly']?.toString() ?? ''),
4124
4137
  fourHourUsage: _asInt(usage['fourHour']),
4125
4138
  weeklyUsage: _asInt(usage['weekly']),
4139
+ fourHourRemaining: _asInt(remaining['fourHour']),
4140
+ weeklyRemaining: _asInt(remaining['weekly']),
4141
+ fourHourReached: reached['fourHour'] == true,
4142
+ weeklyReached: reached['weekly'] == true,
4143
+ fourHourNextDecreaseAt: DateTime.tryParse(
4144
+ nextDecreaseAt['fourHour']?.toString() ?? '',
4145
+ ),
4146
+ weeklyNextDecreaseAt: DateTime.tryParse(
4147
+ nextDecreaseAt['weekly']?.toString() ?? '',
4148
+ ),
4126
4149
  fourHourIsCustom: limits['fourHourIsCustom'] == true,
4127
4150
  weeklyIsCustom: limits['weeklyIsCustom'] == true,
4128
4151
  );
@@ -4132,6 +4155,15 @@ class AccountUsageAndLimits {
4132
4155
  final int? weeklyLimit;
4133
4156
  final int fourHourUsage;
4134
4157
  final int weeklyUsage;
4158
+ final int fourHourRemaining;
4159
+ final int weeklyRemaining;
4160
+ final bool fourHourReached;
4161
+ final bool weeklyReached;
4162
+ final DateTime? fourHourNextDecreaseAt;
4163
+ final DateTime? weeklyNextDecreaseAt;
4135
4164
  final bool fourHourIsCustom;
4136
4165
  final bool weeklyIsCustom;
4166
+
4167
+ bool get hasLimits => fourHourLimit != null || weeklyLimit != null;
4168
+ bool get isReached => fourHourReached || weeklyReached;
4137
4169
  }
@@ -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),