neoagent 2.4.4-beta.8 → 2.5.0

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 (52) hide show
  1. package/.env.example +17 -0
  2. package/README.md +64 -57
  3. package/docs/agent-run-lifecycle.md +65 -0
  4. package/docs/agents-and-users.md +48 -0
  5. package/docs/architecture.md +59 -0
  6. package/docs/automation-architecture.md +50 -0
  7. package/docs/automation.md +54 -70
  8. package/docs/capabilities.md +16 -118
  9. package/docs/clients-and-devices.md +43 -0
  10. package/docs/configuration.md +104 -116
  11. package/docs/development.md +82 -0
  12. package/docs/devices.md +62 -0
  13. package/docs/getting-started.md +47 -57
  14. package/docs/index.md +41 -30
  15. package/docs/integrations-architecture.md +48 -0
  16. package/docs/integrations.md +56 -51
  17. package/docs/memory-architecture.md +90 -0
  18. package/docs/memory.md +78 -0
  19. package/docs/migration.md +12 -5
  20. package/docs/models.md +64 -0
  21. package/docs/operations.md +46 -30
  22. package/docs/persistence.md +49 -0
  23. package/docs/recordings-and-health.md +41 -0
  24. package/docs/runtime-and-tools.md +52 -0
  25. package/docs/security-boundaries.md +65 -111
  26. package/docs/skills.md +32 -45
  27. package/docs/why-neoagent.md +47 -16
  28. package/flutter_app/lib/main_account_settings.dart +79 -15
  29. package/flutter_app/lib/main_chat.dart +155 -8
  30. package/flutter_app/lib/main_controller.dart +38 -4
  31. package/flutter_app/lib/main_devices.dart +9 -3
  32. package/flutter_app/lib/main_models.dart +32 -0
  33. package/landing/index.html +0 -4
  34. package/lib/install_helpers.js +1 -0
  35. package/lib/manager.js +63 -1
  36. package/package.json +2 -2
  37. package/server/admin/admin.js +151 -0
  38. package/server/admin/index.html +55 -3
  39. package/server/public/.last_build_id +1 -1
  40. package/server/public/flutter_bootstrap.js +1 -1
  41. package/server/public/main.dart.js +50995 -50850
  42. package/server/routes/account.js +2 -23
  43. package/server/routes/admin.js +18 -2
  44. package/server/routes/agents.js +5 -1
  45. package/server/routes/memory.js +2 -2
  46. package/server/services/account/service_email_settings.js +167 -0
  47. package/server/services/ai/engine.js +2 -17
  48. package/server/services/ai/rate_limits.js +150 -0
  49. package/server/services/ai/tools.js +11 -8
  50. package/server/services/memory/manager.js +0 -3
  51. package/server/services/websocket.js +5 -1
  52. package/docs/supermemory-memory-review.md +0 -852
@@ -5,6 +5,7 @@ function withInstallEnv(extraEnv = {}) {
5
5
  return {
6
6
  ...process.env,
7
7
  PUPPETEER_SKIP_DOWNLOAD: process.env.PUPPETEER_SKIP_DOWNLOAD || 'true',
8
+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD || '1',
8
9
  ...extraEnv,
9
10
  };
10
11
  }
package/lib/manager.js CHANGED
@@ -1593,7 +1593,12 @@ async function cmdInstall() {
1593
1593
  if (platform === 'macos' && commandExists('launchctl')) {
1594
1594
  installMacService();
1595
1595
  } else if (platform === 'linux' && commandExists('systemctl')) {
1596
- installLinuxService();
1596
+ try {
1597
+ installLinuxService();
1598
+ } catch (err) {
1599
+ logWarn(`systemd setup failed (${err.message}); falling back to detached process`);
1600
+ startFallback();
1601
+ }
1597
1602
  } else {
1598
1603
  startFallback();
1599
1604
  }
@@ -1693,6 +1698,59 @@ async function cmdRebuildWeb() {
1693
1698
  buildBundledWebClientIfPossible();
1694
1699
  }
1695
1700
 
1701
+ async function cmdFix() {
1702
+ cliBanner(`Fix ${APP_NAME}`, 'reset and recover');
1703
+ heading('Backup');
1704
+ backupRuntimeData();
1705
+ logOk(`Runtime data backed up to ${path.join(RUNTIME_HOME, 'backups')}`);
1706
+
1707
+ heading('Stop');
1708
+ cmdStop();
1709
+
1710
+ if (fs.existsSync(path.join(APP_DIR, '.git')) && commandExists('git')) {
1711
+ heading('Reset source files');
1712
+ const dirty = runQuiet('git', ['status', '--porcelain']);
1713
+ if (dirty.status === 0 && dirty.stdout.trim()) {
1714
+ runOrThrow('git', ['checkout', '--', '.']);
1715
+ logOk('Tracked source files reset to HEAD');
1716
+ } else {
1717
+ logOk('Working tree clean — nothing to reset');
1718
+ }
1719
+ }
1720
+
1721
+ heading('Dependencies');
1722
+ const nodeModulesDir = path.join(APP_DIR, 'node_modules');
1723
+ if (fs.existsSync(nodeModulesDir)) {
1724
+ logInfo('Removing node_modules…');
1725
+ fs.rmSync(nodeModulesDir, { recursive: true, force: true });
1726
+ logOk('node_modules removed');
1727
+ }
1728
+ installDependencies();
1729
+
1730
+ heading('Web Client');
1731
+ buildBundledWebClientIfPossible({ required: true });
1732
+
1733
+ heading('Start');
1734
+ const platform = detectPlatform();
1735
+ if (platform === 'macos' && commandExists('launchctl')) {
1736
+ installMacService();
1737
+ } else if (platform === 'linux' && commandExists('systemctl')) {
1738
+ try {
1739
+ installLinuxService();
1740
+ } catch (err) {
1741
+ logWarn(`systemd setup failed (${err.message}); falling back to detached process`);
1742
+ startFallback();
1743
+ }
1744
+ } else {
1745
+ startFallback();
1746
+ }
1747
+
1748
+ const port = loadEnvPort();
1749
+ logOk(`Running on http://localhost:${port}`);
1750
+ heading('Ready');
1751
+ logInfo('Fix complete. Run `neoagent status` to verify.');
1752
+ }
1753
+
1696
1754
  function cmdUninstall() {
1697
1755
  heading(`Uninstall ${APP_NAME}`);
1698
1756
  const platform = detectPlatform();
@@ -1962,6 +2020,7 @@ function printHelp() {
1962
2020
  row('restart', 'Stop, then start');
1963
2021
  row('status', 'Health overview (server, service, config)');
1964
2022
  row('logs', 'Tail server logs');
2023
+ row('fix', 'Backup, reset source, reinstall deps, restart');
1965
2024
  row('uninstall', 'Remove the system service');
1966
2025
  console.log('');
1967
2026
 
@@ -2035,6 +2094,9 @@ async function runCLI(argv) {
2035
2094
  case 'logs':
2036
2095
  cmdLogs();
2037
2096
  break;
2097
+ case 'fix':
2098
+ await cmdFix();
2099
+ break;
2038
2100
  case 'uninstall':
2039
2101
  cmdUninstall();
2040
2102
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.4.4-beta.8",
3
+ "version": "2.5.0",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -35,7 +35,7 @@
35
35
  "dev:test": "./dev/test.sh",
36
36
  "docs:dev": "docusaurus start",
37
37
  "docs:build": "docusaurus build",
38
- "docs:preview": "docusaurus serve build",
38
+ "docs:preview": "docusaurus serve --dir build",
39
39
  "flutter:run:web": "cd flutter_app && flutter run -d chrome --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
40
40
  "flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-} --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
41
41
  "manage": "node bin/neoagent.js",
@@ -225,6 +225,7 @@ async function triggerUpdate() {
225
225
  async function loadConfig() {
226
226
  const el = document.getElementById('config-content');
227
227
  if (!el) return;
228
+ loadEmailConfig();
228
229
  try {
229
230
  const data = await api('/admin/api/config').then((r) => r.json());
230
231
  const cfg = data.config || {};
@@ -246,6 +247,156 @@ async function loadConfig() {
246
247
  }
247
248
  }
248
249
 
250
+ async function loadEmailConfig() {
251
+ const el = document.getElementById('email-config-content');
252
+ if (!el) return;
253
+ try {
254
+ const data = await api('/admin/api/config/email').then((r) => r.json());
255
+ const s = data.settings || {};
256
+ const status = data.configured
257
+ ? '<span class="badge badge-ok">configured</span><span>Account emails are enabled.</span>'
258
+ : `<span class="badge badge-idle">not configured</span><span>Missing: ${esc((data.missing || []).join(', '))}</span>`;
259
+
260
+ el.innerHTML = `
261
+ <div class="email-settings-status">${status}</div>
262
+ <form id="email-settings-form" onsubmit="saveEmailConfig(event)">
263
+ <div class="email-settings-grid">
264
+ <div class="field field-wide">
265
+ <label for="email-from">Sender address</label>
266
+ <input type="text" id="email-from" value="${esc(s.from)}" autocomplete="off">
267
+ </div>
268
+ <div class="field">
269
+ <label for="email-smtp-host">SMTP host</label>
270
+ <input type="text" id="email-smtp-host" value="${esc(s.smtpHost)}" autocomplete="off" spellcheck="false">
271
+ </div>
272
+ <div class="field">
273
+ <label for="email-smtp-port">SMTP port</label>
274
+ <input type="text" inputmode="numeric" id="email-smtp-port" value="${esc(s.smtpPort)}" autocomplete="off">
275
+ </div>
276
+ <div class="field">
277
+ <label for="email-smtp-user">SMTP username</label>
278
+ <input type="text" id="email-smtp-user" value="${esc(s.smtpUser)}" autocomplete="off" spellcheck="false">
279
+ </div>
280
+ <div class="field">
281
+ <label for="email-smtp-password">SMTP password</label>
282
+ <input type="password" id="email-smtp-password" value="" autocomplete="new-password">
283
+ <div style="font-size:11px;color:var(--text-muted);margin-top:6px;">
284
+ ${s.smtpPasswordConfigured ? 'A password is stored. Leave blank to keep it.' : 'No password is stored.'}
285
+ </div>
286
+ </div>
287
+ <div class="field">
288
+ <label for="email-reply-to">Reply-To address</label>
289
+ <input type="text" id="email-reply-to" value="${esc(s.replyTo)}" autocomplete="off">
290
+ </div>
291
+ <div class="field">
292
+ <label for="email-brand-name">Brand name</label>
293
+ <input type="text" id="email-brand-name" value="${esc(s.brandName)}" autocomplete="off">
294
+ </div>
295
+ <div class="field">
296
+ <label for="email-public-url">Public URL override</label>
297
+ <input type="text" id="email-public-url" value="${esc(s.publicUrl)}" autocomplete="off" spellcheck="false">
298
+ </div>
299
+ <div class="field">
300
+ <label for="email-support-url">Support URL</label>
301
+ <input type="text" id="email-support-url" value="${esc(s.supportUrl)}" autocomplete="off" spellcheck="false">
302
+ </div>
303
+ <div class="field">
304
+ <label for="email-token-ttl">Link lifetime (hours)</label>
305
+ <input type="text" inputmode="numeric" id="email-token-ttl" value="${esc(s.tokenTtlHours)}" autocomplete="off">
306
+ </div>
307
+ </div>
308
+ <div class="email-settings-checks">
309
+ ${emailConfigCheckbox('email-smtp-secure', 'Implicit TLS', s.smtpSecure)}
310
+ ${emailConfigCheckbox('email-smtp-require-tls', 'Require STARTTLS', s.smtpRequireTls)}
311
+ ${emailConfigCheckbox('email-smtp-reject-unauthorized', 'Reject invalid TLS certificates', s.smtpRejectUnauthorized)}
312
+ ${emailConfigCheckbox('email-signup-confirmation', 'Require signup confirmation', s.requireSignupConfirmation)}
313
+ ${emailConfigCheckbox('email-change-confirmation', 'Require email change confirmation', s.requireEmailChangeConfirmation)}
314
+ ${emailConfigCheckbox('email-notify-login', 'Notify on unusual login', s.notifyUnusualLogin)}
315
+ ${emailConfigCheckbox('email-notify-account', 'Notify on account changes', s.notifyAccountChanges)}
316
+ </div>
317
+ <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
318
+ <button class="btn btn-primary" type="submit">Save Email Settings</button>
319
+ ${s.smtpPasswordConfigured ? '<button class="btn btn-danger" type="button" onclick="clearEmailPassword(this)">Remove SMTP Password</button>' : ''}
320
+ </div>
321
+ </form>`;
322
+ } catch (err) {
323
+ if (err.message !== 'unauthorized') {
324
+ el.innerHTML = '<div class="empty">Failed to load email configuration</div>';
325
+ }
326
+ }
327
+ }
328
+
329
+ function emailConfigCheckbox(id, label, checked) {
330
+ return `<label class="email-setting-check" for="${id}">
331
+ <input type="checkbox" id="${id}" ${checked ? 'checked' : ''}>
332
+ <span>${esc(label)}</span>
333
+ </label>`;
334
+ }
335
+
336
+ function emailConfigValue(id) {
337
+ return document.getElementById(id)?.value?.trim() || '';
338
+ }
339
+
340
+ function collectEmailConfig(clearSmtpPassword = false) {
341
+ return {
342
+ from: emailConfigValue('email-from'),
343
+ smtpHost: emailConfigValue('email-smtp-host'),
344
+ smtpPort: emailConfigValue('email-smtp-port'),
345
+ smtpUser: emailConfigValue('email-smtp-user'),
346
+ smtpPassword: clearSmtpPassword ? '' : emailConfigValue('email-smtp-password'),
347
+ clearSmtpPassword,
348
+ smtpSecure: document.getElementById('email-smtp-secure')?.checked === true,
349
+ smtpRequireTls: document.getElementById('email-smtp-require-tls')?.checked === true,
350
+ smtpRejectUnauthorized: document.getElementById('email-smtp-reject-unauthorized')?.checked === true,
351
+ replyTo: emailConfigValue('email-reply-to'),
352
+ requireSignupConfirmation: document.getElementById('email-signup-confirmation')?.checked === true,
353
+ requireEmailChangeConfirmation: document.getElementById('email-change-confirmation')?.checked === true,
354
+ notifyUnusualLogin: document.getElementById('email-notify-login')?.checked === true,
355
+ notifyAccountChanges: document.getElementById('email-notify-account')?.checked === true,
356
+ brandName: emailConfigValue('email-brand-name'),
357
+ supportUrl: emailConfigValue('email-support-url'),
358
+ publicUrl: emailConfigValue('email-public-url'),
359
+ tokenTtlHours: emailConfigValue('email-token-ttl'),
360
+ };
361
+ }
362
+
363
+ async function persistEmailConfig(payload, btn) {
364
+ const original = btn?.textContent;
365
+ if (btn) {
366
+ btn.disabled = true;
367
+ btn.textContent = 'Saving…';
368
+ }
369
+ try {
370
+ const res = await api('/admin/api/config/email', {
371
+ method: 'PUT',
372
+ headers: { 'Content-Type': 'application/json' },
373
+ body: JSON.stringify(payload),
374
+ });
375
+ const body = await res.json().catch(() => ({}));
376
+ if (!res.ok) {
377
+ alert(body.error || 'Failed to save email settings');
378
+ return;
379
+ }
380
+ await loadEmailConfig();
381
+ } catch (err) {
382
+ if (err.message !== 'unauthorized') alert('Network error');
383
+ } finally {
384
+ if (btn?.isConnected) {
385
+ btn.disabled = false;
386
+ btn.textContent = original;
387
+ }
388
+ }
389
+ }
390
+
391
+ async function saveEmailConfig(event) {
392
+ event.preventDefault();
393
+ await persistEmailConfig(collectEmailConfig(), event.submitter);
394
+ }
395
+
396
+ async function clearEmailPassword(btn) {
397
+ await persistEmailConfig(collectEmailConfig(true), btn);
398
+ }
399
+
249
400
  // ── Providers ──────────────────────────────────────────────────────────────
250
401
 
251
402
  async function loadProviders() {
@@ -326,6 +326,54 @@
326
326
  color: var(--text-muted) !important;
327
327
  }
328
328
 
329
+ .email-settings-grid {
330
+ display: grid;
331
+ grid-template-columns: repeat(2, minmax(0, 1fr));
332
+ gap: 0 16px;
333
+ }
334
+
335
+ .email-settings-grid .field-wide {
336
+ grid-column: 1 / -1;
337
+ }
338
+
339
+ .email-settings-checks {
340
+ display: grid;
341
+ grid-template-columns: repeat(2, minmax(0, 1fr));
342
+ gap: 10px 16px;
343
+ margin: 4px 0 20px;
344
+ }
345
+
346
+ .email-setting-check {
347
+ display: flex;
348
+ align-items: center;
349
+ gap: 9px;
350
+ color: var(--text-secondary);
351
+ font-size: 13px;
352
+ text-transform: none;
353
+ letter-spacing: 0;
354
+ margin: 0;
355
+ }
356
+
357
+ .email-setting-check input {
358
+ accent-color: var(--accent);
359
+ }
360
+
361
+ .email-settings-status {
362
+ display: flex;
363
+ align-items: center;
364
+ gap: 9px;
365
+ margin-bottom: 18px;
366
+ color: var(--text-muted);
367
+ font-size: 12px;
368
+ }
369
+
370
+ @media (max-width: 760px) {
371
+ .email-settings-grid,
372
+ .email-settings-checks {
373
+ grid-template-columns: 1fr;
374
+ }
375
+ }
376
+
329
377
  /* ── Stats grid ────────────────────────────────────────────── */
330
378
  .stat-grid {
331
379
  display: grid;
@@ -948,7 +996,7 @@
948
996
  <div>
949
997
  <div style="font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;">Default Rate Limits</div>
950
998
  <div style="font-size:12px;color:var(--text-muted);max-width:480px;">
951
- Applied to every user unless they have a custom override. Leave empty for no limit.
999
+ Applied to every user unless they have a custom override. Empty values restore the built-in defaults.
952
1000
  </div>
953
1001
  </div>
954
1002
  <button class="btn btn-primary" style="flex-shrink:0;" onclick="saveDefaultRateLimits(this)">Save Defaults</button>
@@ -956,12 +1004,12 @@
956
1004
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:18px;" id="default-limits-fields">
957
1005
  <div>
958
1006
  <label style="display:block;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);margin-bottom:6px;">4-Hour Limit (tokens)</label>
959
- <input type="number" min="0" step="1" id="default-limit-4h" placeholder="e.g. 500000 — empty = no limit"
1007
+ <input type="number" min="0" step="1" id="default-limit-4h" placeholder="Default: 2500000"
960
1008
  style="width:100%;padding:9px 12px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
961
1009
  </div>
962
1010
  <div>
963
1011
  <label style="display:block;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);margin-bottom:6px;">Weekly Limit (tokens)</label>
964
- <input type="number" min="0" step="1" id="default-limit-weekly" placeholder="e.g. 2000000 — empty = no limit"
1012
+ <input type="number" min="0" step="1" id="default-limit-weekly" placeholder="Default: 10000000"
965
1013
  style="width:100%;padding:9px 12px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;">
966
1014
  </div>
967
1015
  </div>
@@ -1051,6 +1099,10 @@
1051
1099
  </div>
1052
1100
  </div>
1053
1101
  <div class="content">
1102
+ <div class="card" style="margin-bottom:20px;">
1103
+ <div class="card-title">Service Email</div>
1104
+ <div id="email-config-content"><div class="empty"><span class="spinner"></span></div></div>
1105
+ </div>
1054
1106
  <div class="card">
1055
1107
  <div class="card-title">Environment</div>
1056
1108
  <div id="config-content"><div class="empty"><span class="spinner"></span></div></div>
@@ -1 +1 @@
1
- 7e73207d8404cf818eebed5972cebc61
1
+ 19c1d10149165130fbea47cda54945d1
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2478318604" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "1413531917" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });