neoagent 3.2.1-beta.4 → 3.2.1-beta.6

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 (34) hide show
  1. package/extensions/chrome-browser/protocol.mjs +143 -1
  2. package/flutter_app/lib/main_controller.dart +82 -0
  3. package/flutter_app/lib/main_integrations.dart +607 -8
  4. package/flutter_app/lib/main_security.dart +266 -112
  5. package/flutter_app/lib/src/backend_client.dart +78 -0
  6. package/landing/index.html +3 -1
  7. package/lib/schema_migrations.js +48 -0
  8. package/package.json +10 -2
  9. package/server/guest-agent.cli.package.json +13 -0
  10. package/server/guest_agent.js +33 -10
  11. package/server/public/.last_build_id +1 -1
  12. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  13. package/server/public/flutter_bootstrap.js +1 -1
  14. package/server/public/main.dart.js +69495 -68619
  15. package/server/routes/integrations.js +102 -0
  16. package/server/services/ai/systemPrompt.js +2 -2
  17. package/server/services/ai/tools.js +77 -0
  18. package/server/services/browser/controller.js +107 -0
  19. package/server/services/browser/extension/protocol.js +3 -0
  20. package/server/services/browser/extension/provider.js +12 -0
  21. package/server/services/credentials/bitwarden_cli.js +322 -0
  22. package/server/services/credentials/broker.js +594 -0
  23. package/server/services/integrations/bitwarden/constants.js +14 -0
  24. package/server/services/integrations/bitwarden/provider.js +197 -0
  25. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  26. package/server/services/integrations/manager.js +1 -0
  27. package/server/services/integrations/registry.js +2 -0
  28. package/server/services/manager.js +23 -0
  29. package/server/services/runtime/backends/local-vm.js +13 -1
  30. package/server/services/runtime/guest_bootstrap.js +23 -4
  31. package/server/services/runtime/guest_image.js +4 -3
  32. package/server/services/runtime/manager.js +25 -11
  33. package/server/services/runtime/validation.js +7 -6
  34. 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,
@@ -814,8 +814,10 @@ p { margin: 0; text-wrap: pretty; }
814
814
  .nav-center { display: none; }
815
815
  .nav-right .signin { display: none; }
816
816
  .nav-toggle { display: grid; }
817
+ .nav.open { display: flex; flex-direction: column; max-height: 100vh; max-height: 100dvh; }
818
+ .nav.open .nav-inner { flex: none; width: 100%; }
817
819
  .nav.open .mobile-menu { display: flex; }
818
- .mobile-menu { flex-direction: column; gap: 2px; padding: 10px var(--gutter) 22px; background: var(--nav-bg); backdrop-filter: blur(18px); border-bottom: 1px solid var(--line); }
820
+ .mobile-menu { min-height: 0; overflow-y: auto; overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; flex-direction: column; gap: 2px; padding: 10px var(--gutter) 22px; background: var(--nav-bg); backdrop-filter: blur(18px); border-bottom: 1px solid var(--line); }
819
821
  .mobile-menu a { padding: 13px 4px; color: var(--ink-2); font-size: 16px; border-bottom: 1px solid var(--line); }
820
822
  .mobile-menu .btn { margin-top: 12px; justify-content: center; }
821
823
  .split { grid-template-columns: 1fr; gap: 32px; }
@@ -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.4",
3
+ "version": "3.2.1-beta.6",
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": "^1.4.5-lts.1",
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
+ }
@@ -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 GUEST_PROFILE = String(process.env.NEOAGENT_GUEST_PROFILE || 'browser_cli').trim() === 'android'
25
- ? 'android'
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 = new CLIExecutor();
36
- const browserController = GUEST_PROFILE === 'browser_cli'
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 cliExecutor.executeInteractive(command, req.body?.inputs || [], {
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 cliExecutor.execute(command, {
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 cliExecutor.isManaged === 'function' && !cliExecutor.isManaged(pid)) {
173
+ if (typeof executor.isManaged === 'function' && !executor.isManaged(pid)) {
170
174
  return { error: 'pid not managed' };
171
175
  }
172
- const killed = cliExecutor.kill(pid, reason || 'aborted');
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.killAll('shutdown');
373
+ cliExecutor?.killAll?.('shutdown');
351
374
  server.close(() => process.exit(0));
352
375
  }
353
376
 
@@ -1 +1 @@
1
- 8d462aa76c4f455b84daf0d4c44c1356
1
+ 47dfb68683e209ba52dcaade6052a7c2
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"69c8c61792f04cc809dfef0c910414fb9afc06
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2769323075" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "1886361738" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });