neoagent 3.0.1-beta.2 → 3.0.1-beta.21
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 +19 -0
- package/README.md +20 -3
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +224 -0
- package/docs/configuration.md +22 -0
- package/docs/getting-started.md +10 -8
- package/docs/index.md +1 -0
- package/docs/operations.md +1 -1
- package/flutter_app/lib/main.dart +4 -1
- package/flutter_app/lib/main_account_settings.dart +138 -0
- package/flutter_app/lib/main_app_shell.dart +316 -0
- package/flutter_app/lib/main_billing.dart +1465 -0
- package/flutter_app/lib/main_chat.dart +1594 -224
- package/flutter_app/lib/main_controller.dart +263 -26
- package/flutter_app/lib/main_devices.dart +1 -1
- package/flutter_app/lib/main_install.dart +1147 -0
- package/flutter_app/lib/main_navigation.dart +12 -0
- package/flutter_app/lib/main_operations.dart +134 -9
- package/flutter_app/lib/main_settings.dart +148 -52
- package/flutter_app/lib/main_shared.dart +1 -0
- package/flutter_app/lib/src/backend_client.dart +86 -1
- package/landing/index.html +2 -2
- package/lib/manager.js +184 -66
- package/lib/schema_migrations.js +84 -0
- package/package.json +10 -4
- package/server/admin/access.js +12 -7
- package/server/admin/admin.css +78 -0
- package/server/admin/admin.js +511 -23
- package/server/admin/billing.js +415 -0
- package/server/admin/index.html +120 -13
- package/server/admin/users.js +15 -15
- package/server/db/database.js +13 -21
- package/server/db/sessions_db.js +8 -0
- package/server/http/middleware.js +4 -4
- package/server/http/routes.js +9 -0
- package/server/http/static.js +4 -2
- package/server/middleware/requireBilling.js +12 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +89347 -85449
- package/server/routes/account.js +53 -0
- package/server/routes/admin.js +515 -63
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +127 -0
- package/server/routes/billing_webhook.js +43 -0
- package/server/routes/memory.js +1 -4
- package/server/routes/settings.js +1 -2
- package/server/routes/skills.js +1 -1
- package/server/routes/triggers.js +2 -2
- package/server/services/account/erasure.js +263 -0
- package/server/services/account/sessions.js +1 -9
- package/server/services/ai/loop/agent_engine_core.js +42 -0
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/completion_judge.js +42 -0
- package/server/services/ai/loop/conversation_loop.js +248 -34
- package/server/services/ai/loop/progress_monitor.js +6 -6
- package/server/services/ai/loopPolicy.js +37 -44
- package/server/services/ai/messagingFallback.js +25 -1
- package/server/services/ai/models.js +18 -0
- package/server/services/ai/preModelCompaction.js +25 -5
- package/server/services/ai/rate_limits.js +28 -4
- package/server/services/ai/systemPrompt.js +23 -5
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/tools.js +231 -20
- package/server/services/android/controller.js +6 -2
- package/server/services/billing/billing_email.js +106 -0
- package/server/services/billing/config.js +17 -0
- package/server/services/billing/plans.js +121 -0
- package/server/services/billing/stripe_client.js +21 -0
- package/server/services/billing/subscriptions.js +462 -0
- package/server/services/billing/trial_guard.js +111 -0
- package/server/services/browser/contentExtractor.js +629 -0
- package/server/services/browser/controller.js +4 -8
- package/server/services/desktop/gateway.js +7 -4
- package/server/services/integrations/google/calendar.js +30 -2
- package/server/services/integrations/secrets.js +7 -4
- package/server/services/manager.js +11 -0
- package/server/services/messaging/automation.js +1 -1
- package/server/services/messaging/telnyx.js +12 -11
- package/server/services/messaging/whatsapp.js +1 -1
- package/server/services/recordings/manager.js +13 -7
- 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 +182 -0
- package/server/services/runtime/manager.js +0 -1
- package/server/services/runtime/validation.js +3 -8
- package/server/services/tasks/runtime.js +103 -15
- package/server/services/tasks/schedule_utils.js +5 -5
- package/server/services/voice/runtimeManager.js +19 -10
- package/server/services/wearable/gateway.js +8 -5
- package/server/services/websocket.js +26 -8
- package/server/services/workspace/manager.js +37 -3
package/server/admin/users.js
CHANGED
|
@@ -31,9 +31,9 @@ async function saveDefaultRateLimits(btn) {
|
|
|
31
31
|
headers: { 'Content-Type': 'application/json' },
|
|
32
32
|
body: JSON.stringify({ rate_limit_4h: parse(v4h), rate_limit_weekly: parse(vw) }),
|
|
33
33
|
});
|
|
34
|
-
if (!res.ok) { const b = await res.json().catch(() => ({}));
|
|
34
|
+
if (!res.ok) { const b = await res.json().catch(() => ({})); showToast(b.error || 'Failed', 'error'); }
|
|
35
35
|
else { btn.textContent = 'Saved!'; setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 2000); return; }
|
|
36
|
-
} catch (_) {
|
|
36
|
+
} catch (_) { showToast('Network error', 'error'); }
|
|
37
37
|
btn.disabled = false;
|
|
38
38
|
btn.textContent = orig;
|
|
39
39
|
}
|
|
@@ -132,7 +132,7 @@ function renderUsersTable(el, users) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
async function forceLogout(id, username, btn) {
|
|
135
|
-
if (!
|
|
135
|
+
if (!await showConfirmModal({ title: `Force logout @${esc(username)}?`, body: 'This will revoke all active sessions. They can log back in.', confirmLabel: 'Force Logout', confirmClass: 'btn-danger' })) return;
|
|
136
136
|
btn.disabled = true;
|
|
137
137
|
try {
|
|
138
138
|
const res = await api(`/admin/api/users/${id}/sessions`, { method: 'DELETE' });
|
|
@@ -141,22 +141,22 @@ async function forceLogout(id, username, btn) {
|
|
|
141
141
|
setTimeout(loadUsers, 800);
|
|
142
142
|
} else {
|
|
143
143
|
const b = await res.json().catch(() => ({}));
|
|
144
|
-
|
|
144
|
+
showToast(b.error || 'Failed', 'error');
|
|
145
145
|
btn.disabled = false;
|
|
146
146
|
}
|
|
147
147
|
} catch (err) {
|
|
148
|
-
if (err.message !== 'unauthorized')
|
|
148
|
+
if (err.message !== 'unauthorized') showToast('Network error', 'error');
|
|
149
149
|
btn.disabled = false;
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
async function deleteUser(id, displayName, btn) {
|
|
154
|
-
if (!
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
'
|
|
158
|
-
|
|
159
|
-
)) return;
|
|
154
|
+
if (!await showConfirmModal({
|
|
155
|
+
title: `Delete "${esc(displayName)}"?`,
|
|
156
|
+
body: `<strong>GDPR Erasure</strong> — permanently deletes the account and <strong>all</strong> associated data:<br><br>• Agent runs, steps, and messages<br>• Memories and conversations<br>• Integrations and platform connections<br>• Artifacts and files on disk<br>• All sessions<br><br><span style="color:var(--danger)">This action cannot be undone.</span>`,
|
|
157
|
+
confirmLabel: 'Delete User',
|
|
158
|
+
confirmClass: 'btn-danger',
|
|
159
|
+
})) return;
|
|
160
160
|
btn.disabled = true;
|
|
161
161
|
btn.textContent = 'Deleting…';
|
|
162
162
|
try {
|
|
@@ -170,12 +170,12 @@ async function deleteUser(id, displayName, btn) {
|
|
|
170
170
|
}
|
|
171
171
|
} else {
|
|
172
172
|
const b = await res.json().catch(() => ({}));
|
|
173
|
-
|
|
173
|
+
showToast(b.error || 'Failed to delete user', 'error');
|
|
174
174
|
btn.disabled = false;
|
|
175
175
|
btn.textContent = 'Delete';
|
|
176
176
|
}
|
|
177
177
|
} catch (err) {
|
|
178
|
-
if (err.message !== 'unauthorized')
|
|
178
|
+
if (err.message !== 'unauthorized') showToast('Network error', 'error');
|
|
179
179
|
btn.disabled = false;
|
|
180
180
|
btn.textContent = 'Delete';
|
|
181
181
|
}
|
|
@@ -266,12 +266,12 @@ async function editRateLimits(id, username) {
|
|
|
266
266
|
overlay.remove();
|
|
267
267
|
await fetchUsers(document.getElementById('user-search')?.value?.trim() || '');
|
|
268
268
|
} catch (_) {
|
|
269
|
-
|
|
269
|
+
showToast('Failed to save rate limits', 'error');
|
|
270
270
|
bSave.disabled = false;
|
|
271
271
|
bSave.textContent = 'Save';
|
|
272
272
|
}
|
|
273
273
|
};
|
|
274
274
|
} catch (_) {
|
|
275
|
-
|
|
275
|
+
showToast('Failed to fetch rate limits', 'error');
|
|
276
276
|
}
|
|
277
277
|
}
|
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;
|
|
@@ -1328,6 +1322,7 @@ for (const col of [
|
|
|
1328
1322
|
"ALTER TABLE memory_facts ADD COLUMN invalidated_at TEXT",
|
|
1329
1323
|
"ALTER TABLE memory_facts ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1330
1324
|
"ALTER TABLE memory_facts ADD COLUMN supersedes_fact_id TEXT",
|
|
1325
|
+
"ALTER TABLE users ADD COLUMN billing_override_plan_id TEXT",
|
|
1331
1326
|
]) {
|
|
1332
1327
|
try { db.exec(col); } catch { /* column already exists */ }
|
|
1333
1328
|
}
|
|
@@ -1879,7 +1874,7 @@ function migrateIntegrationProviderConfigsTable() {
|
|
|
1879
1874
|
}
|
|
1880
1875
|
|
|
1881
1876
|
function migrateIntegrationSecretStorage() {
|
|
1882
|
-
|
|
1877
|
+
const migrate = db.transaction(() => {
|
|
1883
1878
|
const connectionRows = db
|
|
1884
1879
|
.prepare('SELECT id, credentials_json FROM integration_connections')
|
|
1885
1880
|
.all();
|
|
@@ -1891,11 +1886,7 @@ function migrateIntegrationSecretStorage() {
|
|
|
1891
1886
|
if (!current || isEncryptedValue(current)) continue;
|
|
1892
1887
|
updateConnection.run(encryptValue(current), row.id);
|
|
1893
1888
|
}
|
|
1894
|
-
} catch {
|
|
1895
|
-
// Preserve startup even if a row cannot be re-encrypted.
|
|
1896
|
-
}
|
|
1897
1889
|
|
|
1898
|
-
try {
|
|
1899
1890
|
const stateRows = db
|
|
1900
1891
|
.prepare('SELECT id, code_verifier FROM integration_oauth_states')
|
|
1901
1892
|
.all();
|
|
@@ -1907,11 +1898,7 @@ function migrateIntegrationSecretStorage() {
|
|
|
1907
1898
|
if (!current || isEncryptedValue(current)) continue;
|
|
1908
1899
|
updateState.run(encryptValue(current), row.id);
|
|
1909
1900
|
}
|
|
1910
|
-
} catch {
|
|
1911
|
-
// Preserve startup even if a row cannot be re-encrypted.
|
|
1912
|
-
}
|
|
1913
1901
|
|
|
1914
|
-
try {
|
|
1915
1902
|
const providerRows = db
|
|
1916
1903
|
.prepare('SELECT id, config_json FROM integration_provider_configs')
|
|
1917
1904
|
.all();
|
|
@@ -1923,8 +1910,13 @@ function migrateIntegrationSecretStorage() {
|
|
|
1923
1910
|
if (!current || isEncryptedValue(current)) continue;
|
|
1924
1911
|
updateProviderConfig.run(encryptValue(current), row.id);
|
|
1925
1912
|
}
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
try {
|
|
1916
|
+
migrate();
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
console.error('[DB] Integration secret migration failed — startup cannot continue with partially migrated secrets:', err.message);
|
|
1919
|
+
throw err;
|
|
1928
1920
|
}
|
|
1929
1921
|
}
|
|
1930
1922
|
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const session = require('express-session');
|
|
4
|
-
const Sqlite = require('better-sqlite3');
|
|
5
4
|
const SQLiteStore = require('better-sqlite3-session-store')(session);
|
|
6
5
|
const helmet = require('helmet');
|
|
7
6
|
const cors = require('cors');
|
|
8
|
-
const { DATA_DIR } = require('../../runtime/paths');
|
|
9
7
|
const { logRequestSummary } = require('../utils/logger');
|
|
10
8
|
const { getSessionSecret } = require('../services/account/session_secret');
|
|
11
9
|
|
|
12
|
-
const sessionsDb =
|
|
10
|
+
const sessionsDb = require('../db/sessions_db');
|
|
13
11
|
const LEGACY_SESSION_EXPIRE_FALLBACK = 0;
|
|
14
12
|
|
|
15
13
|
function boolEnv(name, fallback = false) {
|
|
@@ -89,7 +87,9 @@ function buildHelmetOptions({ secureCookies }) {
|
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
return {
|
|
92
|
-
strictTransportSecurity:
|
|
90
|
+
strictTransportSecurity: secureCookies
|
|
91
|
+
? { maxAge: Number(process.env.NEOAGENT_HSTS_MAX_AGE ?? 86400), includeSubDomains: false }
|
|
92
|
+
: false,
|
|
93
93
|
crossOriginOpenerPolicy: false,
|
|
94
94
|
crossOriginResourcePolicy: { policy: 'same-site' },
|
|
95
95
|
originAgentCluster: false,
|
package/server/http/routes.js
CHANGED
|
@@ -48,6 +48,15 @@ function registerApiRoutes(app) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Billing routes are mounted conditionally — only when billing is enabled.
|
|
52
|
+
// The webhook must be mounted before express.json() consumes the raw body;
|
|
53
|
+
// billing_webhook.js applies express.raw() inline for its own route.
|
|
54
|
+
const { isBillingEnabled } = require('../services/billing/config');
|
|
55
|
+
if (isBillingEnabled()) {
|
|
56
|
+
app.use('/api/billing/webhook', require('../routes/billing_webhook'));
|
|
57
|
+
app.use('/api/billing', require('../routes/billing'));
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
setupTelnyxWebhook(app);
|
|
52
61
|
|
|
53
62
|
app.get('/api/health', requireAuth, (req, res) => {
|
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) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBillingEnabled } = require('../services/billing/config');
|
|
4
|
+
|
|
5
|
+
function requireBilling(req, res, next) {
|
|
6
|
+
if (!isBillingEnabled()) {
|
|
7
|
+
return res.status(404).json({ error: 'Billing is not enabled on this server.' });
|
|
8
|
+
}
|
|
9
|
+
next();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { requireBilling };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
480214d898daad039c3f7d2d405af709
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1637623305" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|