neoagent 3.0.1-beta.2 → 3.0.1-beta.20
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.
- package/.env.example +19 -0
- package/README.md +16 -0
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +224 -0
- package/docs/configuration.md +22 -0
- package/docs/index.md +1 -0
- package/flutter_app/lib/main.dart +4 -1
- package/flutter_app/lib/main_app_shell.dart +316 -0
- package/flutter_app/lib/main_billing.dart +1465 -0
- package/flutter_app/lib/main_chat.dart +1568 -231
- package/flutter_app/lib/main_controller.dart +263 -26
- package/flutter_app/lib/main_devices.dart +1 -1
- package/flutter_app/lib/main_install.dart +1147 -0
- package/flutter_app/lib/main_navigation.dart +12 -0
- package/flutter_app/lib/main_operations.dart +134 -9
- package/flutter_app/lib/main_settings.dart +148 -52
- package/flutter_app/lib/main_shared.dart +1 -0
- package/flutter_app/lib/src/backend_client.dart +69 -1
- package/landing/index.html +2 -2
- package/lib/manager.js +156 -0
- package/lib/schema_migrations.js +84 -0
- package/package.json +10 -4
- package/server/admin/access.js +12 -7
- package/server/admin/admin.css +78 -0
- package/server/admin/admin.js +511 -23
- package/server/admin/billing.js +415 -0
- package/server/admin/index.html +120 -13
- package/server/admin/users.js +15 -15
- package/server/db/database.js +13 -21
- package/server/db/sessions_db.js +8 -0
- package/server/http/middleware.js +4 -4
- package/server/http/routes.js +9 -0
- package/server/http/static.js +4 -2
- package/server/middleware/requireBilling.js +12 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +89120 -85431
- package/server/routes/admin.js +524 -29
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +127 -0
- package/server/routes/billing_webhook.js +43 -0
- package/server/routes/memory.js +1 -4
- package/server/routes/settings.js +1 -2
- package/server/routes/skills.js +1 -1
- package/server/routes/triggers.js +2 -2
- package/server/services/account/sessions.js +1 -9
- package/server/services/ai/loop/agent_engine_core.js +42 -0
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/completion_judge.js +42 -0
- package/server/services/ai/loop/conversation_loop.js +248 -34
- package/server/services/ai/loop/progress_monitor.js +6 -6
- package/server/services/ai/loopPolicy.js +37 -44
- package/server/services/ai/messagingFallback.js +25 -1
- package/server/services/ai/models.js +18 -0
- package/server/services/ai/preModelCompaction.js +25 -5
- package/server/services/ai/rate_limits.js +28 -4
- package/server/services/ai/systemPrompt.js +23 -5
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/tools.js +231 -20
- package/server/services/billing/billing_email.js +106 -0
- package/server/services/billing/config.js +17 -0
- package/server/services/billing/plans.js +121 -0
- package/server/services/billing/stripe_client.js +21 -0
- package/server/services/billing/subscriptions.js +462 -0
- package/server/services/billing/trial_guard.js +111 -0
- package/server/services/browser/contentExtractor.js +629 -0
- package/server/services/browser/controller.js +4 -8
- package/server/services/desktop/gateway.js +7 -4
- package/server/services/integrations/google/calendar.js +30 -2
- package/server/services/integrations/secrets.js +7 -4
- package/server/services/manager.js +11 -0
- package/server/services/messaging/automation.js +1 -1
- package/server/services/messaging/telnyx.js +12 -11
- package/server/services/messaging/whatsapp.js +1 -1
- package/server/services/recordings/manager.js +13 -7
- package/server/services/runtime/backends/local-vm.js +40 -22
- package/server/services/tasks/runtime.js +103 -15
- package/server/services/tasks/schedule_utils.js +5 -5
- package/server/services/voice/runtimeManager.js +19 -10
- package/server/services/wearable/gateway.js +8 -5
- package/server/services/websocket.js +26 -8
- package/server/services/workspace/manager.js +37 -3
|
@@ -47,6 +47,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
47
47
|
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
|
48
48
|
bool _connectivityPluginAvailable = true;
|
|
49
49
|
static const int _maxVisibleLogs = 400;
|
|
50
|
+
static const int _maxToolEvents = 500; // separate list from _maxVisibleLogs (chat diagnostics)
|
|
50
51
|
|
|
51
52
|
static const String _configuredBackendUrl = String.fromEnvironment(
|
|
52
53
|
'NEOAGENT_BACKEND_URL',
|
|
@@ -66,6 +67,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
66
67
|
final Map<String, DateTime> _manualRunCooldowns = <String, DateTime>{};
|
|
67
68
|
static const Duration _manualRunCooldownDuration = Duration(seconds: 10);
|
|
68
69
|
static const Duration _homeWidgetSyncCooldown = Duration(seconds: 5);
|
|
70
|
+
static const int _chatHistoryPageSize = 20;
|
|
69
71
|
DateTime? _lastHomeWidgetSyncAt;
|
|
70
72
|
int _authCycle = 0;
|
|
71
73
|
bool _isPollingQrLogin = false;
|
|
@@ -98,6 +100,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
98
100
|
bool isApprovingQrLogin = false;
|
|
99
101
|
bool isCheckingAppUpdate = false;
|
|
100
102
|
bool isOpeningAppUpdate = false;
|
|
103
|
+
bool isLoadingBilling = false;
|
|
104
|
+
bool showBillingSection = false;
|
|
101
105
|
bool socketConnected = false;
|
|
102
106
|
bool hasNetworkConnection = true;
|
|
103
107
|
bool networkStatusKnown = false;
|
|
@@ -142,12 +146,17 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
142
146
|
List<RecordingSessionItem> recordingSessions = const <RecordingSessionItem>[];
|
|
143
147
|
|
|
144
148
|
List<ChatEntry> chatMessages = const <ChatEntry>[];
|
|
149
|
+
bool chatHistoryHasMore = false;
|
|
150
|
+
bool isLoadingOlderChatHistory = false;
|
|
145
151
|
List<AgentProfile> agentProfiles = const <AgentProfile>[];
|
|
146
152
|
String? selectedAgentId;
|
|
147
153
|
List<ModelMeta> supportedModels = const <ModelMeta>[];
|
|
148
154
|
List<AiProviderMeta> aiProviders = const <AiProviderMeta>[];
|
|
149
155
|
List<RunSummary> recentRuns = const <RunSummary>[];
|
|
150
156
|
TokenUsageSnapshot? tokenUsage;
|
|
157
|
+
Map<String, dynamic>? billingSubscription;
|
|
158
|
+
List<Map<String, dynamic>> billingPlans = const <Map<String, dynamic>>[];
|
|
159
|
+
List<Map<String, dynamic>> billingInvoices = const <Map<String, dynamic>>[];
|
|
151
160
|
UpdateStatusSnapshot updateStatus = const UpdateStatusSnapshot();
|
|
152
161
|
List<LogEntry> logs = const <LogEntry>[];
|
|
153
162
|
Map<String, MessagingPlatformStatus> messagingStatuses =
|
|
@@ -158,6 +167,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
158
167
|
MessagingQrState? pendingMessagingQr;
|
|
159
168
|
ToolApprovalRequest? pendingApproval;
|
|
160
169
|
final List<BlockedSenderNotice> _blockedSenderQueue = <BlockedSenderNotice>[];
|
|
170
|
+
final Set<String> _ignoredChats = <String>{};
|
|
161
171
|
List<SkillItem> skills = const <SkillItem>[];
|
|
162
172
|
List<StoreSkillItem> storeSkills = const <StoreSkillItem>[];
|
|
163
173
|
List<OfficialIntegrationItem> officialIntegrations =
|
|
@@ -199,6 +209,9 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
199
209
|
String? _pendingChatDraft;
|
|
200
210
|
List<SharedChatAttachment> _pendingSharedChatAttachments =
|
|
201
211
|
const <SharedChatAttachment>[];
|
|
212
|
+
String? _chatHistoryBeforeCreatedAt;
|
|
213
|
+
String? _chatHistoryBeforeSource;
|
|
214
|
+
String? _chatHistoryBeforeId;
|
|
202
215
|
|
|
203
216
|
ActiveRunState? activeRun;
|
|
204
217
|
List<ToolEventItem> toolEvents = const <ToolEventItem>[];
|
|
@@ -494,6 +507,103 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
494
507
|
];
|
|
495
508
|
}
|
|
496
509
|
|
|
510
|
+
void _resetChatHistoryPagination() {
|
|
511
|
+
chatHistoryHasMore = false;
|
|
512
|
+
isLoadingOlderChatHistory = false;
|
|
513
|
+
_chatHistoryBeforeCreatedAt = null;
|
|
514
|
+
_chatHistoryBeforeSource = null;
|
|
515
|
+
_chatHistoryBeforeId = null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
void _applyChatHistoryCursor(Map<String, dynamic> history) {
|
|
519
|
+
chatHistoryHasMore = history['hasMore'] == true;
|
|
520
|
+
_chatHistoryBeforeCreatedAt = _optionalIdFrom(
|
|
521
|
+
history['nextBeforeCreatedAt'],
|
|
522
|
+
);
|
|
523
|
+
_chatHistoryBeforeSource = _optionalIdFrom(history['nextBeforeSource']);
|
|
524
|
+
_chatHistoryBeforeId = _optionalIdFrom(history['nextBeforeId']);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
String _chatEntryKey(ChatEntry entry) {
|
|
528
|
+
final stableId = entry.id.trim();
|
|
529
|
+
if (stableId.isNotEmpty) {
|
|
530
|
+
return [
|
|
531
|
+
stableId,
|
|
532
|
+
entry.platform,
|
|
533
|
+
entry.role,
|
|
534
|
+
entry.createdAt.toIso8601String(),
|
|
535
|
+
].join('|');
|
|
536
|
+
}
|
|
537
|
+
return [
|
|
538
|
+
entry.role,
|
|
539
|
+
entry.platform,
|
|
540
|
+
entry.createdAt.toIso8601String(),
|
|
541
|
+
entry.content,
|
|
542
|
+
].join('|');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
List<ChatEntry> _chatHistoryEntriesFromResponse(
|
|
546
|
+
Map<String, dynamic> history,
|
|
547
|
+
) {
|
|
548
|
+
return _decodeModelList(
|
|
549
|
+
'chat_history',
|
|
550
|
+
history['messages'],
|
|
551
|
+
ChatEntry.fromJson,
|
|
552
|
+
fallbackToMapValues: true,
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
Future<bool> loadOlderChatHistory() async {
|
|
557
|
+
if (!isAuthenticated ||
|
|
558
|
+
isLoadingOlderChatHistory ||
|
|
559
|
+
!chatHistoryHasMore ||
|
|
560
|
+
_chatHistoryBeforeCreatedAt == null ||
|
|
561
|
+
_chatHistoryBeforeSource == null ||
|
|
562
|
+
_chatHistoryBeforeId == null) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
final agentId = _scopedAgentId;
|
|
567
|
+
final beforeCreatedAt = _chatHistoryBeforeCreatedAt;
|
|
568
|
+
final beforeSource = _chatHistoryBeforeSource;
|
|
569
|
+
final beforeId = _chatHistoryBeforeId;
|
|
570
|
+
isLoadingOlderChatHistory = true;
|
|
571
|
+
notifyListeners();
|
|
572
|
+
try {
|
|
573
|
+
final history = await _backendClient.fetchChatHistory(
|
|
574
|
+
backendUrl,
|
|
575
|
+
agentId: agentId,
|
|
576
|
+
limit: _chatHistoryPageSize,
|
|
577
|
+
beforeCreatedAt: beforeCreatedAt,
|
|
578
|
+
beforeSource: beforeSource,
|
|
579
|
+
beforeId: beforeId,
|
|
580
|
+
);
|
|
581
|
+
if (agentId != _scopedAgentId) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
final olderMessages = _chatHistoryEntriesFromResponse(history);
|
|
585
|
+
_applyChatHistoryCursor(history);
|
|
586
|
+
if (olderMessages.isEmpty) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
final existingKeys = chatMessages.map(_chatEntryKey).toSet();
|
|
590
|
+
final prepended = olderMessages
|
|
591
|
+
.where((entry) => existingKeys.add(_chatEntryKey(entry)))
|
|
592
|
+
.toList(growable: false);
|
|
593
|
+
if (prepended.isEmpty) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
chatMessages = <ChatEntry>[...prepended, ...chatMessages];
|
|
597
|
+
return true;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
errorMessage = _friendlyErrorMessage(error);
|
|
600
|
+
return false;
|
|
601
|
+
} finally {
|
|
602
|
+
isLoadingOlderChatHistory = false;
|
|
603
|
+
notifyListeners();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
497
607
|
String _settingString(String key, String fallback, {bool lowercase = false}) {
|
|
498
608
|
final value = settings[key]?.toString().trim() ?? '';
|
|
499
609
|
if (value.isEmpty) {
|
|
@@ -631,6 +741,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
631
741
|
BlockedSenderNotice? get pendingBlockedSenderNotice =>
|
|
632
742
|
_blockedSenderQueue.isEmpty ? null : _blockedSenderQueue.first;
|
|
633
743
|
|
|
744
|
+
List<String> get ignoredChats => _ignoredChats.toList();
|
|
745
|
+
|
|
634
746
|
void _handleRecordingBridgeChanged() {
|
|
635
747
|
_logRecording('bridge.changed');
|
|
636
748
|
notifyListeners();
|
|
@@ -716,6 +828,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
716
828
|
|
|
717
829
|
Future<void> bootstrap() async {
|
|
718
830
|
_prefs = await SharedPreferences.getInstance();
|
|
831
|
+
_ignoredChats.addAll(_prefs?.getStringList('messaging.ignored_chats') ?? <String>[]);
|
|
719
832
|
await _desktopCompanion.bootstrap(_prefs!);
|
|
720
833
|
final configured = _configuredBackendUrl.trim();
|
|
721
834
|
final savedBackendUrl = _prefs?.getString('backend_url')?.trim() ?? '';
|
|
@@ -1552,6 +1665,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1552
1665
|
linkedAuthProviders = const <LinkedAuthProviderItem>[];
|
|
1553
1666
|
settings = const <String, dynamic>{};
|
|
1554
1667
|
chatMessages = const <ChatEntry>[];
|
|
1668
|
+
_resetChatHistoryPagination();
|
|
1555
1669
|
agentProfiles = const <AgentProfile>[];
|
|
1556
1670
|
selectedAgentId = null;
|
|
1557
1671
|
supportedModels = const <ModelMeta>[];
|
|
@@ -1683,6 +1797,9 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1683
1797
|
if (section == AppSection.accountSettings) {
|
|
1684
1798
|
unawaited(refreshAccountSettings());
|
|
1685
1799
|
}
|
|
1800
|
+
if (section == AppSection.billing) {
|
|
1801
|
+
unawaited(refreshBilling());
|
|
1802
|
+
}
|
|
1686
1803
|
notifyListeners();
|
|
1687
1804
|
}
|
|
1688
1805
|
|
|
@@ -1711,6 +1828,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1711
1828
|
}
|
|
1712
1829
|
selectedAgentId = id;
|
|
1713
1830
|
chatMessages = const <ChatEntry>[];
|
|
1831
|
+
_resetChatHistoryPagination();
|
|
1714
1832
|
recentRuns = const <RunSummary>[];
|
|
1715
1833
|
messagingStatuses = const <String, MessagingPlatformStatus>{};
|
|
1716
1834
|
messagingMessages = const <MessagingMessage>[];
|
|
@@ -1914,14 +2032,20 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1914
2032
|
|
|
1915
2033
|
Future<void> allowMessagingSuggestion(
|
|
1916
2034
|
String platform,
|
|
1917
|
-
QuickAllowSuggestion suggestion,
|
|
1918
|
-
|
|
2035
|
+
QuickAllowSuggestion suggestion, {
|
|
2036
|
+
String? chatId,
|
|
2037
|
+
}) async {
|
|
1919
2038
|
try {
|
|
1920
2039
|
final nextPolicy = _policyWithAddedRule(
|
|
1921
2040
|
currentMessagingAccessPolicy(platform),
|
|
1922
2041
|
suggestion,
|
|
1923
2042
|
);
|
|
1924
2043
|
await saveMessagingAccessPolicy(platform, nextPolicy);
|
|
2044
|
+
if (chatId != null) {
|
|
2045
|
+
_blockedSenderQueue.removeWhere(
|
|
2046
|
+
(notice) => notice.platform == platform && notice.chatId == chatId,
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
1925
2049
|
errorMessage = null;
|
|
1926
2050
|
notifyListeners();
|
|
1927
2051
|
} catch (error) {
|
|
@@ -1930,6 +2054,22 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1930
2054
|
}
|
|
1931
2055
|
}
|
|
1932
2056
|
|
|
2057
|
+
Future<void> ignoreBlockedSender(BlockedSenderNotice notice) async {
|
|
2058
|
+
final key = '${notice.platform}:${notice.chatId ?? notice.sender ?? ''}';
|
|
2059
|
+
_ignoredChats.add(key);
|
|
2060
|
+
_blockedSenderQueue.removeWhere(
|
|
2061
|
+
(n) => n.platform == notice.platform && (n.chatId == notice.chatId || n.sender == notice.sender),
|
|
2062
|
+
);
|
|
2063
|
+
await _prefs?.setStringList('messaging.ignored_chats', _ignoredChats.toList());
|
|
2064
|
+
notifyListeners();
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
Future<void> removeIgnoredChat(String key) async {
|
|
2068
|
+
_ignoredChats.remove(key);
|
|
2069
|
+
await _prefs?.setStringList('messaging.ignored_chats', _ignoredChats.toList());
|
|
2070
|
+
notifyListeners();
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1933
2073
|
void consumeBlockedSenderNotice(String id) {
|
|
1934
2074
|
if (_blockedSenderQueue.isNotEmpty && _blockedSenderQueue.first.id == id) {
|
|
1935
2075
|
_blockedSenderQueue.removeAt(0);
|
|
@@ -1940,6 +2080,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
1940
2080
|
}
|
|
1941
2081
|
|
|
1942
2082
|
void _enqueueBlockedSenderNotice(BlockedSenderNotice notice) {
|
|
2083
|
+
final ignoreKey = '${notice.platform}:${notice.chatId ?? notice.sender ?? ''}';
|
|
2084
|
+
if (_ignoredChats.contains(ignoreKey)) return;
|
|
1943
2085
|
final exists = _blockedSenderQueue.any((item) => item.id == notice.id);
|
|
1944
2086
|
if (!exists) {
|
|
1945
2087
|
_blockedSenderQueue.add(notice);
|
|
@@ -2009,8 +2151,12 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2009
2151
|
|
|
2010
2152
|
final historyFuture = _softRefreshLoad<Map<String, dynamic>>(
|
|
2011
2153
|
'chat_history',
|
|
2012
|
-
_backendClient.fetchChatHistory(
|
|
2013
|
-
|
|
2154
|
+
_backendClient.fetchChatHistory(
|
|
2155
|
+
backendUrl,
|
|
2156
|
+
agentId: agentId,
|
|
2157
|
+
limit: _chatHistoryPageSize,
|
|
2158
|
+
),
|
|
2159
|
+
const <String, dynamic>{'messages': <dynamic>[], 'hasMore': false},
|
|
2014
2160
|
);
|
|
2015
2161
|
final modelsFuture = _softRefreshLoad<Map<String, dynamic>>(
|
|
2016
2162
|
'supported_models',
|
|
@@ -2113,6 +2259,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2113
2259
|
final recordingsFuture = _backendClient
|
|
2114
2260
|
.fetchRecordingSessions(backendUrl)
|
|
2115
2261
|
.catchError((_) => const <Map<String, dynamic>>[]);
|
|
2262
|
+
unawaited(checkBillingEnabled());
|
|
2116
2263
|
final browserFuture = _backendClient
|
|
2117
2264
|
.fetchBrowserStatus(backendUrl)
|
|
2118
2265
|
.catchError((_) => const <String, dynamic>{});
|
|
@@ -2177,12 +2324,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2177
2324
|
return;
|
|
2178
2325
|
}
|
|
2179
2326
|
|
|
2180
|
-
chatMessages =
|
|
2181
|
-
|
|
2182
|
-
history['messages'],
|
|
2183
|
-
ChatEntry.fromJson,
|
|
2184
|
-
fallbackToMapValues: true,
|
|
2185
|
-
);
|
|
2327
|
+
chatMessages = _chatHistoryEntriesFromResponse(history);
|
|
2328
|
+
_applyChatHistoryCursor(history);
|
|
2186
2329
|
|
|
2187
2330
|
supportedModels = _decodeModelList(
|
|
2188
2331
|
'supported_models',
|
|
@@ -4525,12 +4668,17 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4525
4668
|
_appendChatMessage(content, role: 'user', platform: platform);
|
|
4526
4669
|
}
|
|
4527
4670
|
|
|
4671
|
+
List<ToolEventItem> _capToolEvents(List<ToolEventItem> events) {
|
|
4672
|
+
if (events.length <= _maxToolEvents) return events;
|
|
4673
|
+
return events.sublist(events.length - _maxToolEvents);
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4528
4676
|
void _appendToolNote(String summary, {String toolName = 'note'}) {
|
|
4529
4677
|
final trimmed = summary.trim();
|
|
4530
4678
|
if (trimmed.isEmpty) {
|
|
4531
4679
|
return;
|
|
4532
4680
|
}
|
|
4533
|
-
toolEvents = <ToolEventItem>[
|
|
4681
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
4534
4682
|
...toolEvents,
|
|
4535
4683
|
ToolEventItem(
|
|
4536
4684
|
id: 'note-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -4539,7 +4687,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4539
4687
|
status: 'completed',
|
|
4540
4688
|
summary: trimmed,
|
|
4541
4689
|
),
|
|
4542
|
-
];
|
|
4690
|
+
]);
|
|
4543
4691
|
}
|
|
4544
4692
|
|
|
4545
4693
|
Future<void> refreshUpdateStatus() async {
|
|
@@ -4816,6 +4964,95 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4816
4964
|
} catch (_) {}
|
|
4817
4965
|
}
|
|
4818
4966
|
|
|
4967
|
+
// ── Billing ──────────────────────────────────────────────────────────────
|
|
4968
|
+
|
|
4969
|
+
Future<void> checkBillingEnabled() async {
|
|
4970
|
+
try {
|
|
4971
|
+
final r = await _backendClient.getBillingPlans(backendUrl);
|
|
4972
|
+
final enabled = r['plans'] != null;
|
|
4973
|
+
if (showBillingSection != enabled) {
|
|
4974
|
+
showBillingSection = enabled;
|
|
4975
|
+
notifyListeners();
|
|
4976
|
+
}
|
|
4977
|
+
} catch (_) {
|
|
4978
|
+
if (showBillingSection) {
|
|
4979
|
+
showBillingSection = false;
|
|
4980
|
+
notifyListeners();
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
Future<void> refreshBilling() async {
|
|
4986
|
+
if (!isAuthenticated || !showBillingSection) return;
|
|
4987
|
+
isLoadingBilling = true;
|
|
4988
|
+
notifyListeners();
|
|
4989
|
+
try {
|
|
4990
|
+
final results = await Future.wait(<Future<Map<String, dynamic>>>[
|
|
4991
|
+
_backendClient.getBillingInfo(backendUrl),
|
|
4992
|
+
_backendClient.getBillingPlans(backendUrl),
|
|
4993
|
+
_backendClient.getBillingInvoices(backendUrl),
|
|
4994
|
+
]);
|
|
4995
|
+
billingSubscription =
|
|
4996
|
+
results[0]['subscription'] as Map<String, dynamic>?;
|
|
4997
|
+
billingPlans = _asDynList(results[1]['plans'])
|
|
4998
|
+
.cast<Map<String, dynamic>>();
|
|
4999
|
+
billingInvoices = _asDynList(results[2]['invoices'])
|
|
5000
|
+
.cast<Map<String, dynamic>>();
|
|
5001
|
+
} catch (_) {
|
|
5002
|
+
// retain previous data on error
|
|
5003
|
+
} finally {
|
|
5004
|
+
isLoadingBilling = false;
|
|
5005
|
+
notifyListeners();
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
Future<String?> createCheckoutSession(String planId) async {
|
|
5010
|
+
try {
|
|
5011
|
+
final serverUrl = backendUrl;
|
|
5012
|
+
final result = await _backendClient.createCheckoutSession(
|
|
5013
|
+
baseUrl: serverUrl,
|
|
5014
|
+
planId: planId,
|
|
5015
|
+
successUrl: '$serverUrl/',
|
|
5016
|
+
cancelUrl: '$serverUrl/',
|
|
5017
|
+
);
|
|
5018
|
+
return result['url'] as String?;
|
|
5019
|
+
} catch (e) {
|
|
5020
|
+
errorMessage = _friendlyErrorMessage(e);
|
|
5021
|
+
notifyListeners();
|
|
5022
|
+
return null;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
|
|
5026
|
+
Future<String?> createPortalSession() async {
|
|
5027
|
+
try {
|
|
5028
|
+
final serverUrl = backendUrl;
|
|
5029
|
+
final result = await _backendClient.createPortalSession(
|
|
5030
|
+
baseUrl: serverUrl,
|
|
5031
|
+
returnUrl: '$serverUrl/',
|
|
5032
|
+
);
|
|
5033
|
+
return result['url'] as String?;
|
|
5034
|
+
} catch (e) {
|
|
5035
|
+
errorMessage = _friendlyErrorMessage(e);
|
|
5036
|
+
notifyListeners();
|
|
5037
|
+
return null;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
Future<bool> cancelBillingSubscription() async {
|
|
5042
|
+
try {
|
|
5043
|
+
await _backendClient.cancelBillingSubscription(backendUrl);
|
|
5044
|
+
await refreshBilling();
|
|
5045
|
+
return true;
|
|
5046
|
+
} catch (e) {
|
|
5047
|
+
errorMessage = _friendlyErrorMessage(e);
|
|
5048
|
+
notifyListeners();
|
|
5049
|
+
return false;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
List<dynamic> _asDynList(dynamic val) =>
|
|
5054
|
+
val is List ? val : const <dynamic>[];
|
|
5055
|
+
|
|
4819
5056
|
Future<bool> updateAccountEmail({
|
|
4820
5057
|
required String email,
|
|
4821
5058
|
required String currentPassword,
|
|
@@ -6982,7 +7219,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6982
7219
|
'verification: ${payload['verification_need']?.toString() ?? 'none'}',
|
|
6983
7220
|
'freshness: ${payload['freshness_risk']?.toString() ?? 'none'}',
|
|
6984
7221
|
].join(' | ');
|
|
6985
|
-
toolEvents = <ToolEventItem>[
|
|
7222
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
6986
7223
|
...toolEvents,
|
|
6987
7224
|
ToolEventItem(
|
|
6988
7225
|
id: 'analysis-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -6991,7 +7228,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6991
7228
|
status: 'completed',
|
|
6992
7229
|
summary: summary,
|
|
6993
7230
|
),
|
|
6994
|
-
];
|
|
7231
|
+
]);
|
|
6995
7232
|
if (activeRun?.runId == runId) {
|
|
6996
7233
|
activeRun = activeRun!.copyWith(phase: 'Analyzing');
|
|
6997
7234
|
}
|
|
@@ -7016,7 +7253,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7016
7253
|
.where((item) => item.trim().isNotEmpty)
|
|
7017
7254
|
.take(4)
|
|
7018
7255
|
.join(' | ');
|
|
7019
|
-
toolEvents = <ToolEventItem>[
|
|
7256
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
7020
7257
|
...toolEvents,
|
|
7021
7258
|
ToolEventItem(
|
|
7022
7259
|
id: 'plan-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -7025,7 +7262,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7025
7262
|
status: 'completed',
|
|
7026
7263
|
summary: steps.ifEmpty('Execution plan created.'),
|
|
7027
7264
|
),
|
|
7028
|
-
];
|
|
7265
|
+
]);
|
|
7029
7266
|
if (activeRun?.runId == runId) {
|
|
7030
7267
|
activeRun = activeRun!.copyWith(phase: 'Planning');
|
|
7031
7268
|
}
|
|
@@ -7065,10 +7302,10 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7065
7302
|
status: 'running',
|
|
7066
7303
|
summary: _summarizeToolArgs(payload['toolArgs']),
|
|
7067
7304
|
);
|
|
7068
|
-
toolEvents = <ToolEventItem>[
|
|
7305
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
7069
7306
|
...toolEvents.where((event) => event.id != item.id),
|
|
7070
7307
|
item,
|
|
7071
|
-
];
|
|
7308
|
+
]);
|
|
7072
7309
|
if (activeRun?.runId == runId) {
|
|
7073
7310
|
activeRun = activeRun!.copyWith(phase: 'Running tool');
|
|
7074
7311
|
}
|
|
@@ -7083,7 +7320,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7083
7320
|
if (_backgroundRunIds.contains(runId)) {
|
|
7084
7321
|
return;
|
|
7085
7322
|
}
|
|
7086
|
-
toolEvents = <ToolEventItem>[
|
|
7323
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
7087
7324
|
...toolEvents,
|
|
7088
7325
|
ToolEventItem(
|
|
7089
7326
|
id: 'verification-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -7098,7 +7335,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7098
7335
|
) ??
|
|
7099
7336
|
'Verification completed.',
|
|
7100
7337
|
),
|
|
7101
|
-
];
|
|
7338
|
+
]);
|
|
7102
7339
|
if (activeRun?.runId == runId) {
|
|
7103
7340
|
activeRun = activeRun!.copyWith(phase: 'Verifying');
|
|
7104
7341
|
}
|
|
@@ -7138,7 +7375,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7138
7375
|
'Subagent update.',
|
|
7139
7376
|
),
|
|
7140
7377
|
);
|
|
7141
|
-
toolEvents = nextEvents;
|
|
7378
|
+
toolEvents = _capToolEvents(nextEvents);
|
|
7142
7379
|
notifyListeners();
|
|
7143
7380
|
});
|
|
7144
7381
|
socket.on('run:tool_end', (dynamic data) {
|
|
@@ -7171,7 +7408,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7171
7408
|
if (!replaced) {
|
|
7172
7409
|
next.add(updated);
|
|
7173
7410
|
}
|
|
7174
|
-
toolEvents = next;
|
|
7411
|
+
toolEvents = _capToolEvents(next);
|
|
7175
7412
|
final toolName = payload['toolName']?.toString() ?? '';
|
|
7176
7413
|
final screenshotPath =
|
|
7177
7414
|
payload['screenshotPath']?.toString() ??
|
|
@@ -7220,7 +7457,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7220
7457
|
if (_backgroundRunIds.contains(runId)) {
|
|
7221
7458
|
return;
|
|
7222
7459
|
}
|
|
7223
|
-
toolEvents = <ToolEventItem>[
|
|
7460
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
7224
7461
|
...toolEvents,
|
|
7225
7462
|
ToolEventItem(
|
|
7226
7463
|
id: 'steer-queued-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -7230,7 +7467,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7230
7467
|
summary:
|
|
7231
7468
|
'Queued as steering for the current run: ${payload['content']?.toString() ?? ''}',
|
|
7232
7469
|
),
|
|
7233
|
-
];
|
|
7470
|
+
]);
|
|
7234
7471
|
if (activeRun?.runId == runId || activeRun?.runId == 'pending') {
|
|
7235
7472
|
activeRun = activeRun!.copyWith(
|
|
7236
7473
|
pendingSteeringCount: _asInt(payload['pendingCount']),
|
|
@@ -7247,7 +7484,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7247
7484
|
if (_backgroundRunIds.contains(runId)) {
|
|
7248
7485
|
return;
|
|
7249
7486
|
}
|
|
7250
|
-
toolEvents = <ToolEventItem>[
|
|
7487
|
+
toolEvents = _capToolEvents(<ToolEventItem>[
|
|
7251
7488
|
...toolEvents,
|
|
7252
7489
|
ToolEventItem(
|
|
7253
7490
|
id: 'steer-applied-${DateTime.now().microsecondsSinceEpoch}',
|
|
@@ -7258,7 +7495,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7258
7495
|
? 'Applied the latest steering update to the current run.'
|
|
7259
7496
|
: 'Applied ${_asInt(payload['count'])} queued steering updates to the current run.',
|
|
7260
7497
|
),
|
|
7261
|
-
];
|
|
7498
|
+
]);
|
|
7262
7499
|
if (activeRun?.runId == runId || activeRun?.runId == 'pending') {
|
|
7263
7500
|
activeRun = activeRun!.copyWith(
|
|
7264
7501
|
pendingSteeringCount: _asInt(payload['pendingCount']),
|