neoagent 2.4.1-beta.33 → 2.4.1-beta.35
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 +10 -3
- package/flutter_app/lib/main_admin.dart +13 -0
- package/flutter_app/lib/main_controller.dart +24 -6
- package/flutter_app/lib/main_models.dart +28 -0
- package/flutter_app/lib/main_operations.dart +117 -0
- 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 +37961 -37794
- package/server/routes/mcp.js +29 -13
- package/server/routes/memory.js +1 -0
- package/server/services/ai/engine.js +64 -10
- package/server/services/ai/systemPrompt.js +28 -22
- package/server/services/ai/tools.js +24 -3
- package/server/services/desktop/screenRecorder.js +208 -98
- package/server/services/desktop/screen_recorder_support.js +46 -0
- package/server/services/manager.js +51 -53
- package/server/services/mcp/client.js +208 -268
- package/server/services/mcp/client_support.js +172 -0
- package/server/services/mcp/recovery.js +116 -0
- package/server/services/mcp/tool_operations.js +123 -0
- package/server/services/memory/ingestion.js +185 -370
- package/server/services/memory/ingestion_coverage.js +129 -0
- package/server/services/memory/ingestion_documents.js +123 -0
- package/server/services/memory/ingestion_support.js +171 -0
- package/server/services/messaging/automation.js +71 -134
- package/server/services/messaging/inbound_queue.js +111 -0
- package/server/services/messaging/typing_keepalive.js +110 -0
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +240 -53
- package/server/services/tasks/task_repository.js +35 -6
package/.env.example
CHANGED
|
@@ -219,9 +219,16 @@ DEEPGRAM_LANGUAGE=multi
|
|
|
219
219
|
|
|
220
220
|
OLLAMA_URL=http://localhost:11434
|
|
221
221
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
# Connected integration memory refresh cadence in milliseconds.
|
|
223
|
+
NEOAGENT_MEMORY_INGESTION_INTERVAL_MS=600000
|
|
224
|
+
|
|
225
|
+
# Local screen OCR capture. This captures the backend host's macOS desktop,
|
|
226
|
+
# so enable it only when that host is the configured user's desktop.
|
|
227
|
+
NEOAGENT_SCREEN_RECORDER_ENABLED=false
|
|
228
|
+
# Existing NeoAgent user ID that owns all captured screen history. Required when enabled.
|
|
229
|
+
NEOAGENT_SCREEN_RECORDER_USER_ID=
|
|
230
|
+
NEOAGENT_SCREEN_RECORDER_INTERVAL_MS=10000
|
|
231
|
+
NEOAGENT_SCREEN_RECORDER_RETENTION_DAYS=7
|
|
225
232
|
|
|
226
233
|
########################################
|
|
227
234
|
# Messaging and voice integrations
|
|
@@ -429,6 +429,8 @@ class McpPanel extends StatelessWidget {
|
|
|
429
429
|
label: server.status,
|
|
430
430
|
color: server.status == 'running'
|
|
431
431
|
? _success
|
|
432
|
+
: server.hasError
|
|
433
|
+
? _danger
|
|
432
434
|
: _textSecondary,
|
|
433
435
|
),
|
|
434
436
|
],
|
|
@@ -465,6 +467,17 @@ class McpPanel extends StatelessWidget {
|
|
|
465
467
|
),
|
|
466
468
|
],
|
|
467
469
|
),
|
|
470
|
+
if (server.hasError) ...<Widget>[
|
|
471
|
+
const SizedBox(height: 12),
|
|
472
|
+
_InlineError(message: server.error!),
|
|
473
|
+
if (server.retryLabel.isNotEmpty) ...<Widget>[
|
|
474
|
+
const SizedBox(height: 8),
|
|
475
|
+
Text(
|
|
476
|
+
server.retryLabel,
|
|
477
|
+
style: TextStyle(color: _textSecondary),
|
|
478
|
+
),
|
|
479
|
+
],
|
|
480
|
+
],
|
|
468
481
|
const SizedBox(height: 14),
|
|
469
482
|
Wrap(
|
|
470
483
|
spacing: 10,
|
|
@@ -6112,18 +6112,36 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
6112
6112
|
}
|
|
6113
6113
|
|
|
6114
6114
|
Future<void> startMcpServer(int id) async {
|
|
6115
|
-
|
|
6116
|
-
|
|
6115
|
+
try {
|
|
6116
|
+
await _backendClient.startMcpServer(backendUrl, id);
|
|
6117
|
+
await refreshMcp();
|
|
6118
|
+
} catch (error) {
|
|
6119
|
+
errorMessage = _friendlyErrorMessage(error);
|
|
6120
|
+
await refreshMcp();
|
|
6121
|
+
notifyListeners();
|
|
6122
|
+
}
|
|
6117
6123
|
}
|
|
6118
6124
|
|
|
6119
6125
|
Future<void> stopMcpServer(int id) async {
|
|
6120
|
-
|
|
6121
|
-
|
|
6126
|
+
try {
|
|
6127
|
+
await _backendClient.stopMcpServer(backendUrl, id);
|
|
6128
|
+
await refreshMcp();
|
|
6129
|
+
} catch (error) {
|
|
6130
|
+
errorMessage = _friendlyErrorMessage(error);
|
|
6131
|
+
await refreshMcp();
|
|
6132
|
+
notifyListeners();
|
|
6133
|
+
}
|
|
6122
6134
|
}
|
|
6123
6135
|
|
|
6124
6136
|
Future<void> deleteMcpServer(int id) async {
|
|
6125
|
-
|
|
6126
|
-
|
|
6137
|
+
try {
|
|
6138
|
+
await _backendClient.deleteMcpServer(backendUrl, id);
|
|
6139
|
+
await refreshMcp();
|
|
6140
|
+
} catch (error) {
|
|
6141
|
+
errorMessage = _friendlyErrorMessage(error);
|
|
6142
|
+
await refreshMcp();
|
|
6143
|
+
notifyListeners();
|
|
6144
|
+
}
|
|
6127
6145
|
}
|
|
6128
6146
|
|
|
6129
6147
|
Future<void> requestHealthPermissions() async {
|
|
@@ -3099,6 +3099,9 @@ class TaskItem {
|
|
|
3099
3099
|
required this.model,
|
|
3100
3100
|
required this.enabled,
|
|
3101
3101
|
required this.lastRun,
|
|
3102
|
+
required this.lastRunId,
|
|
3103
|
+
required this.lastRunStatus,
|
|
3104
|
+
required this.lastRunError,
|
|
3102
3105
|
required this.taskType,
|
|
3103
3106
|
required this.widgetId,
|
|
3104
3107
|
});
|
|
@@ -3144,6 +3147,9 @@ class TaskItem {
|
|
|
3144
3147
|
'',
|
|
3145
3148
|
enabled: json['enabled'] != false,
|
|
3146
3149
|
lastRun: _parseOptionalTimestamp(json['lastRun']?.toString()),
|
|
3150
|
+
lastRunId: json['lastRunId']?.toString() ?? '',
|
|
3151
|
+
lastRunStatus: json['lastRunStatus']?.toString() ?? '',
|
|
3152
|
+
lastRunError: json['lastRunError']?.toString() ?? '',
|
|
3147
3153
|
taskType:
|
|
3148
3154
|
json['taskType']?.toString().ifEmpty(
|
|
3149
3155
|
json['task_type']?.toString() ?? 'agent_prompt',
|
|
@@ -3168,12 +3174,20 @@ class TaskItem {
|
|
|
3168
3174
|
final String model;
|
|
3169
3175
|
final bool enabled;
|
|
3170
3176
|
final DateTime? lastRun;
|
|
3177
|
+
final String lastRunId;
|
|
3178
|
+
final String lastRunStatus;
|
|
3179
|
+
final String lastRunError;
|
|
3171
3180
|
final String taskType;
|
|
3172
3181
|
final String widgetId;
|
|
3173
3182
|
|
|
3174
3183
|
String get scheduleLabel =>
|
|
3175
3184
|
triggerSummary.trim().isEmpty ? 'Task trigger' : triggerSummary;
|
|
3176
3185
|
String get lastRunLabel => lastRun == null ? '' : _formatTimestamp(lastRun!);
|
|
3186
|
+
String get lastRunStatusLabel =>
|
|
3187
|
+
_titleCase(lastRunStatus.replaceAll('_', ' '));
|
|
3188
|
+
bool get hasLastRunStatus => lastRunStatus.trim().isNotEmpty;
|
|
3189
|
+
bool get lastRunFailed =>
|
|
3190
|
+
lastRunStatus == 'failed' || lastRunStatus == 'error';
|
|
3177
3191
|
bool get hasModelOverride => model.trim().isNotEmpty;
|
|
3178
3192
|
bool get isWidgetRefresh => taskType == 'widget_refresh';
|
|
3179
3193
|
}
|
|
@@ -3383,6 +3397,9 @@ class McpServerItem {
|
|
|
3383
3397
|
required this.enabled,
|
|
3384
3398
|
required this.status,
|
|
3385
3399
|
required this.toolCount,
|
|
3400
|
+
required this.error,
|
|
3401
|
+
required this.consecutiveFails,
|
|
3402
|
+
required this.nextRetryAt,
|
|
3386
3403
|
});
|
|
3387
3404
|
|
|
3388
3405
|
factory McpServerItem.fromJson(Map<dynamic, dynamic> json) {
|
|
@@ -3397,6 +3414,9 @@ class McpServerItem {
|
|
|
3397
3414
|
enabled: json['enabled'] == true,
|
|
3398
3415
|
status: json['status']?.toString().ifEmpty('stopped') ?? 'stopped',
|
|
3399
3416
|
toolCount: _asInt(json['toolCount']),
|
|
3417
|
+
error: json['error']?.toString(),
|
|
3418
|
+
consecutiveFails: _asInt(json['consecutiveFails']),
|
|
3419
|
+
nextRetryAt: _parseOptionalTimestamp(json['nextRetryAt']?.toString()),
|
|
3400
3420
|
);
|
|
3401
3421
|
}
|
|
3402
3422
|
|
|
@@ -3408,6 +3428,14 @@ class McpServerItem {
|
|
|
3408
3428
|
final bool enabled;
|
|
3409
3429
|
final String status;
|
|
3410
3430
|
final int toolCount;
|
|
3431
|
+
final String? error;
|
|
3432
|
+
final int consecutiveFails;
|
|
3433
|
+
final DateTime? nextRetryAt;
|
|
3434
|
+
|
|
3435
|
+
bool get hasError => (error ?? '').trim().isNotEmpty;
|
|
3436
|
+
String get retryLabel => nextRetryAt == null
|
|
3437
|
+
? ''
|
|
3438
|
+
: 'Next retry: ${_formatTimestamp(nextRetryAt!)}';
|
|
3411
3439
|
|
|
3412
3440
|
String get authMethodLabel {
|
|
3413
3441
|
final auth = _jsonMap(config['auth']);
|
|
@@ -4555,6 +4555,89 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4555
4555
|
|
|
4556
4556
|
NeoAgentController get controller => widget.controller;
|
|
4557
4557
|
|
|
4558
|
+
Color _taskRunStatusColor(String status) {
|
|
4559
|
+
switch (status) {
|
|
4560
|
+
case 'completed':
|
|
4561
|
+
return _success;
|
|
4562
|
+
case 'failed':
|
|
4563
|
+
case 'error':
|
|
4564
|
+
return _danger;
|
|
4565
|
+
case 'running':
|
|
4566
|
+
case 'retrying':
|
|
4567
|
+
return _warning;
|
|
4568
|
+
default:
|
|
4569
|
+
return _textSecondary;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
Future<void> _showLastRun(TaskItem task) async {
|
|
4574
|
+
final runId = task.lastRunId.trim();
|
|
4575
|
+
if (runId.isEmpty) return;
|
|
4576
|
+
|
|
4577
|
+
try {
|
|
4578
|
+
final detail = await controller.fetchRunDetail(runId, force: true);
|
|
4579
|
+
if (!mounted) return;
|
|
4580
|
+
await showDialog<void>(
|
|
4581
|
+
context: context,
|
|
4582
|
+
builder: (dialogContext) => AlertDialog(
|
|
4583
|
+
title: Text(detail.run.title),
|
|
4584
|
+
content: ConstrainedBox(
|
|
4585
|
+
constraints: const BoxConstraints(maxWidth: 620),
|
|
4586
|
+
child: SingleChildScrollView(
|
|
4587
|
+
child: Column(
|
|
4588
|
+
mainAxisSize: MainAxisSize.min,
|
|
4589
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
4590
|
+
children: <Widget>[
|
|
4591
|
+
Wrap(
|
|
4592
|
+
spacing: 8,
|
|
4593
|
+
runSpacing: 8,
|
|
4594
|
+
children: <Widget>[
|
|
4595
|
+
_StatusPill(
|
|
4596
|
+
label: detail.run.statusLabel,
|
|
4597
|
+
color: detail.run.statusColor,
|
|
4598
|
+
),
|
|
4599
|
+
_StatusPill(
|
|
4600
|
+
label: '${detail.steps.length} steps',
|
|
4601
|
+
color: _textSecondary,
|
|
4602
|
+
),
|
|
4603
|
+
],
|
|
4604
|
+
),
|
|
4605
|
+
if (detail.run.error.trim().isNotEmpty) ...<Widget>[
|
|
4606
|
+
const SizedBox(height: 16),
|
|
4607
|
+
Text(detail.run.error, style: TextStyle(color: _danger)),
|
|
4608
|
+
],
|
|
4609
|
+
if (detail.response.trim().isNotEmpty) ...<Widget>[
|
|
4610
|
+
const SizedBox(height: 16),
|
|
4611
|
+
SelectableText(detail.response),
|
|
4612
|
+
],
|
|
4613
|
+
if (detail.response.trim().isEmpty &&
|
|
4614
|
+
detail.run.error.trim().isEmpty) ...<Widget>[
|
|
4615
|
+
const SizedBox(height: 16),
|
|
4616
|
+
Text(
|
|
4617
|
+
'This run did not produce a user-facing response.',
|
|
4618
|
+
style: TextStyle(color: _textSecondary),
|
|
4619
|
+
),
|
|
4620
|
+
],
|
|
4621
|
+
],
|
|
4622
|
+
),
|
|
4623
|
+
),
|
|
4624
|
+
),
|
|
4625
|
+
actions: <Widget>[
|
|
4626
|
+
TextButton(
|
|
4627
|
+
onPressed: () => Navigator.of(dialogContext).pop(),
|
|
4628
|
+
child: const Text('Close'),
|
|
4629
|
+
),
|
|
4630
|
+
],
|
|
4631
|
+
),
|
|
4632
|
+
);
|
|
4633
|
+
} catch (error) {
|
|
4634
|
+
if (!mounted) return;
|
|
4635
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
4636
|
+
SnackBar(content: Text(controller.friendlyErrorMessage(error))),
|
|
4637
|
+
);
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4558
4641
|
@override
|
|
4559
4642
|
void didUpdateWidget(covariant TasksPanel oldWidget) {
|
|
4560
4643
|
super.didUpdateWidget(oldWidget);
|
|
@@ -4703,6 +4786,13 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4703
4786
|
label: task.enabled ? 'Active' : 'Paused',
|
|
4704
4787
|
color: task.enabled ? _success : _textSecondary,
|
|
4705
4788
|
),
|
|
4789
|
+
if (task.hasLastRunStatus) ...<Widget>[
|
|
4790
|
+
const SizedBox(width: 8),
|
|
4791
|
+
_StatusPill(
|
|
4792
|
+
label: 'Last: ${task.lastRunStatusLabel}',
|
|
4793
|
+
color: _taskRunStatusColor(task.lastRunStatus),
|
|
4794
|
+
),
|
|
4795
|
+
],
|
|
4706
4796
|
],
|
|
4707
4797
|
),
|
|
4708
4798
|
const SizedBox(height: 10),
|
|
@@ -4734,6 +4824,11 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4734
4824
|
style: TextStyle(color: _textSecondary),
|
|
4735
4825
|
),
|
|
4736
4826
|
],
|
|
4827
|
+
if (task.lastRunFailed &&
|
|
4828
|
+
task.lastRunError.trim().isNotEmpty) ...<Widget>[
|
|
4829
|
+
const SizedBox(height: 8),
|
|
4830
|
+
Text(task.lastRunError, style: TextStyle(color: _danger)),
|
|
4831
|
+
],
|
|
4737
4832
|
const SizedBox(height: 14),
|
|
4738
4833
|
Wrap(
|
|
4739
4834
|
spacing: 10,
|
|
@@ -4753,6 +4848,11 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4753
4848
|
: () => controller.runTaskNow(task.id),
|
|
4754
4849
|
child: Text(_manualRunButtonLabel('Run Now', remaining)),
|
|
4755
4850
|
),
|
|
4851
|
+
if (task.lastRunId.trim().isNotEmpty)
|
|
4852
|
+
OutlinedButton(
|
|
4853
|
+
onPressed: () => _showLastRun(task),
|
|
4854
|
+
child: const Text('View last run'),
|
|
4855
|
+
),
|
|
4756
4856
|
OutlinedButton(
|
|
4757
4857
|
onPressed: () => _confirmDelete(
|
|
4758
4858
|
context,
|
|
@@ -4805,6 +4905,13 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4805
4905
|
label: task.enabled ? 'Active' : 'Paused',
|
|
4806
4906
|
color: task.enabled ? _success : _textSecondary,
|
|
4807
4907
|
),
|
|
4908
|
+
if (task.hasLastRunStatus) ...<Widget>[
|
|
4909
|
+
const SizedBox(width: 8),
|
|
4910
|
+
_StatusPill(
|
|
4911
|
+
label: 'Last: ${task.lastRunStatusLabel}',
|
|
4912
|
+
color: _taskRunStatusColor(task.lastRunStatus),
|
|
4913
|
+
),
|
|
4914
|
+
],
|
|
4808
4915
|
],
|
|
4809
4916
|
),
|
|
4810
4917
|
const SizedBox(height: 10),
|
|
@@ -4839,6 +4946,11 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4839
4946
|
style: TextStyle(color: _textSecondary),
|
|
4840
4947
|
),
|
|
4841
4948
|
],
|
|
4949
|
+
if (task.lastRunFailed &&
|
|
4950
|
+
task.lastRunError.trim().isNotEmpty) ...<Widget>[
|
|
4951
|
+
const SizedBox(height: 8),
|
|
4952
|
+
Text(task.lastRunError, style: TextStyle(color: _danger)),
|
|
4953
|
+
],
|
|
4842
4954
|
const SizedBox(height: 14),
|
|
4843
4955
|
Wrap(
|
|
4844
4956
|
spacing: 10,
|
|
@@ -4868,6 +4980,11 @@ class _TasksPanelState extends State<TasksPanel> {
|
|
|
4868
4980
|
_manualRunButtonLabel('Refresh Now', remaining),
|
|
4869
4981
|
),
|
|
4870
4982
|
),
|
|
4983
|
+
if (task.lastRunId.trim().isNotEmpty)
|
|
4984
|
+
OutlinedButton(
|
|
4985
|
+
onPressed: () => _showLastRun(task),
|
|
4986
|
+
child: const Text('View last run'),
|
|
4987
|
+
),
|
|
4871
4988
|
OutlinedButton(
|
|
4872
4989
|
onPressed: linkedWidget == null
|
|
4873
4990
|
? null
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ddef1ad4fea366bdfef04805d0a9528b
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27d
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1175396021" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|