neoagent 2.3.1-beta.93 → 2.3.1-beta.95
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/com.neoagent.plist +1 -1
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +12 -2
- package/flutter_app/lib/main_chat.dart +23 -16
- package/flutter_app/lib/main_controller.dart +27 -1
- package/flutter_app/lib/main_shared.dart +1 -1
- package/flutter_app/lib/src/backend_client.dart +2 -10
- package/lib/manager.js +260 -134
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +74352 -73147
- package/server/routes/triggers.js +41 -32
- package/server/services/ai/compaction.js +1 -1
- package/server/services/ai/engine.js +94 -21
- package/server/services/ai/hooks.js +127 -0
- package/server/services/ai/loopPolicy.js +146 -0
- package/server/services/ai/recordingInsights.js +11 -13
- package/server/services/ai/systemPrompt.js +8 -1
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/tools.js +30 -0
- package/server/services/android/controller.js +3 -3
- package/server/services/memory/llm_transfer.js +15 -14
- package/server/services/messaging/automation.js +1 -1
- package/server/services/voice/runtime.js +8 -8
package/com.neoagent.plist
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<key>EnvironmentVariables</key>
|
|
18
18
|
<dict>
|
|
19
19
|
<key>PATH</key>
|
|
20
|
-
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
20
|
+
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
21
21
|
<key>HOME</key>
|
|
22
22
|
<string>__HOME__</string>
|
|
23
23
|
<key>NODE_ENV</key>
|
|
@@ -10,9 +10,12 @@ class NotificationInterceptor {
|
|
|
10
10
|
factory NotificationInterceptor() => _instance;
|
|
11
11
|
NotificationInterceptor._internal();
|
|
12
12
|
|
|
13
|
+
static const Duration _perAppCooldown = Duration(seconds: 60);
|
|
14
|
+
|
|
13
15
|
bool _isListening = false;
|
|
14
16
|
String _backendUrl = '';
|
|
15
17
|
String _token = '';
|
|
18
|
+
final Map<String, DateTime> _lastTriggerTimes = {};
|
|
16
19
|
|
|
17
20
|
Future<void> initialize(String backendUrl, String token) async {
|
|
18
21
|
_backendUrl = backendUrl;
|
|
@@ -29,14 +32,21 @@ class NotificationInterceptor {
|
|
|
29
32
|
NotificationListenerService.notificationsStream.listen((
|
|
30
33
|
ServiceNotificationEvent event,
|
|
31
34
|
) {
|
|
32
|
-
// Filter out noisy system notifications or ongoing foreground services
|
|
33
35
|
if (event.packageName == null ||
|
|
34
36
|
event.packageName!.contains('android.system')) {
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
//
|
|
40
|
+
// Skip removed notifications and persistent ongoing ones
|
|
39
41
|
if (event.hasRemoved == true) return;
|
|
42
|
+
if (event.onGoing == true) return;
|
|
43
|
+
|
|
44
|
+
// Per-app cooldown to avoid flooding the backend
|
|
45
|
+
final pkg = event.packageName!;
|
|
46
|
+
final now = DateTime.now();
|
|
47
|
+
final last = _lastTriggerTimes[pkg];
|
|
48
|
+
if (last != null && now.difference(last) < _perAppCooldown) return;
|
|
49
|
+
_lastTriggerTimes[pkg] = now;
|
|
40
50
|
|
|
41
51
|
_sendToBackend(event);
|
|
42
52
|
});
|
|
@@ -128,17 +128,22 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
128
128
|
Future<void> _attachFiles() async {
|
|
129
129
|
final result = await FilePicker.platform.pickFiles(
|
|
130
130
|
allowMultiple: true,
|
|
131
|
-
withData:
|
|
131
|
+
withData: kIsWeb,
|
|
132
132
|
type: FileType.any,
|
|
133
133
|
);
|
|
134
134
|
if (!mounted || result == null || result.files.isEmpty) {
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
final attachments = result.files
|
|
138
|
-
.where(
|
|
138
|
+
.where(
|
|
139
|
+
(file) =>
|
|
140
|
+
kIsWeb
|
|
141
|
+
? file.bytes != null
|
|
142
|
+
: file.path?.trim().isNotEmpty == true,
|
|
143
|
+
)
|
|
139
144
|
.map(
|
|
140
145
|
(file) => SharedChatAttachment(
|
|
141
|
-
uri: file.path!,
|
|
146
|
+
uri: kIsWeb ? file.name : file.path!,
|
|
142
147
|
name: file.name,
|
|
143
148
|
mimeType: _mimeTypeForFileName(file.name),
|
|
144
149
|
sizeBytes: file.size,
|
|
@@ -158,20 +163,20 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
bool get _isNearBottom {
|
|
167
|
+
if (!_scrollController.hasClients) return true;
|
|
168
|
+
final pos = _scrollController.position;
|
|
169
|
+
if (!pos.hasContentDimensions) return true;
|
|
170
|
+
return pos.pixels >= pos.maxScrollExtent - 80;
|
|
171
|
+
}
|
|
172
|
+
|
|
161
173
|
void _scrollToBottom() {
|
|
162
174
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
163
|
-
if (!mounted || !_scrollController.hasClients)
|
|
164
|
-
|
|
175
|
+
if (!mounted || !_scrollController.hasClients) return;
|
|
176
|
+
final pos = _scrollController.position;
|
|
177
|
+
if (pos.hasContentDimensions) {
|
|
178
|
+
_scrollController.jumpTo(pos.maxScrollExtent);
|
|
165
179
|
}
|
|
166
|
-
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
167
|
-
unawaited(
|
|
168
|
-
WidgetsBinding.instance.endOfFrame.then((_) {
|
|
169
|
-
if (!mounted || !_scrollController.hasClients) {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
|
|
@@ -185,13 +190,14 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
185
190
|
_lastMessageCount = messages.length;
|
|
186
191
|
_lastToolCount = controller.toolEvents.length;
|
|
187
192
|
_lastStream = controller.streamingAssistant;
|
|
188
|
-
_scrollToBottom();
|
|
193
|
+
if (_isNearBottom) _scrollToBottom();
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
return Column(
|
|
192
197
|
children: <Widget>[
|
|
193
198
|
Expanded(
|
|
194
|
-
child:
|
|
199
|
+
child: SelectionArea(
|
|
200
|
+
child: ListView(
|
|
195
201
|
controller: _scrollController,
|
|
196
202
|
padding: _pagePadding(context),
|
|
197
203
|
children: <Widget>[
|
|
@@ -257,6 +263,7 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
257
263
|
),
|
|
258
264
|
],
|
|
259
265
|
),
|
|
266
|
+
),
|
|
260
267
|
),
|
|
261
268
|
Container(
|
|
262
269
|
padding: const EdgeInsets.fromLTRB(20, 14, 20, 20),
|
|
@@ -69,6 +69,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
69
69
|
DateTime? _lastHomeWidgetSyncAt;
|
|
70
70
|
int _authCycle = 0;
|
|
71
71
|
bool _isPollingQrLogin = false;
|
|
72
|
+
bool _socketHasConnectedOnce = false;
|
|
72
73
|
List<LogEntry> _serverLogs = const <LogEntry>[];
|
|
73
74
|
List<LogEntry> _clientLogs = const <LogEntry>[];
|
|
74
75
|
|
|
@@ -6335,7 +6336,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6335
6336
|
|
|
6336
6337
|
List<ChatEntry> get visibleChatMessages {
|
|
6337
6338
|
final entries = <ChatEntry>[...chatMessages];
|
|
6338
|
-
if (activeRun != null && streamingAssistant.trim().isEmpty) {
|
|
6339
|
+
if (isSendingMessage && activeRun != null && streamingAssistant.trim().isEmpty) {
|
|
6339
6340
|
entries.add(
|
|
6340
6341
|
ChatEntry(
|
|
6341
6342
|
id: '',
|
|
@@ -6381,6 +6382,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6381
6382
|
|
|
6382
6383
|
void _disconnectSocket() {
|
|
6383
6384
|
socketConnected = false;
|
|
6385
|
+
_socketHasConnectedOnce = false;
|
|
6384
6386
|
if (_liveVoiceSessionOpenCompleter != null &&
|
|
6385
6387
|
!_liveVoiceSessionOpenCompleter!.isCompleted) {
|
|
6386
6388
|
_liveVoiceSessionOpenCompleter!.completeError(
|
|
@@ -6417,6 +6419,10 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6417
6419
|
socketConnected = true;
|
|
6418
6420
|
socket.emit('client:request_logs');
|
|
6419
6421
|
socket.emit('integrations:status');
|
|
6422
|
+
if (_socketHasConnectedOnce && isAuthenticated) {
|
|
6423
|
+
unawaited(refresh());
|
|
6424
|
+
}
|
|
6425
|
+
_socketHasConnectedOnce = true;
|
|
6420
6426
|
voiceAssistantLiveState = voiceAssistantLiveState.copyWith(
|
|
6421
6427
|
transportState: 'connected',
|
|
6422
6428
|
clearError: _hasRecoverableLiveVoiceTurn(),
|
|
@@ -7110,6 +7116,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7110
7116
|
}
|
|
7111
7117
|
if (_backgroundRunIds.remove(runId)) {
|
|
7112
7118
|
unawaited(refreshRunsOnly());
|
|
7119
|
+
unawaited(refreshMemory());
|
|
7113
7120
|
notifyListeners();
|
|
7114
7121
|
return;
|
|
7115
7122
|
}
|
|
@@ -7136,6 +7143,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7136
7143
|
}
|
|
7137
7144
|
if (_backgroundRunIds.remove(runId)) {
|
|
7138
7145
|
unawaited(refreshRunsOnly());
|
|
7146
|
+
unawaited(refreshMemory());
|
|
7139
7147
|
notifyListeners();
|
|
7140
7148
|
return;
|
|
7141
7149
|
}
|
|
@@ -7179,6 +7187,24 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
7179
7187
|
'I could not complete that request right now. Please try again in a moment.';
|
|
7180
7188
|
notifyListeners();
|
|
7181
7189
|
});
|
|
7190
|
+
socket.on('tasks:task_complete', (dynamic _) {
|
|
7191
|
+
unawaited(refreshTasks());
|
|
7192
|
+
});
|
|
7193
|
+
socket.on('tasks:task_running', (dynamic _) {
|
|
7194
|
+
unawaited(refreshTasks());
|
|
7195
|
+
});
|
|
7196
|
+
socket.on('tasks:task_error', (dynamic _) {
|
|
7197
|
+
unawaited(refreshTasks());
|
|
7198
|
+
});
|
|
7199
|
+
socket.on('tasks:task_deleted', (dynamic _) {
|
|
7200
|
+
unawaited(refreshTasks());
|
|
7201
|
+
});
|
|
7202
|
+
socket.on('tasks:task_skipped', (dynamic _) {
|
|
7203
|
+
unawaited(refreshTasks());
|
|
7204
|
+
});
|
|
7205
|
+
socket.on('skill:draft_created', (dynamic _) {
|
|
7206
|
+
unawaited(refreshSkills());
|
|
7207
|
+
});
|
|
7182
7208
|
socket.connect();
|
|
7183
7209
|
_socket = socket;
|
|
7184
7210
|
}
|
|
@@ -1191,7 +1191,7 @@ class _ChatBubble extends StatelessWidget {
|
|
|
1191
1191
|
),
|
|
1192
1192
|
MarkdownBody(
|
|
1193
1193
|
data: entry.content,
|
|
1194
|
-
selectable:
|
|
1194
|
+
selectable: false,
|
|
1195
1195
|
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context))
|
|
1196
1196
|
.copyWith(
|
|
1197
1197
|
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
@@ -1733,14 +1733,13 @@ class BackendClient {
|
|
|
1733
1733
|
}
|
|
1734
1734
|
try {
|
|
1735
1735
|
final response = await request.timeout(_requestTimeout);
|
|
1736
|
-
if (
|
|
1736
|
+
if (response.statusCode >= 400) {
|
|
1737
1737
|
_log(
|
|
1738
|
-
'request.
|
|
1738
|
+
'request.error',
|
|
1739
1739
|
data: <String, Object?>{
|
|
1740
1740
|
'method': method,
|
|
1741
1741
|
'uri': uri.toString(),
|
|
1742
1742
|
'statusCode': response.statusCode,
|
|
1743
|
-
'allowUnauthorized': allowUnauthorized,
|
|
1744
1743
|
},
|
|
1745
1744
|
);
|
|
1746
1745
|
}
|
|
@@ -1775,13 +1774,6 @@ class BackendClient {
|
|
|
1775
1774
|
}
|
|
1776
1775
|
}
|
|
1777
1776
|
|
|
1778
|
-
bool _isNoisySettingsStatusPoll(String method, Uri uri, int statusCode) {
|
|
1779
|
-
if (method != 'GET' || statusCode != 200) {
|
|
1780
|
-
return false;
|
|
1781
|
-
}
|
|
1782
|
-
return uri.path == '/api/settings/update/status';
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1777
|
String _describeTransportError(Object error, Uri uri) {
|
|
1786
1778
|
final text = error.toString().trim();
|
|
1787
1779
|
final lower = text.toLowerCase();
|