neoagent 2.4.2-beta.4 → 2.4.2-beta.6

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 (62) hide show
  1. package/.env.example +0 -9
  2. package/README.md +48 -18
  3. package/docs/configuration.md +0 -10
  4. package/flutter_app/lib/main.dart +0 -1
  5. package/flutter_app/lib/main_controller.dart +0 -160
  6. package/flutter_app/lib/main_models.dart +28 -0
  7. package/flutter_app/lib/main_operations.dart +17 -1
  8. package/flutter_app/lib/main_runtime.dart +0 -22
  9. package/flutter_app/lib/main_settings.dart +9 -0
  10. package/flutter_app/lib/main_shared.dart +0 -161
  11. package/flutter_app/lib/src/backend_client.dart +8 -0
  12. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
  13. package/flutter_app/pubspec.lock +0 -8
  14. package/flutter_app/pubspec.yaml +0 -1
  15. package/package.json +1 -1
  16. package/server/admin/admin.css +23 -0
  17. package/server/admin/admin.js +17 -19
  18. package/server/admin/analytics.js +254 -48
  19. package/server/admin/index.html +9 -2
  20. package/server/db/database.js +112 -0
  21. package/server/http/routes.js +1 -0
  22. package/server/public/.last_build_id +1 -1
  23. package/server/public/assets/AssetManifest.bin +1 -1
  24. package/server/public/assets/AssetManifest.bin.json +1 -1
  25. package/server/public/assets/NOTICES +0 -183
  26. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  27. package/server/public/flutter_bootstrap.js +2 -2
  28. package/server/public/main.dart.js +77723 -78469
  29. package/server/routes/admin.js +73 -24
  30. package/server/routes/mcp.js +9 -0
  31. package/server/routes/memory.js +35 -2
  32. package/server/routes/runtime.js +5 -2
  33. package/server/routes/settings.js +34 -1
  34. package/server/routes/skills.js +42 -0
  35. package/server/routes/task_webhooks.js +65 -0
  36. package/server/services/ai/engine.js +525 -24
  37. package/server/services/ai/learning.js +56 -8
  38. package/server/services/ai/models.js +4 -4
  39. package/server/services/ai/providers/anthropic.js +40 -8
  40. package/server/services/ai/providers/google.js +10 -0
  41. package/server/services/ai/providers/openai.js +3 -15
  42. package/server/services/ai/providers/openaiCompatible.js +11 -0
  43. package/server/services/ai/repetitionGuard.js +60 -0
  44. package/server/services/ai/systemPrompt.js +52 -16
  45. package/server/services/ai/taskAnalysis.js +15 -6
  46. package/server/services/ai/toolEvidence.js +3 -1
  47. package/server/services/ai/toolRunner.js +43 -1
  48. package/server/services/ai/toolSelector.js +92 -46
  49. package/server/services/ai/tools.js +89 -9
  50. package/server/services/ai/usage.js +114 -0
  51. package/server/services/manager.js +34 -0
  52. package/server/services/memory/manager.js +170 -4
  53. package/server/services/security/capability_audit.js +107 -0
  54. package/server/services/tasks/adapters/index.js +1 -0
  55. package/server/services/tasks/adapters/webhook.js +14 -0
  56. package/server/services/tasks/runtime.js +1 -1
  57. package/server/services/tasks/webhooks.js +146 -0
  58. package/server/services/workspace/code_navigation.js +194 -0
  59. package/server/services/workspace/structured_data.js +93 -0
  60. package/server/utils/cloud-security.js +24 -2
  61. package/flutter_app/lib/src/analytics_service.dart +0 -294
  62. package/server/config/analytics.js +0 -30
@@ -2393,167 +2393,6 @@ class _GlobalWebUpdateBanner extends StatelessWidget {
2393
2393
  }
2394
2394
  }
2395
2395
 
2396
- class _GlobalAnalyticsConsentBanner extends StatelessWidget {
2397
- const _GlobalAnalyticsConsentBanner({required this.controller});
2398
-
2399
- final NeoAgentController controller;
2400
-
2401
- @override
2402
- Widget build(BuildContext context) {
2403
- return LayoutBuilder(
2404
- builder: (context, constraints) {
2405
- final compact = constraints.maxWidth < 700;
2406
- final body = Text(
2407
- 'NeoAgent uses anonymous analytics to understand startup, navigation, and setup flows. No messages, credentials, or personal content are collected.',
2408
- style: TextStyle(color: _textSecondary, height: 1.35),
2409
- );
2410
- return Material(
2411
- color: Colors.transparent,
2412
- child: _GlassSurface(
2413
- borderRadius: BorderRadius.circular(22),
2414
- blurSigma: 26,
2415
- fillColor: _bgCard.withValues(alpha: 0.94),
2416
- borderColor: _accent.withValues(alpha: 0.18),
2417
- overlayGradient: LinearGradient(
2418
- colors: <Color>[
2419
- _accent.withValues(alpha: 0.12),
2420
- _bgCard.withValues(alpha: 0.88),
2421
- _bgSecondary.withValues(alpha: 0.86),
2422
- ],
2423
- stops: const <double>[0, 0.22, 1],
2424
- begin: const Alignment(-1, -1),
2425
- end: const Alignment(1, 1),
2426
- ),
2427
- boxShadow: <BoxShadow>[
2428
- BoxShadow(
2429
- color: Colors.black.withValues(alpha: 0.22),
2430
- blurRadius: 34,
2431
- offset: const Offset(0, 18),
2432
- ),
2433
- BoxShadow(
2434
- color: _accent.withValues(alpha: 0.1),
2435
- blurRadius: 24,
2436
- offset: const Offset(0, 8),
2437
- ),
2438
- ],
2439
- padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
2440
- child: compact
2441
- ? Column(
2442
- crossAxisAlignment: CrossAxisAlignment.start,
2443
- children: <Widget>[
2444
- Row(
2445
- crossAxisAlignment: CrossAxisAlignment.start,
2446
- children: <Widget>[
2447
- Container(
2448
- padding: const EdgeInsets.all(10),
2449
- decoration: BoxDecoration(
2450
- color: _accent.withValues(alpha: 0.16),
2451
- shape: BoxShape.circle,
2452
- border: Border.all(
2453
- color: _accent.withValues(alpha: 0.24),
2454
- ),
2455
- ),
2456
- child: Icon(
2457
- Icons.cookie_outlined,
2458
- color: _accent,
2459
- size: 22,
2460
- ),
2461
- ),
2462
- const SizedBox(width: 12),
2463
- Expanded(
2464
- child: Column(
2465
- crossAxisAlignment: CrossAxisAlignment.start,
2466
- children: <Widget>[
2467
- Text(
2468
- 'Analytics cookies',
2469
- style: TextStyle(
2470
- color: _textPrimary,
2471
- fontSize: 15,
2472
- fontWeight: FontWeight.w700,
2473
- ),
2474
- ),
2475
- const SizedBox(height: 6),
2476
- body,
2477
- ],
2478
- ),
2479
- ),
2480
- ],
2481
- ),
2482
- const SizedBox(height: 14),
2483
- Row(
2484
- children: <Widget>[
2485
- Expanded(
2486
- child: OutlinedButton(
2487
- onPressed: controller.declineAnalyticsConsent,
2488
- child: const Text('Decline'),
2489
- ),
2490
- ),
2491
- const SizedBox(width: 10),
2492
- Expanded(
2493
- child: FilledButton(
2494
- onPressed: controller.acceptAnalyticsConsent,
2495
- child: const Text('Allow analytics'),
2496
- ),
2497
- ),
2498
- ],
2499
- ),
2500
- ],
2501
- )
2502
- : Row(
2503
- crossAxisAlignment: CrossAxisAlignment.center,
2504
- children: <Widget>[
2505
- Container(
2506
- padding: const EdgeInsets.all(10),
2507
- decoration: BoxDecoration(
2508
- color: _accent.withValues(alpha: 0.16),
2509
- shape: BoxShape.circle,
2510
- border: Border.all(
2511
- color: _accent.withValues(alpha: 0.24),
2512
- ),
2513
- ),
2514
- child: Icon(
2515
- Icons.cookie_outlined,
2516
- color: _accent,
2517
- size: 22,
2518
- ),
2519
- ),
2520
- const SizedBox(width: 14),
2521
- Expanded(
2522
- child: Column(
2523
- crossAxisAlignment: CrossAxisAlignment.start,
2524
- children: <Widget>[
2525
- Text(
2526
- 'Analytics cookies',
2527
- style: TextStyle(
2528
- color: _textPrimary,
2529
- fontSize: 15,
2530
- fontWeight: FontWeight.w700,
2531
- ),
2532
- ),
2533
- const SizedBox(height: 6),
2534
- body,
2535
- ],
2536
- ),
2537
- ),
2538
- const SizedBox(width: 16),
2539
- OutlinedButton(
2540
- onPressed: controller.declineAnalyticsConsent,
2541
- child: const Text('Decline'),
2542
- ),
2543
- const SizedBox(width: 10),
2544
- FilledButton(
2545
- onPressed: controller.acceptAnalyticsConsent,
2546
- child: const Text('Allow analytics'),
2547
- ),
2548
- ],
2549
- ),
2550
- ),
2551
- );
2552
- },
2553
- );
2554
- }
2555
- }
2556
-
2557
2396
  class _DesktopCloseDecision {
2558
2397
  const _DesktopCloseDecision({
2559
2398
  required this.keepRunning,
@@ -1055,6 +1055,14 @@ class BackendClient {
1055
1055
  return getList(baseUrl, '/api/skills');
1056
1056
  }
1057
1057
 
1058
+ Future<List<Map<String, dynamic>>> fetchSkillMetrics(String baseUrl) {
1059
+ return getList(baseUrl, '/api/skills/metrics/summary');
1060
+ }
1061
+
1062
+ Future<List<Map<String, dynamic>>> fetchSkillAudit(String baseUrl) {
1063
+ return getList(baseUrl, '/api/skills/audit/summary');
1064
+ }
1065
+
1058
1066
  Future<List<Map<String, dynamic>>> fetchSkillStore(String baseUrl) async {
1059
1067
  return getList(baseUrl, '/api/store');
1060
1068
  }
@@ -12,7 +12,6 @@ import file_picker
12
12
  import flutter_secure_storage_macos
13
13
  import geolocator_apple
14
14
  import hotkey_manager_macos
15
- import mixpanel_flutter
16
15
  import mobile_scanner
17
16
  import package_info_plus
18
17
  import path_provider_foundation
@@ -33,7 +32,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
33
32
  FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
34
33
  GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
35
34
  HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
36
- MixpanelFlutterPlugin.register(with: registry.registrar(forPlugin: "MixpanelFlutterPlugin"))
37
35
  MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
38
36
  FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
39
37
  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
@@ -598,14 +598,6 @@ packages:
598
598
  url: "https://pub.dev"
599
599
  source: hosted
600
600
  version: "1.18.0"
601
- mixpanel_flutter:
602
- dependency: "direct main"
603
- description:
604
- name: mixpanel_flutter
605
- sha256: "6962b8ce61eb6202d6545bee648e21a7bc9f7e804fa293912e9673329f499ece"
606
- url: "https://pub.dev"
607
- source: hosted
608
- version: "2.7.0"
609
601
  mobile_scanner:
610
602
  dependency: "direct main"
611
603
  description:
@@ -15,7 +15,6 @@ dependencies:
15
15
  google_fonts: ^8.1.0
16
16
  image: ^4.5.4
17
17
  http: ^1.5.0
18
- mixpanel_flutter: ^2.6.1
19
18
  connectivity_plus: 6.1.5
20
19
  shared_preferences: ^2.5.3
21
20
  flutter_secure_storage: ^9.2.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.4.2-beta.4",
3
+ "version": "2.4.2-beta.6",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -185,6 +185,29 @@ button:disabled,
185
185
  border: 1px solid rgba(222, 138, 120, 0.20);
186
186
  }
187
187
 
188
+ .range-btn {
189
+ padding: 5px 12px;
190
+ font-size: 12px;
191
+ font-weight: 600;
192
+ border: 1px solid var(--border);
193
+ border-radius: 6px;
194
+ background: transparent;
195
+ color: var(--text-muted);
196
+ cursor: pointer;
197
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
198
+ }
199
+
200
+ .range-btn:hover {
201
+ background: rgba(224, 240, 224, 0.07);
202
+ color: var(--text);
203
+ }
204
+
205
+ .range-btn.active {
206
+ background: var(--accent-muted);
207
+ color: var(--accent);
208
+ border-color: var(--accent);
209
+ }
210
+
188
211
  .btn-danger:hover:not(:disabled) {
189
212
  background: rgba(222, 138, 120, 0.20);
190
213
  }
@@ -355,11 +355,11 @@ async function loadModels() {
355
355
  try {
356
356
  const data = await api('/admin/api/models').then((r) => r.json());
357
357
  const models = data.models || [];
358
- const enabledModels = data.enabledModels || [];
358
+ const disabledSet = new Set(data.disabledModels || []);
359
359
 
360
- if (!models.length) { el.innerHTML = '<div class="empty">No models found</div>'; return; }
360
+ if (!models.length) { el.innerHTML = '<div class="empty">No models found — configure providers first.</div>'; return; }
361
361
 
362
- // Group by provider for better readability
362
+ // Sort: provider alpha, then price low→high within provider
363
363
  models.sort((a, b) => {
364
364
  if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
365
365
  const ac = a.inputCostPerM ?? Infinity;
@@ -367,12 +367,12 @@ async function loadModels() {
367
367
  return ac - bc;
368
368
  });
369
369
 
370
- const enabledCount = enabledModels.length === 0 ? models.length : enabledModels.length;
370
+ const enabledCount = models.length - disabledSet.size;
371
371
 
372
372
  let html = `
373
373
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;">
374
- <button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(true)">Select All</button>
375
- <button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(false)">Select None</button>
374
+ <button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(true)">Enable All</button>
375
+ <button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(false)">Disable All</button>
376
376
  <span style="margin-left:4px;font-size:12px;color:var(--text-muted);" id="model-count-label">${enabledCount} of ${models.length} enabled</span>
377
377
  </div>
378
378
  <table class="users-table">
@@ -387,15 +387,14 @@ async function loadModels() {
387
387
  <tbody>`;
388
388
 
389
389
  for (const m of models) {
390
- const isChecked = enabledModels.length === 0 || enabledModels.includes(m.id);
391
- const rowOpacity = isChecked ? '1' : '0.45';
390
+ const isEnabled = !disabledSet.has(m.id);
392
391
  const priceStr = fmtModelPrice(m.inputCostPerM);
393
392
  const tierCls = priceTierClass(m.priceTier);
394
393
  const icon = purposeIcon(m.purpose);
395
394
  html += `
396
- <tr style="opacity:${rowOpacity}" id="model-row-${esc(m.id)}">
395
+ <tr style="opacity:${isEnabled ? '1' : '0.45'}">
397
396
  <td style="text-align:center;">
398
- <input type="checkbox" class="model-cb" value="${esc(m.id)}" ${isChecked ? 'checked' : ''}
397
+ <input type="checkbox" class="model-cb" value="${esc(m.id)}" ${isEnabled ? 'checked' : ''}
399
398
  onchange="onModelToggle(this)">
400
399
  </td>
401
400
  <td>
@@ -403,7 +402,7 @@ async function loadModels() {
403
402
  <div style="font-size:11px;color:var(--text-muted);font-family:var(--font-mono);margin-top:2px;">${esc(m.id)}</div>
404
403
  </td>
405
404
  <td style="font-size:13px;text-transform:capitalize;">${esc(m.provider)}</td>
406
- <td><span class="badge badge-idle" style="gap:4px;">${icon} ${esc(m.purpose)}</span></td>
405
+ <td><span class="badge badge-idle">${icon} ${esc(m.purpose)}</span></td>
407
406
  <td><span class="badge ${tierCls}">${esc(m.priceTier ?? '?')}</span></td>
408
407
  <td style="text-align:right;font-family:var(--font-mono);font-size:13px;font-weight:600;color:var(--text);">${priceStr}</td>
409
408
  </tr>
@@ -442,20 +441,19 @@ function toggleAllModels(enable) {
442
441
  async function saveEnabledModels(btn) {
443
442
  const cbs = document.querySelectorAll('.model-cb');
444
443
  if (!cbs.length) return;
445
-
446
- // If all are checked, we can save an empty list to mean "all enabled"
447
- const allChecked = Array.from(cbs).every(cb => cb.checked);
448
- const enabledModels = allChecked ? [] : Array.from(cbs).filter(cb => cb.checked).map(cb => cb.value);
449
-
444
+
445
+ // Persist only the disabled (unchecked) models
446
+ const disabledModels = Array.from(cbs).filter(cb => !cb.checked).map(cb => cb.value);
447
+
450
448
  btn.disabled = true;
451
449
  const original = btn.textContent;
452
450
  btn.textContent = 'Saving…';
453
-
451
+
454
452
  try {
455
- const res = await api('/admin/api/models/enabled', {
453
+ const res = await api('/admin/api/models/config', {
456
454
  method: 'PUT',
457
455
  headers: { 'Content-Type': 'application/json' },
458
- body: JSON.stringify({ enabledModels }),
456
+ body: JSON.stringify({ disabledModels }),
459
457
  });
460
458
  if (!res.ok) {
461
459
  const body = await res.json().catch(() => ({}));