neoagent 2.3.1-beta.85 → 2.3.1-beta.86

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.
@@ -2,14 +2,23 @@ const { LocalVmExecutionBackend } = require('./backends/local-vm');
2
2
  const { QemuVmManager } = require('./qemu');
3
3
  const { getRuntimeSettings } = require('./settings');
4
4
  const { ExtensionBrowserProvider } = require('../browser/extension/provider');
5
+ const { AndroidController } = require('../android/controller');
5
6
 
6
7
  class RuntimeManager {
7
8
  constructor(options = {}) {
8
9
  this.browserExtensionRegistry = options.browserExtensionRegistry || null;
9
- this.vmBackend = new LocalVmExecutionBackend({
10
- vmManager: options.vmManager || new QemuVmManager(),
10
+ const browserVmManager = options.browserVmManager || new QemuVmManager({
11
+ runtimeProfile: 'browser_cli',
12
+ memoryMb: 2048,
13
+ cpus: 2,
14
+ warmup: false,
15
+ });
16
+ this.browserBackend = new LocalVmExecutionBackend({
17
+ runtimeProfile: 'browser_cli',
18
+ vmManager: browserVmManager,
11
19
  artifactStore: options.artifactStore,
12
20
  });
21
+ this.androidControllers = new Map();
13
22
  this.getExtensionBrowserProvider = options.getExtensionBrowserProvider || ((userId) => new ExtensionBrowserProvider({
14
23
  registry: options.browserExtensionRegistry,
15
24
  artifactStore: options.artifactStore,
@@ -31,25 +40,27 @@ class RuntimeManager {
31
40
 
32
41
  resolveBackend(userId, requested) {
33
42
  void userId;
34
- void requested;
35
- return this.vmBackend;
43
+ return this.browserBackend;
36
44
  }
37
45
 
38
46
  async executeCommand(userId, command, options = {}) {
39
- const backend = this.resolveBackend(userId, options.backend);
47
+ const backend = this.resolveBackend(userId, 'browser_cli');
40
48
  return backend.executeCommand(userId, command, options);
41
49
  }
42
50
 
43
- hasVmForUser(userId) {
44
- return Boolean(this.vmBackend?.vmManager?.hasVm?.(userId));
51
+ hasVmForUser(userId, capability = 'browser') {
52
+ if (capability === 'android') {
53
+ return Boolean(this.androidControllers.get(String(userId || '').trim()));
54
+ }
55
+ return Boolean(this.browserBackend?.vmManager?.hasVm?.(userId));
45
56
  }
46
57
 
47
58
  async killCommand(userId, pid, reason = 'aborted') {
48
- return this.vmBackend.killCommand(userId, pid, reason);
59
+ return this.browserBackend.killCommand(userId, pid, reason);
49
60
  }
50
61
 
51
62
  async getCommandExecutorForUser(userId) {
52
- return this.vmBackend.getCommandExecutorForUser(userId);
63
+ return this.browserBackend.getCommandExecutorForUser(userId);
53
64
  }
54
65
 
55
66
  async getBrowserProviderForUser(userId) {
@@ -57,22 +68,49 @@ class RuntimeManager {
57
68
  if (settings.browser_backend === 'extension' && this.hasActiveExtensionBrowser(userId)) {
58
69
  return this.getExtensionBrowserProvider(userId);
59
70
  }
60
- return this.vmBackend.getBrowserProviderForUser(userId);
71
+ return this.browserBackend.getBrowserProviderForUser(userId);
61
72
  }
62
73
 
63
74
  async getAndroidProviderForUser(userId) {
64
- return this.vmBackend.getAndroidProviderForUser(userId);
75
+ const key = String(userId || '').trim();
76
+ if (!key) {
77
+ throw new Error('Android provider requires a user ID.');
78
+ }
79
+ if (!this.androidControllers.has(key)) {
80
+ this.androidControllers.set(key, new AndroidController({
81
+ userId: key,
82
+ runtimeBackend: 'host',
83
+ artifactStore: null,
84
+ }));
85
+ }
86
+ return this.androidControllers.get(key);
65
87
  }
66
88
 
67
- async isGuestAgentReadyForUser(userId, timeoutMs = 1000) {
68
- if (typeof this.vmBackend?.isGuestAgentReadyForUser !== 'function') {
89
+ async isGuestAgentReadyForUser(userId, timeoutMs = 1000, capability = 'browser') {
90
+ if (capability === 'android') {
91
+ const controller = this.androidControllers.get(String(userId || '').trim());
92
+ if (!controller || typeof controller.getStatus !== 'function') {
93
+ return false;
94
+ }
95
+ try {
96
+ const status = await controller.getStatus();
97
+ return Boolean(status?.bootstrapped || status?.serial || status?.starting);
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ if (typeof this.browserBackend?.isGuestAgentReadyForUser !== 'function') {
69
103
  return false;
70
104
  }
71
- return this.vmBackend.isGuestAgentReadyForUser(userId, timeoutMs);
105
+ return this.browserBackend.isGuestAgentReadyForUser(userId, timeoutMs);
72
106
  }
73
107
 
74
108
  async shutdown() {
75
- await Promise.allSettled([this.vmBackend.shutdown()]);
109
+ await Promise.allSettled([
110
+ this.browserBackend?.shutdown?.(),
111
+ ...[...this.androidControllers.values()].map((controller) => controller?.stopEmulator?.().catch?.(() => {})),
112
+ ]);
113
+ this.androidControllers.clear();
76
114
  }
77
115
  }
78
116
 
@@ -9,12 +9,9 @@ const { spawn, spawnSync } = require('child_process');
9
9
  const { DATA_DIR } = require('../../../runtime/paths');
10
10
  const { ensureGuestBootstrapSeed } = require('./guest_bootstrap');
11
11
 
12
+ const REPO_ROOT = path.resolve(__dirname, '../../..');
12
13
  const VM_ROOT = path.join(DATA_DIR, 'runtime-vms');
13
- const BASE_IMAGE_CACHE_ROOT = path.join(VM_ROOT, 'base-images');
14
- const TEMPLATE_ROOT = path.join(VM_ROOT, 'templates');
15
14
  fs.mkdirSync(VM_ROOT, { recursive: true });
16
- fs.mkdirSync(BASE_IMAGE_CACHE_ROOT, { recursive: true });
17
- fs.mkdirSync(TEMPLATE_ROOT, { recursive: true });
18
15
 
19
16
  const DEFAULT_UBUNTU_BASE_IMAGE_URLS = Object.freeze({
20
17
  x64: 'https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img',
@@ -67,6 +64,47 @@ function generateGuestToken() {
67
64
  return crypto.randomBytes(32).toString('hex');
68
65
  }
69
66
 
67
+ function computeRuntimeTemplateSignature(guestArch, runtimeProfile = 'browser_cli') {
68
+ const hash = crypto.createHash('sha256');
69
+ const normalizedProfile = runtimeProfile === 'android' ? 'android' : 'browser_cli';
70
+ const trackedFiles = normalizedProfile === 'android'
71
+ ? [
72
+ 'server/guest-agent.android.package.json',
73
+ 'server/guest_agent.js',
74
+ 'server/services/android/controller.js',
75
+ 'server/services/cli/executor.js',
76
+ 'server/services/runtime/guest_bootstrap.js',
77
+ 'runtime/env.js',
78
+ 'runtime/paths.js',
79
+ ]
80
+ : [
81
+ 'server/guest-agent.browser.package.json',
82
+ 'server/guest_agent.js',
83
+ 'server/services/browser/controller.js',
84
+ 'server/services/cli/executor.js',
85
+ 'server/services/runtime/guest_bootstrap.js',
86
+ 'runtime/env.js',
87
+ 'runtime/paths.js',
88
+ ];
89
+
90
+ hash.update(String(guestArch || 'x64'));
91
+ hash.update('\0');
92
+ hash.update(normalizedProfile);
93
+ for (const relativePath of trackedFiles) {
94
+ const filePath = path.join(REPO_ROOT, relativePath);
95
+ hash.update('\0');
96
+ hash.update(relativePath);
97
+ hash.update('\0');
98
+ try {
99
+ hash.update(fs.readFileSync(filePath));
100
+ } catch (error) {
101
+ hash.update(`missing:${error?.code || 'unknown'}`);
102
+ }
103
+ }
104
+
105
+ return hash.digest('hex');
106
+ }
107
+
70
108
  function resolveGuestToken(userRoot) {
71
109
  const tokenPath = path.join(userRoot, 'guest-token.txt');
72
110
  try {
@@ -551,9 +589,22 @@ function formatReadinessIssues(readiness) {
551
589
  return issues.length > 0 ? issues : ['VM runtime is unavailable on this host.'];
552
590
  }
553
591
 
592
+ function readTemplateReadyMetadata(readySentinelPath) {
593
+ try {
594
+ const parsed = JSON.parse(fs.readFileSync(readySentinelPath, 'utf8'));
595
+ if (parsed && typeof parsed === 'object') {
596
+ return parsed;
597
+ }
598
+ } catch {}
599
+ return null;
600
+ }
601
+
554
602
  class QemuVmManager {
555
603
  constructor(options = {}) {
556
- this.rootDir = path.resolve(options.rootDir || VM_ROOT);
604
+ this.runtimeProfile = options.runtimeProfile === 'android' ? 'android' : 'browser_cli';
605
+ this.rootDir = path.resolve(options.rootDir || path.join(VM_ROOT, this.runtimeProfile));
606
+ this.baseImageCacheRoot = path.resolve(options.baseImageCacheRoot || path.join(this.rootDir, 'base-images'));
607
+ this.templateRootDir = path.resolve(options.templateRootDir || path.join(this.rootDir, 'templates'));
557
608
  this.baseImagePath = options.baseImagePath || process.env.NEOAGENT_VM_BASE_IMAGE || '';
558
609
  this.guestArch = options.guestArch || guestArchForHost();
559
610
  this.baseImageUrl = normalizeBaseImageUrlForArch(
@@ -565,12 +616,17 @@ class QemuVmManager {
565
616
  this.instances = new Map();
566
617
  this.baseImagePromise = null;
567
618
  this.runtimeTemplatePromise = null;
619
+ this.warmupEnabled = options.warmup === true;
568
620
  fs.mkdirSync(this.rootDir, { recursive: true });
569
- setTimeout(() => {
570
- this.ensureRuntimeTemplateAvailable().catch((error) => {
571
- console.warn(`[VM] Background runtime template warmup failed: ${error.message}`);
572
- });
573
- }, 0);
621
+ fs.mkdirSync(this.baseImageCacheRoot, { recursive: true });
622
+ fs.mkdirSync(this.templateRootDir, { recursive: true });
623
+ if (this.warmupEnabled) {
624
+ setTimeout(() => {
625
+ this.ensureRuntimeTemplateAvailable().catch((error) => {
626
+ console.warn(`[VM:${this.runtimeProfile}] Background runtime template warmup failed: ${error.message}`);
627
+ });
628
+ }, 0);
629
+ }
574
630
  }
575
631
 
576
632
  getBaseImageCachePath() {
@@ -579,7 +635,7 @@ class QemuVmManager {
579
635
  }
580
636
  const parsed = new URL(this.baseImageUrl);
581
637
  const filename = path.basename(parsed.pathname || '') || `${this.guestArch}-base.img`;
582
- return path.join(BASE_IMAGE_CACHE_ROOT, filename);
638
+ return path.join(this.baseImageCacheRoot, filename);
583
639
  }
584
640
 
585
641
  resolveBaseImagePath() {
@@ -619,7 +675,7 @@ class QemuVmManager {
619
675
  }
620
676
 
621
677
  getRuntimeTemplateRoot() {
622
- return path.join(TEMPLATE_ROOT, this.guestArch);
678
+ return path.join(this.templateRootDir, this.guestArch);
623
679
  }
624
680
 
625
681
  getRuntimeTemplateDiskPath() {
@@ -631,13 +687,24 @@ class QemuVmManager {
631
687
  }
632
688
 
633
689
  getRuntimeTemplateReadyMarker() {
634
- return '/var/lib/neoagent/browser-runtime-ready';
690
+ return this.runtimeProfile === 'android'
691
+ ? '/var/lib/neoagent/bootstrap-complete'
692
+ : '/var/lib/neoagent/browser-runtime-ready';
693
+ }
694
+
695
+ getRuntimeTemplateSignature() {
696
+ return computeRuntimeTemplateSignature(this.guestArch, this.runtimeProfile);
635
697
  }
636
698
 
637
699
  async ensureRuntimeTemplateAvailable() {
638
700
  const readyDiskPath = this.getRuntimeTemplateDiskPath();
639
701
  const readySentinelPath = path.join(this.getRuntimeTemplateRoot(), '.runtime-template-ready');
640
- if (fs.existsSync(readyDiskPath) && fs.existsSync(readySentinelPath)) {
702
+ const readyMetadata = readTemplateReadyMetadata(readySentinelPath);
703
+ if (
704
+ fs.existsSync(readyDiskPath)
705
+ && readyMetadata
706
+ && readyMetadata.signature === this.getRuntimeTemplateSignature()
707
+ ) {
641
708
  return readyDiskPath;
642
709
  }
643
710
  if (!this.runtimeTemplatePromise) {
@@ -653,9 +720,15 @@ class QemuVmManager {
653
720
  const readySentinelPath = path.join(this.getRuntimeTemplateRoot(), '.runtime-template-ready');
654
721
  const lockDir = this.getRuntimeTemplateLockDir();
655
722
  const acquireStartedAt = Date.now();
723
+ const expectedSignature = this.getRuntimeTemplateSignature();
656
724
 
657
725
  while (true) {
658
- if (fs.existsSync(readyDiskPath) && fs.existsSync(readySentinelPath)) {
726
+ const readyMetadata = readTemplateReadyMetadata(readySentinelPath);
727
+ if (
728
+ fs.existsSync(readyDiskPath)
729
+ && readyMetadata
730
+ && readyMetadata.signature === expectedSignature
731
+ ) {
659
732
  return readyDiskPath;
660
733
  }
661
734
 
@@ -698,8 +771,8 @@ class QemuVmManager {
698
771
  const templateDiskPath = this.getRuntimeTemplateDiskPath();
699
772
  const readyMarkerPath = this.getRuntimeTemplateReadyMarker();
700
773
  const readySentinelPath = path.join(templateRoot, '.runtime-template-ready');
774
+ const templateSignature = this.getRuntimeTemplateSignature();
701
775
 
702
- fs.rmSync(templateRoot, { recursive: true, force: true });
703
776
  fs.mkdirSync(templateRoot, { recursive: true });
704
777
 
705
778
  const baseImagePath = await this.ensureBaseImageAvailable();
@@ -709,6 +782,8 @@ class QemuVmManager {
709
782
  userRoot: templateRoot,
710
783
  guestToken,
711
784
  guestArch: this.guestArch,
785
+ runtimeMode: 'template',
786
+ runtimeProfile: this.runtimeProfile,
712
787
  });
713
788
  const consoleLogPath = path.join(templateRoot, 'console.log');
714
789
  const firmware = this.guestArch === 'arm64'
@@ -736,7 +811,7 @@ class QemuVmManager {
736
811
  firmwareVarsPath,
737
812
  });
738
813
 
739
- console.log(`[VM] Building runtime template for ${this.guestArch}: ${qemuBinaryPath} ${args.join(' ')}`);
814
+ console.log(`[VM:${this.runtimeProfile}] Building runtime template for ${this.guestArch}: ${qemuBinaryPath} ${args.join(' ')}`);
740
815
  const child = spawn(qemuBinaryPath, args, {
741
816
  cwd: templateRoot,
742
817
  detached: process.platform !== 'win32',
@@ -749,7 +824,7 @@ class QemuVmManager {
749
824
  });
750
825
 
751
826
  const baseUrl = `http://127.0.0.1:${agentPort}`;
752
- const checkLiveness = () => !child.killed && child.exitCode === null;
827
+ const checkLiveness = () => isPidAlive(child.pid);
753
828
  try {
754
829
  await waitForGuestAgentHealth(baseUrl, guestToken, {
755
830
  timeoutMs: 30 * 60 * 1000,
@@ -761,7 +836,31 @@ class QemuVmManager {
761
836
  intervalMs: 2000,
762
837
  checkLiveness,
763
838
  });
764
- fs.writeFileSync(readySentinelPath, `${new Date().toISOString()}\n`, 'utf8');
839
+ try {
840
+ await requestGuestAgent(baseUrl, guestToken, '/exec', {
841
+ command: [
842
+ 'cloud-init clean --logs --seed --machine-id || true',
843
+ 'rm -rf /var/lib/cloud/instances/* /var/lib/cloud/seed/* || true',
844
+ 'rm -f /var/lib/neoagent/bootstrap-complete /var/lib/neoagent/browser-runtime-ready || true',
845
+ 'rm -f /var/lib/systemd/random-seed || true',
846
+ 'truncate -s 0 /etc/machine-id || true',
847
+ 'sync',
848
+ ].join('; '),
849
+ timeout: 120000,
850
+ }, { timeoutMs: 120000 });
851
+ } catch (cleanupError) {
852
+ console.warn(`[VM:${this.runtimeProfile}] Template cleanup after bootstrap failed: ${cleanupError.message}`);
853
+ }
854
+ fs.writeFileSync(
855
+ readySentinelPath,
856
+ JSON.stringify({
857
+ signature: templateSignature,
858
+ runtimeProfile: this.runtimeProfile,
859
+ guestArch: this.guestArch,
860
+ builtAt: new Date().toISOString(),
861
+ }, null, 2),
862
+ 'utf8',
863
+ );
765
864
  } finally {
766
865
  try {
767
866
  if (process.platform === 'win32') {
@@ -841,6 +940,8 @@ class QemuVmManager {
841
940
  userRoot,
842
941
  guestToken,
843
942
  guestArch: this.guestArch,
943
+ runtimeMode: 'user',
944
+ runtimeProfile: this.runtimeProfile,
844
945
  });
845
946
  const consoleLogPath = path.join(userRoot, 'console.log');
846
947
  const firmware = this.guestArch === 'arm64'
@@ -881,7 +982,7 @@ class QemuVmManager {
881
982
  firmwareVarsPath,
882
983
  });
883
984
 
884
- console.log(`[VM] Starting QEMU for user ${key} (${this.guestArch}): ${qemuBinaryPath} ${args.join(' ')}`);
985
+ console.log(`[VM:${this.runtimeProfile}] Starting QEMU for user ${key} (${this.guestArch}): ${qemuBinaryPath} ${args.join(' ')}`);
885
986
  const child = spawn(qemuBinaryPath, args, {
886
987
  cwd: userRoot,
887
988
  detached: process.platform !== 'win32',
@@ -926,6 +1027,7 @@ class QemuVmManager {
926
1027
 
927
1028
  const session = {
928
1029
  userId: key,
1030
+ runtimeProfile: this.runtimeProfile,
929
1031
  process: child,
930
1032
  qemuBinary,
931
1033
  qemuArgs: args,
@@ -953,12 +1055,36 @@ class QemuVmManager {
953
1055
  if (!session) return;
954
1056
 
955
1057
  try {
1058
+ try {
1059
+ await requestGuestAgent(session.baseUrl, session.guestToken, '/browser/close', {}, { timeoutMs: 10000 });
1060
+ } catch {}
1061
+ try {
1062
+ await requestGuestAgent(session.baseUrl, session.guestToken, '/android/stop', {}, { timeoutMs: 10000 });
1063
+ } catch {}
1064
+ try {
1065
+ await requestGuestAgent(session.baseUrl, session.guestToken, '/exec', {
1066
+ command: 'sync || true',
1067
+ timeout: 15000,
1068
+ }, { timeoutMs: 20000 });
1069
+ } catch {}
1070
+
956
1071
  if (process.platform === 'win32') {
957
- spawnSync('taskkill', ['/F', '/T', '/PID', session.process.pid]);
1072
+ spawnSync('taskkill', ['/T', '/PID', session.process.pid]);
958
1073
  } else {
959
- process.kill(-session.process.pid, 'SIGKILL');
1074
+ process.kill(-session.process.pid, 'SIGTERM');
1075
+ }
1076
+ const shutdownStartedAt = Date.now();
1077
+ while (isPidAlive(session.process.pid) && Date.now() - shutdownStartedAt < 10000) {
1078
+ await sleep(250);
1079
+ }
1080
+ if (isPidAlive(session.process.pid)) {
1081
+ if (process.platform === 'win32') {
1082
+ spawnSync('taskkill', ['/F', '/T', '/PID', session.process.pid]);
1083
+ } else {
1084
+ process.kill(-session.process.pid, 'SIGKILL');
1085
+ }
960
1086
  }
961
- } catch (err) {
1087
+ } catch {
962
1088
  try {
963
1089
  session.process.kill('SIGKILL');
964
1090
  } catch {}
@@ -978,7 +1104,6 @@ class QemuVmManager {
978
1104
  }
979
1105
 
980
1106
  module.exports = {
981
- BASE_IMAGE_CACHE_ROOT,
982
1107
  DEFAULT_UBUNTU_BASE_IMAGE_URLS,
983
1108
  QemuVmManager,
984
1109
  VM_ROOT,
@@ -19,8 +19,8 @@ function getEffectiveDefaults() {
19
19
  }
20
20
 
21
21
  const BASE_FALLBACK_SETTINGS = Object.freeze({
22
- runtime_profile: 'trusted-host',
23
- runtime_backend: 'host',
22
+ runtime_profile: 'secure-vm',
23
+ runtime_backend: 'vm',
24
24
  browser_backend: 'vm',
25
25
  android_backend: 'host',
26
26
  mcp_backend: 'host-remote',
@@ -36,15 +36,10 @@ function normalizeChoice(value, allowed, fallback) {
36
36
  function deriveDefaultsForProfile(profile) {
37
37
  switch (profile) {
38
38
  case 'secure-vm':
39
- return {
40
- runtime_backend: 'vm',
41
- browser_backend: 'vm',
42
- android_backend: 'vm',
43
- };
44
39
  case 'trusted-host':
45
40
  default:
46
41
  return {
47
- runtime_backend: 'host',
42
+ runtime_backend: 'vm',
48
43
  browser_backend: 'vm',
49
44
  android_backend: 'host',
50
45
  };
@@ -56,14 +51,14 @@ function normalizeRuntimeSettings(raw = {}) {
56
51
  const defaults = getEffectiveDefaults();
57
52
  const profile = normalizeChoice(raw.runtime_profile, ['secure-vm', 'trusted-host'], defaults.runtime_profile);
58
53
  const derived = deriveDefaultsForProfile(profile);
59
- const runtimeBackend = normalizeChoice(raw.runtime_backend, ['host', 'vm'], derived.runtime_backend);
54
+ const runtimeBackend = normalizeChoice(raw.runtime_backend, ['vm'], derived.runtime_backend);
60
55
  const browserBackend = normalizeChoice(raw.browser_backend, ['vm', 'extension'], derived.browser_backend);
61
56
  const androidBackend = normalizeChoice(raw.android_backend, ['host', 'vm'], derived.android_backend);
62
57
  return {
63
- runtime_profile: profile,
64
- runtime_backend: policy.allowHostRuntime ? runtimeBackend : (runtimeBackend === 'host' ? 'vm' : runtimeBackend),
58
+ runtime_profile: profile === 'trusted-host' ? 'secure-vm' : profile,
59
+ runtime_backend: runtimeBackend,
65
60
  browser_backend: browserBackend === 'extension' ? 'extension' : 'vm',
66
- android_backend: policy.allowHostRuntime ? androidBackend : (androidBackend === 'host' ? 'vm' : androidBackend),
61
+ android_backend: androidBackend === 'vm' ? 'host' : androidBackend,
67
62
  mcp_backend: 'host-remote',
68
63
  };
69
64
  }
@@ -107,8 +102,8 @@ function validateRuntimeSettings(raw = {}) {
107
102
  if (settings.browser_backend !== 'vm' && settings.browser_backend !== 'extension') {
108
103
  issues.push('This deployment requires the VM browser backend or a paired browser extension backend.');
109
104
  }
110
- if (settings.android_backend !== 'vm') {
111
- issues.push('This deployment requires the VM Android backend.');
105
+ if (settings.android_backend !== 'host') {
106
+ issues.push('This deployment requires the host Android backend.');
112
107
  }
113
108
  }
114
109
 
@@ -5,20 +5,19 @@ const { getDeploymentPolicy } = require('../../utils/deployment');
5
5
  function getRuntimeValidation(runtimeManager) {
6
6
  const policy = getDeploymentPolicy();
7
7
  const nodeEnvIsProd = String(process.env.NODE_ENV || '').trim().toLowerCase() === 'prod';
8
- const vmReadiness = runtimeManager?.vmBackend?.vmManager?.getReadiness?.() || null;
8
+ const browserVmReadiness = runtimeManager?.browserBackend?.vmManager?.getReadiness?.() || null;
9
+ const vmReadiness = browserVmReadiness || null;
9
10
  const issues = [];
10
11
 
11
12
  if (policy.profile === 'prod' || nodeEnvIsProd) {
12
- if (!vmReadiness?.ready) {
13
- if (!vmReadiness) {
14
- issues.push('prod profile requires a working local VM runtime.');
15
- } else {
16
- if (!vmReadiness.qemuAvailable) {
17
- issues.push(`prod profile requires QEMU (${vmReadiness.qemuBinary}) to be installed.`);
18
- }
19
- if (!vmReadiness.baseImageExists && !vmReadiness.downloadConfigured) {
20
- issues.push('prod profile requires a VM base image or a downloadable base image URL.');
21
- }
13
+ if (!browserVmReadiness) {
14
+ issues.push('prod profile requires a working local VM runtime for browser/CLI.');
15
+ } else if (!browserVmReadiness.ready) {
16
+ if (!browserVmReadiness.qemuAvailable) {
17
+ issues.push(`prod profile requires QEMU (${browserVmReadiness.qemuBinary}) to be installed for browser/CLI.`);
18
+ }
19
+ if (!browserVmReadiness.baseImageExists && !browserVmReadiness.downloadConfigured) {
20
+ issues.push('prod profile requires a VM base image or a downloadable base image URL for browser/CLI.');
22
21
  }
23
22
  }
24
23
  }
@@ -65,13 +65,13 @@ function getDeploymentPolicy(env = process.env) {
65
65
  allowSelfUpdate: mode !== DEPLOYMENT_MODE_MANAGED,
66
66
  registrationOpen: isProdProfile,
67
67
  runtimeDefaults: {
68
- runtime_profile: isProdProfile ? 'secure-vm' : 'trusted-host',
69
- runtime_backend: isProdProfile ? 'vm' : 'host',
68
+ runtime_profile: 'secure-vm',
69
+ runtime_backend: 'vm',
70
70
  browser_backend: 'vm',
71
- android_backend: isProdProfile ? 'vm' : 'host',
71
+ android_backend: 'host',
72
72
  mcp_backend: 'host-remote',
73
73
  },
74
- allowHostRuntime: !isProdProfile,
74
+ allowHostRuntime: false,
75
75
  };
76
76
  }
77
77
 
@@ -1,15 +0,0 @@
1
- {
2
- "name": "neoagent-guest-agent",
3
- "private": true,
4
- "version": "1.0.0",
5
- "description": "Minimal guest runtime for NeoAgent VM browser, CLI, and Android services",
6
- "engines": {
7
- "node": ">=20"
8
- },
9
- "dependencies": {
10
- "express": "^4.21.2",
11
- "playwright": "^1.59.1",
12
- "proper-lockfile": "^4.1.2",
13
- "puppeteer-core": "^24.40.0"
14
- }
15
- }