neoagent 3.0.1-beta.8 → 3.1.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 (100) hide show
  1. package/.env.example +2 -10
  2. package/README.md +12 -3
  3. package/docs/automation.md +37 -0
  4. package/docs/benchmarking.md +102 -0
  5. package/docs/billing.md +34 -8
  6. package/docs/configuration.md +11 -2
  7. package/docs/getting-started.md +10 -8
  8. package/docs/operations.md +1 -1
  9. package/flutter_app/lib/main.dart +3 -0
  10. package/flutter_app/lib/main_account_settings.dart +138 -0
  11. package/flutter_app/lib/main_app_shell.dart +38 -13
  12. package/flutter_app/lib/main_billing.dart +1465 -0
  13. package/flutter_app/lib/main_chat.dart +1612 -214
  14. package/flutter_app/lib/main_controller.dart +398 -26
  15. package/flutter_app/lib/main_devices.dart +293 -207
  16. package/flutter_app/lib/main_models.dart +142 -0
  17. package/flutter_app/lib/main_navigation.dart +19 -1
  18. package/flutter_app/lib/main_operations.dart +288 -9
  19. package/flutter_app/lib/main_settings.dart +510 -300
  20. package/flutter_app/lib/main_shared.dart +2 -0
  21. package/flutter_app/lib/main_timeline.dart +1378 -0
  22. package/flutter_app/lib/src/backend_client.dart +149 -19
  23. package/flutter_app/lib/src/desktop_companion_actions.dart +132 -21
  24. package/flutter_app/lib/src/desktop_companion_io.dart +65 -1
  25. package/flutter_app/lib/src/desktop_companion_stub.dart +12 -0
  26. package/flutter_app/lib/src/desktop_ocr_bridge.dart +2 -0
  27. package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +125 -0
  28. package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +30 -0
  29. package/flutter_app/lib/src/desktop_passive_history.dart +332 -0
  30. package/flutter_app/lib/src/recording_bridge_io.dart +80 -72
  31. package/flutter_app/lib/src/recording_bridge_web.dart +127 -114
  32. package/flutter_app/lib/src/recording_chunk_queue.dart +149 -0
  33. package/flutter_app/lib/src/recording_chunk_queue_io.dart +182 -0
  34. package/flutter_app/lib/src/recording_payloads.dart +9 -0
  35. package/flutter_app/macos/Runner/AppDelegate.swift +25 -0
  36. package/flutter_app/windows/runner/flutter_window.cpp +75 -0
  37. package/landing/index.html +3 -3
  38. package/lib/manager.js +184 -66
  39. package/package.json +4 -1
  40. package/server/admin/access.js +12 -7
  41. package/server/admin/admin.css +78 -0
  42. package/server/admin/admin.js +436 -10
  43. package/server/admin/billing.js +165 -42
  44. package/server/admin/index.html +80 -5
  45. package/server/admin/users.js +15 -15
  46. package/server/db/database.js +125 -20
  47. package/server/http/routes.js +1 -0
  48. package/server/http/static.js +4 -2
  49. package/server/public/.last_build_id +1 -1
  50. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  51. package/server/public/canvaskit/wimp.js.symbols +8475 -8467
  52. package/server/public/canvaskit/wimp.wasm +0 -0
  53. package/server/public/flutter_bootstrap.js +2 -2
  54. package/server/public/main.dart.js +91077 -87037
  55. package/server/routes/account.js +53 -0
  56. package/server/routes/admin.js +345 -64
  57. package/server/routes/agents.js +203 -21
  58. package/server/routes/billing.js +5 -0
  59. package/server/routes/browser.js +8 -1
  60. package/server/routes/recordings.js +96 -6
  61. package/server/routes/screenHistory.js +140 -2
  62. package/server/routes/timeline.js +43 -0
  63. package/server/services/account/erasure.js +263 -0
  64. package/server/services/ai/hooks.js +4 -1
  65. package/server/services/ai/loop/agent_engine_core.js +8 -1
  66. package/server/services/ai/loop/blank_recovery.js +36 -0
  67. package/server/services/ai/loop/conversation_loop.js +166 -33
  68. package/server/services/ai/messagingFallback.js +22 -0
  69. package/server/services/ai/rate_limits.js +28 -5
  70. package/server/services/ai/systemPrompt.js +6 -5
  71. package/server/services/ai/taskAnalysis.js +2 -0
  72. package/server/services/ai/toolEvidence.js +15 -0
  73. package/server/services/ai/toolResult.js +40 -0
  74. package/server/services/ai/tools.js +163 -4
  75. package/server/services/android/controller.js +6 -2
  76. package/server/services/billing/plans.js +2 -1
  77. package/server/services/browser/anti_detection.js +192 -0
  78. package/server/services/browser/controller.js +180 -54
  79. package/server/services/desktop/auth.js +3 -0
  80. package/server/services/desktop/registry.js +50 -2
  81. package/server/services/integrations/google/calendar.js +22 -14
  82. package/server/services/manager.js +12 -42
  83. package/server/services/memory/ingestion_chunking.js +268 -0
  84. package/server/services/messaging/telnyx.js +9 -8
  85. package/server/services/recordings/manager.js +60 -27
  86. package/server/services/runtime/backends/local-vm.js +40 -22
  87. package/server/services/runtime/docker-vm-manager.js +157 -266
  88. package/server/services/runtime/guest_bootstrap.js +17 -5
  89. package/server/services/runtime/guest_image.js +188 -0
  90. package/server/services/runtime/manager.js +0 -1
  91. package/server/services/runtime/validation.js +3 -8
  92. package/server/services/social_video/service.js +60 -10
  93. package/server/services/tasks/runtime.js +234 -9
  94. package/server/services/tasks/schedule_utils.js +5 -5
  95. package/server/services/tasks/task_repository.js +13 -0
  96. package/server/services/timeline/service.js +558 -0
  97. package/server/services/wearable/gateway.js +1 -1
  98. package/server/services/websocket.js +21 -3
  99. package/server/services/desktop/screenRecorder.js +0 -292
  100. package/server/services/desktop/screen_recorder_support.js +0 -46
@@ -51,13 +51,6 @@ function initializeDatabase(db, dbPath) {
51
51
  return db;
52
52
  }
53
53
 
54
- function sleepSync(ms) {
55
- if (ms <= 0) return;
56
- const buffer = new SharedArrayBuffer(4);
57
- const view = new Int32Array(buffer);
58
- Atomics.wait(view, 0, 0, ms);
59
- }
60
-
61
54
  function isSqliteBusyError(error) {
62
55
  return Boolean(
63
56
  error &&
@@ -69,7 +62,9 @@ function isSqliteBusyError(error) {
69
62
  );
70
63
  }
71
64
 
72
- function runWithBusyRetry(action, { attempts = 5, delayMs = 50, label = 'SQLite operation' } = {}) {
65
+ function runWithBusyRetry(action, { attempts = 3, label = 'SQLite operation' } = {}) {
66
+ // busy_timeout pragma already handles waiting at the SQLite layer;
67
+ // this loop is a last-resort failsafe without adding extra main-thread blocking.
73
68
  let lastError = null;
74
69
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
75
70
  try {
@@ -80,9 +75,8 @@ function runWithBusyRetry(action, { attempts = 5, delayMs = 50, label = 'SQLite
80
75
  throw error;
81
76
  }
82
77
  console.warn(
83
- `[Database] ${label} hit a busy lock on attempt ${attempt}/${attempts}; retrying in ${delayMs}ms.`,
78
+ `[Database] ${label} hit a busy lock on attempt ${attempt}/${attempts}; retrying.`,
84
79
  );
85
- sleepSync(delayMs);
86
80
  }
87
81
  }
88
82
  throw lastError;
@@ -412,6 +406,9 @@ db.exec(`
412
406
  session_id INTEGER,
413
407
  last_connected_at TEXT,
414
408
  last_seen_at TEXT,
409
+ passive_history_enabled INTEGER DEFAULT 0,
410
+ passive_history_last_uploaded_at TEXT,
411
+ passive_history_last_error TEXT,
415
412
  revoked_at TEXT,
416
413
  created_at TEXT DEFAULT (datetime('now')),
417
414
  updated_at TEXT DEFAULT (datetime('now')),
@@ -509,7 +506,6 @@ db.exec(`
509
506
  CREATE INDEX IF NOT EXISTS idx_browser_extension_pairing_status ON browser_extension_pairing_requests(status, expires_at);
510
507
  CREATE INDEX IF NOT EXISTS idx_browser_extension_tokens_user ON browser_extension_tokens(user_id, status, created_at DESC);
511
508
  CREATE INDEX IF NOT EXISTS idx_browser_extension_tokens_hash_status ON browser_extension_tokens(token_hash, status);
512
- CREATE INDEX IF NOT EXISTS idx_desktop_companion_devices_user ON desktop_companion_devices(user_id, status, created_at DESC);
513
509
  CREATE INDEX IF NOT EXISTS idx_messages_user ON messages(user_id, created_at DESC);
514
510
  CREATE INDEX IF NOT EXISTS idx_messages_platform ON messages(platform, platform_chat_id);
515
511
  CREATE INDEX IF NOT EXISTS idx_messages_dedup ON messages(user_id, platform, platform_msg_id) WHERE platform_msg_id IS NOT NULL;
@@ -1026,11 +1022,34 @@ db.exec(`
1026
1022
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1027
1023
  user_id INTEGER NOT NULL,
1028
1024
  timestamp TEXT DEFAULT (datetime('now')),
1025
+ captured_at TEXT,
1026
+ device_id TEXT,
1027
+ device_label TEXT,
1029
1028
  app_name TEXT,
1029
+ window_title TEXT,
1030
1030
  text_content TEXT,
1031
+ ocr_engine TEXT,
1032
+ ocr_confidence REAL,
1031
1033
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
1032
1034
  );
1033
1035
 
1036
+ CREATE TABLE IF NOT EXISTS timeline_events (
1037
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1038
+ user_id INTEGER NOT NULL,
1039
+ agent_id TEXT,
1040
+ source_kind TEXT NOT NULL,
1041
+ event_kind TEXT NOT NULL,
1042
+ occurred_at TEXT NOT NULL,
1043
+ title TEXT NOT NULL,
1044
+ summary TEXT,
1045
+ source_id TEXT,
1046
+ group_key TEXT,
1047
+ metadata_json TEXT DEFAULT '{}',
1048
+ created_at TEXT DEFAULT (datetime('now')),
1049
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
1050
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL
1051
+ );
1052
+
1034
1053
  CREATE TABLE IF NOT EXISTS notification_history (
1035
1054
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1036
1055
  user_id INTEGER NOT NULL,
@@ -1055,6 +1074,9 @@ db.exec(`
1055
1074
 
1056
1075
  CREATE INDEX IF NOT EXISTS idx_screen_history_user ON screen_history(user_id, timestamp DESC);
1057
1076
  CREATE INDEX IF NOT EXISTS idx_notification_history_user ON notification_history(user_id, timestamp DESC);
1077
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_user ON timeline_events(user_id, occurred_at DESC, id DESC);
1078
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_source ON timeline_events(user_id, source_kind, occurred_at DESC, id DESC);
1079
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_group ON timeline_events(user_id, source_kind, group_key, occurred_at DESC, id DESC);
1058
1080
 
1059
1081
  CREATE TABLE IF NOT EXISTS artifacts (
1060
1082
  id TEXT PRIMARY KEY,
@@ -1320,8 +1342,17 @@ for (const col of [
1320
1342
  "ALTER TABLE desktop_companion_devices ADD COLUMN session_id INTEGER",
1321
1343
  "ALTER TABLE desktop_companion_devices ADD COLUMN last_connected_at TEXT",
1322
1344
  "ALTER TABLE desktop_companion_devices ADD COLUMN last_seen_at TEXT",
1345
+ "ALTER TABLE desktop_companion_devices ADD COLUMN passive_history_enabled INTEGER DEFAULT 0",
1346
+ "ALTER TABLE desktop_companion_devices ADD COLUMN passive_history_last_uploaded_at TEXT",
1347
+ "ALTER TABLE desktop_companion_devices ADD COLUMN passive_history_last_error TEXT",
1323
1348
  "ALTER TABLE desktop_companion_devices ADD COLUMN revoked_at TEXT",
1324
1349
  "ALTER TABLE desktop_companion_devices ADD COLUMN updated_at TEXT DEFAULT (datetime('now'))",
1350
+ "ALTER TABLE screen_history ADD COLUMN captured_at TEXT",
1351
+ "ALTER TABLE screen_history ADD COLUMN device_id TEXT",
1352
+ "ALTER TABLE screen_history ADD COLUMN device_label TEXT",
1353
+ "ALTER TABLE screen_history ADD COLUMN window_title TEXT",
1354
+ "ALTER TABLE screen_history ADD COLUMN ocr_engine TEXT",
1355
+ "ALTER TABLE screen_history ADD COLUMN ocr_confidence REAL",
1325
1356
  "ALTER TABLE memory_facts ADD COLUMN valid_from TEXT",
1326
1357
  "ALTER TABLE memory_facts ADD COLUMN valid_to TEXT",
1327
1358
  "ALTER TABLE memory_facts ADD COLUMN learned_at TEXT",
@@ -1344,6 +1375,30 @@ function tableHasColumn(tableName, columnName) {
1344
1375
  }
1345
1376
  }
1346
1377
 
1378
+ function createLegacyCompatibleIndexes() {
1379
+ const deferredIndexes = [
1380
+ {
1381
+ table: 'desktop_companion_devices',
1382
+ columns: ['user_id', 'status', 'created_at'],
1383
+ sql: 'CREATE INDEX IF NOT EXISTS idx_desktop_companion_devices_user ON desktop_companion_devices(user_id, status, created_at DESC)',
1384
+ },
1385
+ {
1386
+ table: 'screen_history',
1387
+ columns: ['user_id', 'device_id', 'captured_at'],
1388
+ sql: 'CREATE INDEX IF NOT EXISTS idx_screen_history_device ON screen_history(user_id, device_id, captured_at DESC)',
1389
+ },
1390
+ ];
1391
+
1392
+ for (const { table, columns, sql } of deferredIndexes) {
1393
+ if (!columns.every((column) => tableHasColumn(table, column))) continue;
1394
+ try {
1395
+ db.exec(sql);
1396
+ } catch {
1397
+ // Keep startup resilient for partially migrated local databases.
1398
+ }
1399
+ }
1400
+ }
1401
+
1347
1402
  function createAgentScopedIndexes() {
1348
1403
  const statements = [
1349
1404
  ['agent_runs', 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent ON agent_runs(user_id, agent_id, created_at DESC)'],
@@ -1961,7 +2016,8 @@ function backfillTaskTriggers() {
1961
2016
  .all();
1962
2017
  const update = db.prepare(
1963
2018
  `UPDATE scheduled_tasks
1964
- SET trigger_type = ?, trigger_config = ?, execution_mode = COALESCE(NULLIF(execution_mode, ''), 'prompt')
2019
+ SET trigger_type = ?, trigger_config = ?, cron_expression = ?, run_at = ?,
2020
+ execution_mode = COALESCE(NULLIF(execution_mode, ''), 'prompt')
1965
2021
  WHERE id = ?`,
1966
2022
  );
1967
2023
  const tx = db.transaction(() => {
@@ -1973,17 +2029,39 @@ function backfillTaskTriggers() {
1973
2029
  } catch {
1974
2030
  parsedConfig = {};
1975
2031
  }
1976
- const hasConfig = parsedConfig && typeof parsedConfig === 'object' && !Array.isArray(parsedConfig) && Object.keys(parsedConfig).length > 0;
1977
- if (triggerType !== 'schedule' && hasConfig) {
1978
- update.run(triggerType, JSON.stringify(parsedConfig), row.id);
2032
+ if (!parsedConfig || typeof parsedConfig !== 'object' || Array.isArray(parsedConfig)) {
2033
+ parsedConfig = {};
2034
+ }
2035
+
2036
+ // Non-schedule triggers keep their own config untouched.
2037
+ if (triggerType !== 'schedule') {
2038
+ if (Object.keys(parsedConfig).length > 0) {
2039
+ update.run(triggerType, JSON.stringify(parsedConfig), row.cron_expression, row.run_at, row.id);
2040
+ }
1979
2041
  continue;
1980
2042
  }
1981
2043
 
1982
- const mode = row.one_time ? 'one_time' : 'recurring';
1983
- const config = row.one_time
1984
- ? { mode, runAt: row.run_at || null }
1985
- : { mode, cronExpression: row.cron_expression || null };
1986
- update.run('schedule', JSON.stringify(config), row.id);
2044
+ // Schedule triggers: reconcile the structured trigger_config with the
2045
+ // legacy cron_expression/run_at columns WITHOUT discarding a value that
2046
+ // exists in only one place. A prior version rebuilt trigger_config from
2047
+ // the legacy columns unconditionally, which blanked cronExpression for
2048
+ // tasks that carried it only in trigger_config and silently stopped them
2049
+ // from ever being scheduled.
2050
+ const cronExpression = String(parsedConfig.cronExpression || row.cron_expression || '').trim() || null;
2051
+ const runAt = parsedConfig.runAt || row.run_at || null;
2052
+ const isOneTime = cronExpression
2053
+ ? false
2054
+ : (Boolean(row.one_time) || parsedConfig.mode === 'one_time');
2055
+ const config = isOneTime
2056
+ ? { mode: 'one_time', runAt }
2057
+ : { mode: 'recurring', cronExpression };
2058
+ update.run(
2059
+ 'schedule',
2060
+ JSON.stringify(config),
2061
+ isOneTime ? null : cronExpression,
2062
+ isOneTime ? runAt : null,
2063
+ row.id,
2064
+ );
1987
2065
  }
1988
2066
  });
1989
2067
  tx();
@@ -1998,7 +2076,34 @@ rebuildCoreMemoryForAgents();
1998
2076
  migrateIntegrationConnectionsTable();
1999
2077
  migrateIntegrationOauthStatesTable();
2000
2078
  migrateIntegrationProviderConfigsTable();
2079
+ createLegacyCompatibleIndexes();
2001
2080
  createAgentScopedIndexes();
2081
+
2082
+ try {
2083
+ db.exec(`
2084
+ CREATE TABLE IF NOT EXISTS timeline_events (
2085
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2086
+ user_id INTEGER NOT NULL,
2087
+ agent_id TEXT,
2088
+ source_kind TEXT NOT NULL,
2089
+ event_kind TEXT NOT NULL,
2090
+ occurred_at TEXT NOT NULL,
2091
+ title TEXT NOT NULL,
2092
+ summary TEXT,
2093
+ source_id TEXT,
2094
+ group_key TEXT,
2095
+ metadata_json TEXT DEFAULT '{}',
2096
+ created_at TEXT DEFAULT (datetime('now')),
2097
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
2098
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL
2099
+ );
2100
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_user ON timeline_events(user_id, occurred_at DESC, id DESC);
2101
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_source ON timeline_events(user_id, source_kind, occurred_at DESC, id DESC);
2102
+ CREATE INDEX IF NOT EXISTS idx_timeline_events_group ON timeline_events(user_id, source_kind, group_key, occurred_at DESC, id DESC);
2103
+ `);
2104
+ } catch {
2105
+ // Keep startup resilient for partially migrated local databases.
2106
+ }
2002
2107
  migrateIntegrationSecretStorage();
2003
2108
  backfillVerifiedAccountEmails();
2004
2109
  rebuildFtsForAgents();
@@ -34,6 +34,7 @@ const routeRegistry = [
34
34
  { basePath: '/api/wearable', modulePath: '../routes/wearable' },
35
35
  { basePath: '/api/mobile/health', modulePath: '../routes/mobile-health' },
36
36
  { basePath: '/api/screen-history', modulePath: '../routes/screenHistory' },
37
+ { basePath: '/api/timeline', modulePath: '../routes/timeline' },
37
38
  { basePath: '/api/triggers', modulePath: '../routes/triggers' },
38
39
  { basePath: '/api/security', modulePath: '../routes/security' },
39
40
  ];
@@ -78,8 +78,10 @@ function registerStaticRoutes(app) {
78
78
  app.get(/^\/app(\/.*)?$/, serveFlutterApp);
79
79
 
80
80
  // Landing page at /
81
- app.use(express.static(LANDING_DIR));
82
- app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
81
+ if (fs.existsSync(path.join(LANDING_DIR, 'index.html'))) {
82
+ app.use(express.static(LANDING_DIR));
83
+ app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
84
+ }
83
85
  }
84
86
 
85
87
  function serveFlutterApp(req, res) {
@@ -1 +1 @@
1
- dc90ac758721f68d16ff899fbce82971
1
+ 4a3cf72801d868c962de138d534e3ede