neoagent 3.2.1-beta.3 → 3.2.1-beta.5
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/extensions/chrome-browser/protocol.mjs +143 -1
- package/flutter_app/lib/main_controller.dart +82 -0
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/lib/schema_migrations.js +48 -0
- package/package.json +10 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +33 -10
- 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 +69495 -68619
- package/server/routes/integrations.js +102 -0
- package/server/services/ai/systemPrompt.js +2 -2
- package/server/services/ai/tools.js +77 -0
- package/server/services/browser/controller.js +107 -0
- package/server/services/browser/extension/protocol.js +3 -0
- package/server/services/browser/extension/provider.js +12 -0
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/manager.js +1 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/manager.js +23 -0
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/runtime/backends/local-vm.js +13 -1
- package/server/services/runtime/guest_bootstrap.js +23 -4
- package/server/services/runtime/guest_image.js +4 -3
- package/server/services/runtime/manager.js +25 -11
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
|
@@ -1359,6 +1359,84 @@ class BackendClient {
|
|
|
1359
1359
|
return _asMap(_decodeJson(response.body));
|
|
1360
1360
|
}
|
|
1361
1361
|
|
|
1362
|
+
Future<Map<String, dynamic>> unlockBitwarden(
|
|
1363
|
+
String baseUrl, {
|
|
1364
|
+
required String masterPassword,
|
|
1365
|
+
required int idleTimeoutMinutes,
|
|
1366
|
+
String? agentId,
|
|
1367
|
+
}) {
|
|
1368
|
+
return postMap(
|
|
1369
|
+
baseUrl,
|
|
1370
|
+
'/api/integrations/bitwarden/unlock',
|
|
1371
|
+
_withAgentId(<String, dynamic>{
|
|
1372
|
+
'masterPassword': masterPassword,
|
|
1373
|
+
'idleTimeoutMinutes': idleTimeoutMinutes,
|
|
1374
|
+
}, agentId),
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
Future<Map<String, dynamic>> lockBitwarden(
|
|
1379
|
+
String baseUrl, {
|
|
1380
|
+
String? agentId,
|
|
1381
|
+
}) {
|
|
1382
|
+
return postMap(
|
|
1383
|
+
baseUrl,
|
|
1384
|
+
'/api/integrations/bitwarden/lock',
|
|
1385
|
+
_withAgentId(<String, dynamic>{}, agentId),
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
Future<Map<String, dynamic>> fetchBitwardenItems(
|
|
1390
|
+
String baseUrl, {
|
|
1391
|
+
String? agentId,
|
|
1392
|
+
}) {
|
|
1393
|
+
return getMap(
|
|
1394
|
+
baseUrl,
|
|
1395
|
+
_withAgentQuery('/api/integrations/bitwarden/items', agentId),
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
Future<Map<String, dynamic>> fetchCredentialBindings(
|
|
1400
|
+
String baseUrl, {
|
|
1401
|
+
String? agentId,
|
|
1402
|
+
}) {
|
|
1403
|
+
return getMap(
|
|
1404
|
+
baseUrl,
|
|
1405
|
+
_withAgentQuery('/api/integrations/bitwarden/bindings', agentId),
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
Future<Map<String, dynamic>> createCredentialBinding(
|
|
1410
|
+
String baseUrl, {
|
|
1411
|
+
required Map<String, dynamic> binding,
|
|
1412
|
+
String? agentId,
|
|
1413
|
+
}) {
|
|
1414
|
+
return postMap(
|
|
1415
|
+
baseUrl,
|
|
1416
|
+
'/api/integrations/bitwarden/bindings',
|
|
1417
|
+
_withAgentId(binding, agentId),
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
Future<Map<String, dynamic>> deleteCredentialBinding(
|
|
1422
|
+
String baseUrl,
|
|
1423
|
+
String bindingId, {
|
|
1424
|
+
String? agentId,
|
|
1425
|
+
}) async {
|
|
1426
|
+
final response = await _httpClient.delete(
|
|
1427
|
+
_resolveUri(
|
|
1428
|
+
baseUrl,
|
|
1429
|
+
_withAgentQuery(
|
|
1430
|
+
'/api/integrations/bitwarden/bindings/${Uri.encodeComponent(bindingId)}',
|
|
1431
|
+
agentId,
|
|
1432
|
+
),
|
|
1433
|
+
),
|
|
1434
|
+
headers: const <String, String>{'Accept': 'application/json'},
|
|
1435
|
+
);
|
|
1436
|
+
_throwIfError(response);
|
|
1437
|
+
return _asMap(_decodeJson(response.body));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1362
1440
|
Future<Map<String, dynamic>> fetchMessagingStatus(
|
|
1363
1441
|
String baseUrl, {
|
|
1364
1442
|
String? agentId,
|
package/lib/schema_migrations.js
CHANGED
|
@@ -713,6 +713,52 @@ function migrateMessagingInboundJobs(db) {
|
|
|
713
713
|
`);
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
function migrateCredentialBroker(db) {
|
|
717
|
+
db.exec(`
|
|
718
|
+
CREATE TABLE IF NOT EXISTS credential_bindings (
|
|
719
|
+
id TEXT PRIMARY KEY,
|
|
720
|
+
user_id INTEGER NOT NULL,
|
|
721
|
+
agent_id TEXT NOT NULL,
|
|
722
|
+
provider_key TEXT NOT NULL,
|
|
723
|
+
connection_id INTEGER NOT NULL,
|
|
724
|
+
alias TEXT NOT NULL,
|
|
725
|
+
usage_type TEXT NOT NULL CHECK(usage_type IN ('browser', 'http')),
|
|
726
|
+
item_ref_encrypted TEXT NOT NULL,
|
|
727
|
+
field_config_encrypted TEXT NOT NULL,
|
|
728
|
+
target_config_json TEXT NOT NULL DEFAULT '{}',
|
|
729
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
730
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
731
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
732
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
733
|
+
FOREIGN KEY (connection_id) REFERENCES integration_connections(id) ON DELETE CASCADE,
|
|
734
|
+
UNIQUE(user_id, agent_id, alias)
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
CREATE INDEX IF NOT EXISTS idx_credential_bindings_scope
|
|
738
|
+
ON credential_bindings(user_id, agent_id, provider_key, usage_type);
|
|
739
|
+
|
|
740
|
+
CREATE TABLE IF NOT EXISTS credential_usage_audit (
|
|
741
|
+
id TEXT PRIMARY KEY,
|
|
742
|
+
user_id INTEGER NOT NULL,
|
|
743
|
+
agent_id TEXT NOT NULL,
|
|
744
|
+
run_id TEXT,
|
|
745
|
+
binding_id TEXT,
|
|
746
|
+
operation TEXT NOT NULL,
|
|
747
|
+
target TEXT,
|
|
748
|
+
outcome TEXT NOT NULL,
|
|
749
|
+
error_code TEXT,
|
|
750
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
751
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
752
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
|
|
753
|
+
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE SET NULL,
|
|
754
|
+
FOREIGN KEY (binding_id) REFERENCES credential_bindings(id) ON DELETE SET NULL
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
CREATE INDEX IF NOT EXISTS idx_credential_usage_audit_scope
|
|
758
|
+
ON credential_usage_audit(user_id, agent_id, created_at DESC);
|
|
759
|
+
`);
|
|
760
|
+
}
|
|
761
|
+
|
|
716
762
|
function runSchemaMigrations(db) {
|
|
717
763
|
removeRetiredCaptureData(db);
|
|
718
764
|
migrateMemoryEmbeddingIndex(db);
|
|
@@ -729,6 +775,7 @@ function runSchemaMigrations(db) {
|
|
|
729
775
|
migrateBilling(db);
|
|
730
776
|
migrateAgentRunLifecycle(db);
|
|
731
777
|
migrateMessagingInboundJobs(db);
|
|
778
|
+
migrateCredentialBroker(db);
|
|
732
779
|
}
|
|
733
780
|
|
|
734
781
|
module.exports = {
|
|
@@ -748,5 +795,6 @@ module.exports = {
|
|
|
748
795
|
migrateBilling,
|
|
749
796
|
migrateAgentRunLifecycle,
|
|
750
797
|
migrateMessagingInboundJobs,
|
|
798
|
+
migrateCredentialBroker,
|
|
751
799
|
runSchemaMigrations,
|
|
752
800
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "3.2.1-beta.
|
|
3
|
+
"version": "3.2.1-beta.5",
|
|
4
4
|
"description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"homepage": "https://github.com/NeoLabs-Systems/NeoAgent#readme",
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
70
|
+
"@bitwarden/cli": "^2026.6.0",
|
|
70
71
|
"@google/generative-ai": "^0.24.0",
|
|
71
72
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
72
73
|
"@remotion/cli": "^4.0.459",
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
"geoip-lite": "^1.4.10",
|
|
87
88
|
"googleapis": "^150.0.1",
|
|
88
89
|
"helmet": "^8.0.0",
|
|
89
|
-
"multer": "^
|
|
90
|
+
"multer": "^2.2.0",
|
|
90
91
|
"node-cron": "^4.6.0",
|
|
91
92
|
"node-pty": "^1.0.0",
|
|
92
93
|
"nodemailer": "^9.0.3",
|
|
@@ -109,17 +110,24 @@
|
|
|
109
110
|
"ws": "^8.21.0"
|
|
110
111
|
},
|
|
111
112
|
"overrides": {
|
|
113
|
+
"@bitwarden/cli": {
|
|
114
|
+
"form-data": "4.0.6",
|
|
115
|
+
"inquirer": "8.2.7",
|
|
116
|
+
"multer": "2.2.0"
|
|
117
|
+
},
|
|
112
118
|
"@hono/node-server": "^2.0.11",
|
|
113
119
|
"axios": "^1.15.2",
|
|
114
120
|
"basic-ftp": "^5.3.0",
|
|
115
121
|
"dompurify": "^3.4.12",
|
|
116
122
|
"fast-uri": "^3.1.4",
|
|
123
|
+
"form-data": "4.0.6",
|
|
117
124
|
"follow-redirects": "^1.16.0",
|
|
118
125
|
"hono": "^4.12.31",
|
|
119
126
|
"ip-address": "^10.2.0",
|
|
120
127
|
"linkify-it": "^5.0.2",
|
|
121
128
|
"protobufjs": "^7.6.5",
|
|
122
129
|
"serialize-javascript": "^7.0.5",
|
|
130
|
+
"tmp": "0.2.7",
|
|
123
131
|
"undici": "^6.27.0",
|
|
124
132
|
"uuid": "$uuid",
|
|
125
133
|
"webpackbar": "^7.0.0",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neoagent-guest-agent-cli",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Guest runtime for isolated NeoAgent CLI services",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"express": "^4.21.2",
|
|
11
|
+
"proper-lockfile": "^4.1.2"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/server/guest_agent.js
CHANGED
|
@@ -4,7 +4,6 @@ const express = require('express');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { CLIExecutor } = require('./services/cli/executor');
|
|
8
7
|
const { RUNTIME_HOME, DATA_DIR } = require('../runtime/paths');
|
|
9
8
|
|
|
10
9
|
const PORT = Number(process.env.NEOAGENT_GUEST_AGENT_PORT || 8421);
|
|
@@ -21,8 +20,9 @@ function resolveGuestToken() {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
const AUTH_TOKEN = resolveGuestToken();
|
|
24
|
-
const
|
|
25
|
-
|
|
23
|
+
const RAW_GUEST_PROFILE = String(process.env.NEOAGENT_GUEST_PROFILE || 'browser_cli').trim();
|
|
24
|
+
const GUEST_PROFILE = ['android', 'browser', 'cli', 'browser_cli'].includes(RAW_GUEST_PROFILE)
|
|
25
|
+
? RAW_GUEST_PROFILE
|
|
26
26
|
: 'browser_cli';
|
|
27
27
|
const FILE_ROOT = path.join(RUNTIME_HOME, 'guest-agent-files');
|
|
28
28
|
const MAX_APK_STREAM_BYTES = Number(process.env.NEOAGENT_GUEST_MAX_APK_STREAM_BYTES || 512 * 1024 * 1024);
|
|
@@ -32,8 +32,10 @@ fs.mkdirSync(FILE_ROOT, { recursive: true });
|
|
|
32
32
|
const app = express();
|
|
33
33
|
app.use(express.json({ limit: '100mb' }));
|
|
34
34
|
|
|
35
|
-
const cliExecutor =
|
|
36
|
-
|
|
35
|
+
const cliExecutor = ['cli', 'browser_cli', 'android'].includes(GUEST_PROFILE)
|
|
36
|
+
? new (require('./services/cli/executor').CLIExecutor)()
|
|
37
|
+
: null;
|
|
38
|
+
const browserController = ['browser', 'browser_cli'].includes(GUEST_PROFILE)
|
|
37
39
|
? new (require('./services/browser/controller').BrowserController)({ runtimeBackend: 'vm' })
|
|
38
40
|
: null;
|
|
39
41
|
const androidController = GUEST_PROFILE === 'android'
|
|
@@ -139,18 +141,19 @@ app.get('/health', (_req, res) => {
|
|
|
139
141
|
|
|
140
142
|
app.post('/exec', async (req, res) => {
|
|
141
143
|
await handleRequest(req, res, async (signal) => {
|
|
144
|
+
const executor = requireCapability(cliExecutor, 'cli');
|
|
142
145
|
const command = String(req.body?.command || '').trim();
|
|
143
146
|
if (!command) {
|
|
144
147
|
return { error: 'command is required' };
|
|
145
148
|
}
|
|
146
149
|
if (req.body?.pty) {
|
|
147
|
-
return
|
|
150
|
+
return executor.executeInteractive(command, req.body?.inputs || [], {
|
|
148
151
|
cwd: req.body?.cwd,
|
|
149
152
|
timeout: req.body?.timeout,
|
|
150
153
|
signal,
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
|
-
return
|
|
156
|
+
return executor.execute(command, {
|
|
154
157
|
cwd: req.body?.cwd,
|
|
155
158
|
timeout: req.body?.timeout,
|
|
156
159
|
stdinInput: req.body?.stdin_input,
|
|
@@ -161,15 +164,16 @@ app.post('/exec', async (req, res) => {
|
|
|
161
164
|
|
|
162
165
|
app.post('/exec/kill', async (req, res) => {
|
|
163
166
|
await handle(res, async () => {
|
|
167
|
+
const executor = requireCapability(cliExecutor, 'cli');
|
|
164
168
|
const pid = Number(req.body?.pid);
|
|
165
169
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
166
170
|
return { error: 'pid is required' };
|
|
167
171
|
}
|
|
168
172
|
const reason = String(req.body?.reason || 'aborted').trim();
|
|
169
|
-
if (typeof
|
|
173
|
+
if (typeof executor.isManaged === 'function' && !executor.isManaged(pid)) {
|
|
170
174
|
return { error: 'pid not managed' };
|
|
171
175
|
}
|
|
172
|
-
const killed =
|
|
176
|
+
const killed = executor.kill(pid, reason || 'aborted');
|
|
173
177
|
return { success: killed, pid };
|
|
174
178
|
});
|
|
175
179
|
});
|
|
@@ -207,6 +211,22 @@ app.get('/browser/status', async (_req, res) => {
|
|
|
207
211
|
};
|
|
208
212
|
});
|
|
209
213
|
});
|
|
214
|
+
|
|
215
|
+
app.use('/browser', (req, res, next) => {
|
|
216
|
+
if (!browserController?.hasProtectedCredentialFill?.()) return next();
|
|
217
|
+
const allowed = new Set([
|
|
218
|
+
'/status',
|
|
219
|
+
'/credential-submit',
|
|
220
|
+
'/credential-cancel',
|
|
221
|
+
'/close',
|
|
222
|
+
]);
|
|
223
|
+
if (allowed.has(req.path)) return next();
|
|
224
|
+
return res.status(423).json({
|
|
225
|
+
error: 'Browser control is paused while a protected credential fill is active. Submit or cancel it first.',
|
|
226
|
+
code: 'PROTECTED_CREDENTIAL_FILL_ACTIVE',
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
210
230
|
function requireCapability(controller, name) {
|
|
211
231
|
if (!controller) {
|
|
212
232
|
throw new Error(`${name} runtime is unavailable in this guest profile.`);
|
|
@@ -227,6 +247,9 @@ app.post('/browser/screenshot-jpeg', async (req, res) => handleRequest(req, res,
|
|
|
227
247
|
app.post('/browser/click', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false, { signal })));
|
|
228
248
|
app.post('/browser/click-point', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false, { signal })));
|
|
229
249
|
app.post('/browser/fill', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), { ...(req.body || {}), signal })));
|
|
250
|
+
app.post('/browser/credential-fill', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').fillCredential(req.body || {}, { signal })));
|
|
251
|
+
app.post('/browser/credential-submit', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').submitProtectedCredential(req.body?.protectedFillId, { signal })));
|
|
252
|
+
app.post('/browser/credential-cancel', async (req, res) => handleRequest(req, res, () => requireCapability(browserController, 'browser').cancelProtectedCredential(req.body?.protectedFillId)));
|
|
230
253
|
app.post('/browser/type-text', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').typeText(String(req.body?.text || ''), { ...(req.body || {}), signal })));
|
|
231
254
|
app.post('/browser/press-key', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').pressKey(req.body?.key, req.body?.screenshot !== false, { signal })));
|
|
232
255
|
app.post('/browser/scroll', async (req, res) => handleRequest(req, res, (signal) => requireCapability(browserController, 'browser').scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false, { signal })));
|
|
@@ -347,7 +370,7 @@ async function shutdown() {
|
|
|
347
370
|
} catch (err) {
|
|
348
371
|
console.warn('[GuestAgent] Failed to close android controller:', err?.message);
|
|
349
372
|
}
|
|
350
|
-
cliExecutor
|
|
373
|
+
cliExecutor?.killAll?.('shutdown');
|
|
351
374
|
server.close(() => process.exit(0));
|
|
352
375
|
}
|
|
353
376
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
33e941ab4e958fa1f09d072c6fdd687f
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"69c8c61792f04cc809dfef0c910414fb9afc06
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "3763943593" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|