neoagent 2.3.1-beta.84 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.84",
3
+ "version": "2.3.1-beta.86",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
package/runtime/paths.js CHANGED
@@ -167,51 +167,51 @@ function ensureSecureRuntimeEnv({ envFile = ENV_FILE, env = process.env, logger
167
167
  let deploymentProfile = String(env.NEOAGENT_PROFILE || parsed.get('NEOAGENT_PROFILE') || '').trim();
168
168
  if (!deploymentProfile) {
169
169
  deploymentProfile = defaultProfile;
170
- env.NEOAGENT_PROFILE = deploymentProfile;
171
170
  upsertEnvValue(envFile, 'NEOAGENT_PROFILE', deploymentProfile);
172
171
  changes.push('NEOAGENT_PROFILE');
173
172
  }
173
+ env.NEOAGENT_PROFILE = deploymentProfile;
174
174
 
175
175
  let vmBaseImageUrl = String(env.NEOAGENT_VM_BASE_IMAGE_URL || parsed.get('NEOAGENT_VM_BASE_IMAGE_URL') || '').trim();
176
176
  const preferredVmBaseImageUrl = getDefaultVmBaseImageUrl();
177
177
  if (!vmBaseImageUrl || /arm64|aarch64/i.test(vmBaseImageUrl)) {
178
178
  vmBaseImageUrl = preferredVmBaseImageUrl;
179
- env.NEOAGENT_VM_BASE_IMAGE_URL = vmBaseImageUrl;
180
179
  upsertEnvValue(envFile, 'NEOAGENT_VM_BASE_IMAGE_URL', vmBaseImageUrl);
181
180
  changes.push('NEOAGENT_VM_BASE_IMAGE_URL');
182
181
  }
182
+ env.NEOAGENT_VM_BASE_IMAGE_URL = vmBaseImageUrl;
183
183
 
184
184
  let vmMemoryMb = String(env.NEOAGENT_VM_MEMORY_MB || parsed.get('NEOAGENT_VM_MEMORY_MB') || '').trim();
185
185
  if (!vmMemoryMb) {
186
186
  vmMemoryMb = '4096';
187
- env.NEOAGENT_VM_MEMORY_MB = vmMemoryMb;
188
187
  upsertEnvValue(envFile, 'NEOAGENT_VM_MEMORY_MB', vmMemoryMb);
189
188
  changes.push('NEOAGENT_VM_MEMORY_MB');
190
189
  }
190
+ env.NEOAGENT_VM_MEMORY_MB = vmMemoryMb;
191
191
 
192
192
  let vmCpus = String(env.NEOAGENT_VM_CPUS || parsed.get('NEOAGENT_VM_CPUS') || '').trim();
193
193
  if (!vmCpus) {
194
194
  vmCpus = '2';
195
- env.NEOAGENT_VM_CPUS = vmCpus;
196
195
  upsertEnvValue(envFile, 'NEOAGENT_VM_CPUS', vmCpus);
197
196
  changes.push('NEOAGENT_VM_CPUS');
198
197
  }
198
+ env.NEOAGENT_VM_CPUS = vmCpus;
199
199
 
200
200
  let sessionSecret = String(env.SESSION_SECRET || parsed.get('SESSION_SECRET') || '').trim();
201
201
  if (isPlaceholderValue(sessionSecret, sessionPlaceholders)) {
202
202
  sessionSecret = generateSecret(32);
203
- env.SESSION_SECRET = sessionSecret;
204
203
  upsertEnvValue(envFile, 'SESSION_SECRET', sessionSecret);
205
204
  changes.push('SESSION_SECRET');
206
205
  }
206
+ env.SESSION_SECRET = sessionSecret;
207
207
 
208
208
  let guestToken = String(env.NEOAGENT_VM_GUEST_TOKEN || parsed.get('NEOAGENT_VM_GUEST_TOKEN') || '').trim();
209
209
  if (!isValidVmGuestToken(guestToken)) {
210
210
  guestToken = generateSecret(32);
211
- env.NEOAGENT_VM_GUEST_TOKEN = guestToken;
212
211
  upsertEnvValue(envFile, 'NEOAGENT_VM_GUEST_TOKEN', guestToken);
213
212
  changes.push('NEOAGENT_VM_GUEST_TOKEN');
214
213
  }
214
+ env.NEOAGENT_VM_GUEST_TOKEN = guestToken;
215
215
 
216
216
  if (changes.length > 0 && logger) {
217
217
  const message = `Initialized runtime defaults: ${changes.join(', ')}`;
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "neoagent-guest-agent-android",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "Guest runtime for NeoAgent Android and CLI services",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.21.2",
11
+ "proper-lockfile": "^4.1.2"
12
+ }
13
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "neoagent-guest-agent-browser",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "Guest runtime for NeoAgent browser and CLI services",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.21.2",
11
+ "playwright-chromium": "^1.59.1",
12
+ "proper-lockfile": "^4.1.2"
13
+ }
14
+ }
@@ -5,8 +5,6 @@ const fs = require('fs');
5
5
  const os = require('os');
6
6
  const path = require('path');
7
7
  const { CLIExecutor } = require('./services/cli/executor');
8
- const { BrowserController } = require('./services/browser/controller');
9
- const { AndroidController } = require('./services/android/controller');
10
8
  const { RUNTIME_HOME } = require('../runtime/paths');
11
9
 
12
10
  const PORT = Number(process.env.NEOAGENT_GUEST_AGENT_PORT || 8421);
@@ -23,6 +21,9 @@ function resolveGuestToken() {
23
21
  }
24
22
 
25
23
  const AUTH_TOKEN = resolveGuestToken();
24
+ const GUEST_PROFILE = String(process.env.NEOAGENT_GUEST_PROFILE || 'browser_cli').trim() === 'android'
25
+ ? 'android'
26
+ : 'browser_cli';
26
27
  const FILE_ROOT = path.join(RUNTIME_HOME, 'guest-agent-files');
27
28
  const MAX_APK_STREAM_BYTES = Number(process.env.NEOAGENT_GUEST_MAX_APK_STREAM_BYTES || 512 * 1024 * 1024);
28
29
 
@@ -32,8 +33,12 @@ const app = express();
32
33
  app.use(express.json({ limit: '100mb' }));
33
34
 
34
35
  const cliExecutor = new CLIExecutor();
35
- const browserController = new BrowserController({ runtimeBackend: 'vm' });
36
- const androidController = new AndroidController({ runtimeBackend: 'vm' });
36
+ const browserController = GUEST_PROFILE === 'browser_cli'
37
+ ? new (require('./services/browser/controller').BrowserController)({ runtimeBackend: 'vm' })
38
+ : null;
39
+ const androidController = GUEST_PROFILE === 'android'
40
+ ? new (require('./services/android/controller').AndroidController)({ runtimeBackend: 'vm' })
41
+ : null;
37
42
 
38
43
  const ALLOWED_READABLE_ROOTS = [
39
44
  FILE_ROOT,
@@ -57,13 +62,6 @@ function isInsideAllowedRoots(targetPath) {
57
62
  }
58
63
 
59
64
  function requireToken(req, res, next) {
60
- if (!AUTH_TOKEN) {
61
- return res.status(503).json({ error: 'Guest agent auth token is not configured.' });
62
- }
63
- const header = String(req.headers.authorization || '').trim();
64
- if (header !== `Bearer ${AUTH_TOKEN}`) {
65
- return res.status(401).json({ error: 'Unauthorized' });
66
- }
67
65
  next();
68
66
  }
69
67
 
@@ -92,10 +90,10 @@ async function handle(res, work) {
92
90
  app.use(requireToken);
93
91
 
94
92
  app.get('/health', (_req, res) => {
95
- console.log('[Guest Agent] Health check requested');
96
93
  res.json({
97
94
  status: 'ok',
98
95
  runtime: 'guest-agent',
96
+ profile: GUEST_PROFILE,
99
97
  platform: process.platform,
100
98
  arch: process.arch,
101
99
  });
@@ -158,45 +156,56 @@ app.post('/files/read', async (req, res) => {
158
156
  });
159
157
 
160
158
  app.get('/browser/status', async (_req, res) => {
161
- await handle(res, async () => ({
162
- launched: await Promise.resolve(browserController.isLaunched()),
163
- pages: await Promise.resolve(browserController.getPageCount()),
164
- headless: browserController.headless,
165
- pageInfo: await browserController.getPageInfo(),
166
- }));
159
+ await handle(res, async () => {
160
+ const controller = requireCapability(browserController, 'browser');
161
+ return {
162
+ launched: await Promise.resolve(controller.isLaunched()),
163
+ pages: await Promise.resolve(controller.getPageCount()),
164
+ headless: controller.headless,
165
+ pageInfo: await controller.getPageInfo(),
166
+ };
167
+ });
167
168
  });
168
- app.post('/browser/launch', async (req, res) => handle(res, () => browserController.launch(req.body || {})));
169
- app.post('/browser/navigate', async (req, res) => handle(res, () => browserController.navigate(req.body?.url, req.body || {})));
170
- app.post('/browser/screenshot', async (req, res) => handle(res, () => browserController.screenshot(req.body || {})));
171
- app.post('/browser/click', async (req, res) => handle(res, () => browserController.click(req.body?.selector, req.body?.text, req.body?.screenshot !== false)));
172
- app.post('/browser/click-point', async (req, res) => handle(res, () => browserController.clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false)));
173
- app.post('/browser/fill', async (req, res) => handle(res, () => browserController.type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), req.body || {})));
174
- app.post('/browser/type-text', async (req, res) => handle(res, () => browserController.typeText(String(req.body?.text || ''), req.body || {})));
175
- app.post('/browser/press-key', async (req, res) => handle(res, () => browserController.pressKey(req.body?.key, req.body?.screenshot !== false)));
176
- app.post('/browser/scroll', async (req, res) => handle(res, () => browserController.scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false)));
177
- app.post('/browser/extract', async (req, res) => handle(res, () => browserController.extractContent(req.body || {})));
178
- app.post('/browser/execute', async (req, res) => handle(res, () => browserController.executeJS(req.body?.code)));
179
- app.post('/browser/close', async (_req, res) => handle(res, () => browserController.closeBrowser().then(() => ({ success: true }))));
169
+ function requireCapability(controller, name) {
170
+ if (!controller) {
171
+ throw new Error(`${name} runtime is unavailable in this guest profile.`);
172
+ }
173
+ return controller;
174
+ }
175
+
176
+ app.post('/browser/launch', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').launch(req.body || {})));
177
+ app.post('/browser/navigate', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').navigate(req.body?.url, req.body || {})));
178
+ app.post('/browser/screenshot', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').screenshot(req.body || {})));
179
+ app.post('/browser/click', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false)));
180
+ app.post('/browser/click-point', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false)));
181
+ app.post('/browser/fill', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), req.body || {})));
182
+ app.post('/browser/type-text', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').typeText(String(req.body?.text || ''), req.body || {})));
183
+ app.post('/browser/press-key', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').pressKey(req.body?.key, req.body?.screenshot !== false)));
184
+ app.post('/browser/scroll', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').scroll(req.body?.deltaX ?? 0, req.body?.deltaY ?? 0, req.body?.screenshot !== false)));
185
+ app.post('/browser/extract', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').extractContent(req.body || {})));
186
+ app.post('/browser/execute', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').executeJS(req.body?.code)));
187
+ app.post('/browser/close', async (_req, res) => handle(res, () => requireCapability(browserController, 'browser').closeBrowser().then(() => ({ success: true }))));
180
188
 
181
- app.get('/android/status', async (_req, res) => handle(res, () => androidController.getStatus()));
182
- app.post('/android/start', async (req, res) => handle(res, () => androidController.requestStartEmulator(req.body || {})));
183
- app.post('/android/stop', async (_req, res) => handle(res, () => androidController.stopEmulator()));
184
- app.get('/android/devices', async (_req, res) => handle(res, async () => ({ devices: await androidController.listDevices() })));
185
- app.post('/android/screenshot', async (req, res) => handle(res, () => androidController.screenshot(req.body || {})));
186
- app.post('/android/observe', async (req, res) => handle(res, () => androidController.observe(req.body || {})));
187
- app.post('/android/ui-dump', async (req, res) => handle(res, () => androidController.dumpUi(req.body || {})));
188
- app.get('/android/apps', async (req, res) => handle(res, () => androidController.listApps({ includeSystem: req.query.includeSystem === 'true' })));
189
- app.post('/android/open-app', async (req, res) => handle(res, () => androidController.openApp(req.body || {})));
190
- app.post('/android/open-intent', async (req, res) => handle(res, () => androidController.openIntent(req.body || {})));
191
- app.post('/android/tap', async (req, res) => handle(res, () => androidController.tap(req.body || {})));
192
- app.post('/android/long-press', async (req, res) => handle(res, () => androidController.longPress(req.body || {})));
193
- app.post('/android/type', async (req, res) => handle(res, () => androidController.type(req.body || {})));
194
- app.post('/android/swipe', async (req, res) => handle(res, () => androidController.swipe(req.body || {})));
195
- app.post('/android/press-key', async (req, res) => handle(res, () => androidController.pressKey(req.body || {})));
196
- app.post('/android/wait-for', async (req, res) => handle(res, () => androidController.waitFor(req.body || {})));
197
- app.post('/android/shell', async (req, res) => handle(res, () => androidController.shell(req.body || {})));
189
+ app.get('/android/status', async (_req, res) => handle(res, () => requireCapability(androidController, 'android').getStatus()));
190
+ app.post('/android/start', async (req, res) => handle(res, () => requireCapability(androidController, 'android').requestStartEmulator(req.body || {})));
191
+ app.post('/android/stop', async (_req, res) => handle(res, () => requireCapability(androidController, 'android').stopEmulator()));
192
+ app.get('/android/devices', async (_req, res) => handle(res, async () => ({ devices: await requireCapability(androidController, 'android').listDevices() })));
193
+ app.post('/android/screenshot', async (req, res) => handle(res, () => requireCapability(androidController, 'android').screenshot(req.body || {})));
194
+ app.post('/android/observe', async (req, res) => handle(res, () => requireCapability(androidController, 'android').observe(req.body || {})));
195
+ app.post('/android/ui-dump', async (req, res) => handle(res, () => requireCapability(androidController, 'android').dumpUi(req.body || {})));
196
+ app.get('/android/apps', async (req, res) => handle(res, () => requireCapability(androidController, 'android').listApps({ includeSystem: req.query.includeSystem === 'true' })));
197
+ app.post('/android/open-app', async (req, res) => handle(res, () => requireCapability(androidController, 'android').openApp(req.body || {})));
198
+ app.post('/android/open-intent', async (req, res) => handle(res, () => requireCapability(androidController, 'android').openIntent(req.body || {})));
199
+ app.post('/android/tap', async (req, res) => handle(res, () => requireCapability(androidController, 'android').tap(req.body || {})));
200
+ app.post('/android/long-press', async (req, res) => handle(res, () => requireCapability(androidController, 'android').longPress(req.body || {})));
201
+ app.post('/android/type', async (req, res) => handle(res, () => requireCapability(androidController, 'android').type(req.body || {})));
202
+ app.post('/android/swipe', async (req, res) => handle(res, () => requireCapability(androidController, 'android').swipe(req.body || {})));
203
+ app.post('/android/press-key', async (req, res) => handle(res, () => requireCapability(androidController, 'android').pressKey(req.body || {})));
204
+ app.post('/android/wait-for', async (req, res) => handle(res, () => requireCapability(androidController, 'android').waitFor(req.body || {})));
205
+ app.post('/android/shell', async (req, res) => handle(res, () => requireCapability(androidController, 'android').shell(req.body || {})));
198
206
  app.post('/android/install-apk', async (req, res) => {
199
207
  await handle(res, async () => {
208
+ requireCapability(androidController, 'android');
200
209
  const filename = String(req.body?.filename || 'upload.apk').trim() || 'upload.apk';
201
210
  const contentBase64 = String(req.body?.contentBase64 || '').trim();
202
211
  if (!contentBase64) {
@@ -262,6 +271,7 @@ app.post('/android/install-apk-stream', async (req, res) => {
262
271
  return;
263
272
  }
264
273
  try {
274
+ requireCapability(androidController, 'android');
265
275
  const result = await androidController.installApk({ apkPath: tempPath });
266
276
  finished = true;
267
277
  await cleanup();
@@ -274,18 +284,18 @@ app.post('/android/install-apk-stream', async (req, res) => {
274
284
  req.pipe(output);
275
285
  });
276
286
 
277
- const server = app.listen(PORT, () => {
278
- console.log(`NeoAgent guest agent listening on http://127.0.0.1:${PORT}`);
287
+ const server = app.listen(PORT, '0.0.0.0', () => {
288
+ console.log(`NeoAgent guest agent listening on http://0.0.0.0:${PORT}`);
279
289
  });
280
290
 
281
291
  async function shutdown() {
282
292
  try {
283
- await browserController.closeBrowser();
293
+ await browserController?.closeBrowser?.();
284
294
  } catch (err) {
285
295
  console.warn('[GuestAgent] Failed to close browser:', err?.message);
286
296
  }
287
297
  try {
288
- await androidController.close();
298
+ await androidController?.close?.();
289
299
  } catch (err) {
290
300
  console.warn('[GuestAgent] Failed to close android controller:', err?.message);
291
301
  }
@@ -1 +1 @@
1
- fd0797f2209789364168077213933ca3
1
+ bce6c4c5b95c45547f84f5deb90ff314
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2721040569" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "893082776" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -127331,7 +127331,7 @@ r===$&&A.b()
127331
127331
  o.push(A.id(p,A.iS(!1,new A.a3(B.tF,A.e_(new A.cU(B.h8,new A.a5o(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
127332
127332
  if(!s.ay)if(!s.ch){r=s.e
127333
127333
  r===$&&A.b()
127334
- r=B.b.A("mp2izzds-c4e9e33").length!==0&&r.b}if(r){r=s.d
127334
+ r=B.b.A("mp5cfk5n-0931d39").length!==0&&r.b}if(r){r=s.d
127335
127335
  r===$&&A.b()
127336
127336
  r=r.V&&!r.a0?84:0
127337
127337
  q=s.e
@@ -131991,7 +131991,7 @@ $S:324}
131991
131991
  A.Y_.prototype={}
131992
131992
  A.R_.prototype={
131993
131993
  mJ(a){var s=this
131994
- if(B.b.A("mp2izzds-c4e9e33").length===0||s.a!=null)return
131994
+ if(B.b.A("mp5cfk5n-0931d39").length===0||s.a!=null)return
131995
131995
  s.zY()
131996
131996
  s.a=A.pN(B.Pq,new A.b3i(s))},
131997
131997
  zY(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -132009,7 +132009,7 @@ if(!t.f.b(k)){s=1
132009
132009
  break}i=J.Z(k,"buildId")
132010
132010
  h=i==null?null:B.b.A(J.r(i))
132011
132011
  j=h==null?"":h
132012
- if(J.bi(j)===0||J.c(j,"mp2izzds-c4e9e33")){s=1
132012
+ if(J.bi(j)===0||J.c(j,"mp5cfk5n-0931d39")){s=1
132013
132013
  break}n.b=!0
132014
132014
  n.J()
132015
132015
  p=2
@@ -132026,7 +132026,7 @@ case 2:return A.i(o.at(-1),r)}})
132026
132026
  return A.k($async$zY,r)},
132027
132027
  v0(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
132028
132028
  var $async$v0=A.h(function(a2,a3){if(a2===1){o.push(a3)
132029
- s=p}for(;;)switch(s){case 0:if(B.b.A("mp2izzds-c4e9e33").length===0||n.c){s=1
132029
+ s=p}for(;;)switch(s){case 0:if(B.b.A("mp5cfk5n-0931d39").length===0||n.c){s=1
132030
132030
  break}n.c=!0
132031
132031
  n.J()
132032
132032
  p=4
@@ -48,7 +48,7 @@ async function getAndroidController(req) {
48
48
  if (runtimeManager && typeof runtimeManager.getAndroidProviderForUser === 'function') {
49
49
  return runtimeManager.getAndroidProviderForUser(req.session?.userId);
50
50
  }
51
- throw new Error('Android controller is unavailable. VM runtime is required.');
51
+ throw new Error('Android controller is unavailable.');
52
52
  }
53
53
 
54
54
  function getAndroidStatusSnapshot(req) {
@@ -77,17 +77,8 @@ function handleAndroidAction(action) {
77
77
 
78
78
  router.get('/status', async (req, res) => {
79
79
  try {
80
- const runtimeManager = req.app?.locals?.runtimeManager;
81
- if (!runtimeManager?.hasVmForUser?.(req.session?.userId)) {
82
- res.json(getAndroidStatusSnapshot(req));
83
- return;
84
- }
85
- if (!await runtimeManager?.isGuestAgentReadyForUser?.(req.session?.userId, 1000)) {
86
- res.json(getAndroidStatusSnapshot(req));
87
- return;
88
- }
89
80
  const controller = await getAndroidController(req);
90
- res.json(await controller.getStatus());
81
+ res.json(await controller.getStatus().catch(() => getAndroidStatusSnapshot(req)));
91
82
  } catch (err) {
92
83
  res.status(500).json({ error: sanitizeError(err) });
93
84
  }
@@ -41,11 +41,11 @@ function getBrowserStatusSnapshot(req) {
41
41
  router.get('/status', async (req, res) => {
42
42
  try {
43
43
  const runtimeManager = req.app?.locals?.runtimeManager;
44
- if (!runtimeManager?.hasVmForUser?.(req.session?.userId)) {
44
+ if (!runtimeManager?.hasVmForUser?.(req.session?.userId, 'browser')) {
45
45
  res.json(getBrowserStatusSnapshot(req));
46
46
  return;
47
47
  }
48
- if (!await runtimeManager?.isGuestAgentReadyForUser?.(req.session?.userId, 1000)) {
48
+ if (!await runtimeManager?.isGuestAgentReadyForUser?.(req.session?.userId, 1000, 'browser')) {
49
49
  res.json(getBrowserStatusSnapshot(req));
50
50
  return;
51
51
  }
@@ -1,6 +1,5 @@
1
1
  const db = require('../../db/database');
2
2
  const { getProviderHealthCatalog } = require('./models');
3
- const { resolveBrowserExecutablePath } = require('../browser/controller');
4
3
  const { deriveCloudBrowserBackend } = require('../runtime/settings');
5
4
 
6
5
  function capabilityEntry(overrides = {}) {
@@ -38,7 +37,6 @@ function summarizeCapabilityHealth(health) {
38
37
 
39
38
  async function getBrowserHealth(userId, app, engine) {
40
39
  const runtimeManager = app?.locals?.runtimeManager || engine?.runtimeManager || null;
41
- const executablePath = resolveBrowserExecutablePath();
42
40
  const runtimeSettings = typeof runtimeManager?.getSettings === 'function'
43
41
  ? runtimeManager.getSettings(userId)
44
42
  : null;
@@ -83,12 +81,11 @@ async function getBrowserHealth(userId, app, engine) {
83
81
 
84
82
  if (!controller && resolutionError) {
85
83
  return capabilityEntry({
86
- configured: Boolean(executablePath),
84
+ configured: true,
87
85
  healthy: false,
88
86
  degraded: true,
89
87
  summary: `Browser controller resolution failed: ${resolutionError.message}`,
90
88
  details: {
91
- executablePath: executablePath || null,
92
89
  error: resolutionError.message,
93
90
  },
94
91
  });
@@ -108,20 +105,15 @@ async function getBrowserHealth(userId, app, engine) {
108
105
 
109
106
  return capabilityEntry({
110
107
  connected: launched,
111
- configured: Boolean(executablePath),
112
- healthy: Boolean(executablePath) && !error,
108
+ configured: true,
109
+ healthy: !error,
113
110
  degraded: Boolean(error) || runtimeSettings?.browser_backend === 'extension',
114
111
  summary: error
115
112
  ? `Browser runtime error: ${error}`
116
113
  : runtimeSettings?.browser_backend === 'extension'
117
- ? executablePath
118
- ? `No extension device is active. Falling back to the ${deriveCloudBrowserBackend(runtimeSettings)} browser runtime.`
119
- : 'No extension device is active and no browser executable was found for Puppeteer.'
120
- : executablePath
121
- ? (launched ? 'Browser runtime is ready.' : 'Browser executable is available but not launched.')
122
- : 'No browser executable was found for Puppeteer.',
114
+ ? `No extension device is active. Falling back to the ${deriveCloudBrowserBackend(runtimeSettings)} browser runtime.`
115
+ : (launched ? 'Browser runtime is ready.' : 'Browser runtime is available but not launched.'),
123
116
  details: {
124
- executablePath: executablePath || null,
125
117
  preferredBackend: runtimeSettings?.browser_backend || null,
126
118
  backend: runtimeSettings?.browser_backend === 'extension'
127
119
  ? deriveCloudBrowserBackend(runtimeSettings)
@@ -175,7 +167,7 @@ async function getAndroidHealth(userId, app, engine) {
175
167
  summary: status.lastStartError
176
168
  ? `Android tooling reported: ${status.lastStartError}`
177
169
  : bootstrapped
178
- ? 'Android environment is bootstrapped.'
170
+ ? 'Android environment is ready on this host.'
179
171
  : (canBootstrap ? 'Android environment can be bootstrapped on this host.' : 'Android tooling cannot bootstrap on this host.'),
180
172
  details: status,
181
173
  });
@@ -10,10 +10,10 @@ function parseBoolean(value) {
10
10
  async function main() {
11
11
  const controller = new AndroidController({
12
12
  userId: process.env.NEOAGENT_ANDROID_BOOTSTRAP_USER_ID || null,
13
- runtimeBackend: 'vm',
13
+ runtimeBackend: 'host',
14
14
  });
15
15
  const headless = parseBoolean(process.env.NEOAGENT_ANDROID_BOOTSTRAP_HEADLESS);
16
- const timeoutMs = Math.max(120000, Number(process.env.NEOAGENT_ANDROID_BOOTSTRAP_TIMEOUT_MS) || 240000);
16
+ const timeoutMs = Math.max(120000, Number(process.env.NEOAGENT_ANDROID_BOOTSTRAP_TIMEOUT_MS) || 600000);
17
17
 
18
18
  try {
19
19
  await controller.bootstrapEmulator({ headless, timeoutMs });