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 +1 -1
- package/runtime/paths.js +6 -6
- package/server/guest-agent.android.package.json +13 -0
- package/server/guest-agent.browser.package.json +14 -0
- package/server/guest_agent.js +61 -51
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/routes/android.js +2 -11
- package/server/routes/browser.js +2 -2
- package/server/services/ai/capabilityHealth.js +6 -14
- package/server/services/android/android_bootstrap_worker.js +2 -2
- package/server/services/android/controller.js +529 -133
- package/server/services/browser/controller.js +187 -42
- package/server/services/runtime/backends/local-vm.js +62 -33
- package/server/services/runtime/guest_bootstrap.js +287 -113
- package/server/services/runtime/manager.js +53 -15
- package/server/services/runtime/qemu.js +477 -86
- package/server/services/runtime/settings.js +9 -14
- package/server/services/runtime/validation.js +11 -38
- package/server/utils/deployment.js +4 -4
- package/server/guest-agent.package.json +0 -16
package/package.json
CHANGED
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
|
+
}
|
package/server/guest_agent.js
CHANGED
|
@@ -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 =
|
|
36
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
app.post('/browser/
|
|
176
|
-
app.post('/browser/
|
|
177
|
-
app.post('/browser/
|
|
178
|
-
app.post('/browser/
|
|
179
|
-
app.post('/browser/
|
|
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://
|
|
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
|
|
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
|
|
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
|
-
|
|
1
|
+
bce6c4c5b95c45547f84f5deb90ff314
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
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("
|
|
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("
|
|
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,"
|
|
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("
|
|
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
|
package/server/routes/android.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/server/routes/browser.js
CHANGED
|
@@ -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:
|
|
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:
|
|
112
|
-
healthy:
|
|
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
|
-
?
|
|
118
|
-
|
|
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
|
|
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: '
|
|
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) ||
|
|
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 });
|