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.
- package/.env.example +2 -10
- package/README.md +12 -3
- package/docs/automation.md +37 -0
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +34 -8
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +10 -8
- package/docs/operations.md +1 -1
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_account_settings.dart +138 -0
- package/flutter_app/lib/main_app_shell.dart +38 -13
- package/flutter_app/lib/main_billing.dart +1465 -0
- package/flutter_app/lib/main_chat.dart +1612 -214
- package/flutter_app/lib/main_controller.dart +398 -26
- package/flutter_app/lib/main_devices.dart +293 -207
- package/flutter_app/lib/main_models.dart +142 -0
- package/flutter_app/lib/main_navigation.dart +19 -1
- package/flutter_app/lib/main_operations.dart +288 -9
- package/flutter_app/lib/main_settings.dart +510 -300
- package/flutter_app/lib/main_shared.dart +2 -0
- package/flutter_app/lib/main_timeline.dart +1378 -0
- package/flutter_app/lib/src/backend_client.dart +149 -19
- package/flutter_app/lib/src/desktop_companion_actions.dart +132 -21
- package/flutter_app/lib/src/desktop_companion_io.dart +65 -1
- package/flutter_app/lib/src/desktop_companion_stub.dart +12 -0
- package/flutter_app/lib/src/desktop_ocr_bridge.dart +2 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +125 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +30 -0
- package/flutter_app/lib/src/desktop_passive_history.dart +332 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +80 -72
- package/flutter_app/lib/src/recording_bridge_web.dart +127 -114
- package/flutter_app/lib/src/recording_chunk_queue.dart +149 -0
- package/flutter_app/lib/src/recording_chunk_queue_io.dart +182 -0
- package/flutter_app/lib/src/recording_payloads.dart +9 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +25 -0
- package/flutter_app/windows/runner/flutter_window.cpp +75 -0
- package/landing/index.html +3 -3
- package/lib/manager.js +184 -66
- package/package.json +4 -1
- package/server/admin/access.js +12 -7
- package/server/admin/admin.css +78 -0
- package/server/admin/admin.js +436 -10
- package/server/admin/billing.js +165 -42
- package/server/admin/index.html +80 -5
- package/server/admin/users.js +15 -15
- package/server/db/database.js +125 -20
- package/server/http/routes.js +1 -0
- package/server/http/static.js +4 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/canvaskit/wimp.js.symbols +8475 -8467
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +91077 -87037
- package/server/routes/account.js +53 -0
- package/server/routes/admin.js +345 -64
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +5 -0
- package/server/routes/browser.js +8 -1
- package/server/routes/recordings.js +96 -6
- package/server/routes/screenHistory.js +140 -2
- package/server/routes/timeline.js +43 -0
- package/server/services/account/erasure.js +263 -0
- package/server/services/ai/hooks.js +4 -1
- package/server/services/ai/loop/agent_engine_core.js +8 -1
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/conversation_loop.js +166 -33
- package/server/services/ai/messagingFallback.js +22 -0
- package/server/services/ai/rate_limits.js +28 -5
- package/server/services/ai/systemPrompt.js +6 -5
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/toolEvidence.js +15 -0
- package/server/services/ai/toolResult.js +40 -0
- package/server/services/ai/tools.js +163 -4
- package/server/services/android/controller.js +6 -2
- package/server/services/billing/plans.js +2 -1
- package/server/services/browser/anti_detection.js +192 -0
- package/server/services/browser/controller.js +180 -54
- package/server/services/desktop/auth.js +3 -0
- package/server/services/desktop/registry.js +50 -2
- package/server/services/integrations/google/calendar.js +22 -14
- package/server/services/manager.js +12 -42
- package/server/services/memory/ingestion_chunking.js +268 -0
- package/server/services/messaging/telnyx.js +9 -8
- package/server/services/recordings/manager.js +60 -27
- package/server/services/runtime/backends/local-vm.js +40 -22
- package/server/services/runtime/docker-vm-manager.js +157 -266
- package/server/services/runtime/guest_bootstrap.js +17 -5
- package/server/services/runtime/guest_image.js +188 -0
- package/server/services/runtime/manager.js +0 -1
- package/server/services/runtime/validation.js +3 -8
- package/server/services/social_video/service.js +60 -10
- package/server/services/tasks/runtime.js +234 -9
- package/server/services/tasks/schedule_utils.js +5 -5
- package/server/services/tasks/task_repository.js +13 -0
- package/server/services/timeline/service.js +558 -0
- package/server/services/wearable/gateway.js +1 -1
- package/server/services/websocket.js +21 -3
- package/server/services/desktop/screenRecorder.js +0 -292
- package/server/services/desktop/screen_recorder_support.js +0 -46
package/server/db/database.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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 = ?,
|
|
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
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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();
|
package/server/http/routes.js
CHANGED
|
@@ -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
|
];
|
package/server/http/static.js
CHANGED
|
@@ -78,8 +78,10 @@ function registerStaticRoutes(app) {
|
|
|
78
78
|
app.get(/^\/app(\/.*)?$/, serveFlutterApp);
|
|
79
79
|
|
|
80
80
|
// Landing page at /
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
1
|
+
4a3cf72801d868c962de138d534e3ede
|
|
Binary file
|