neoagent 2.3.1-beta.85 → 2.3.1-beta.87
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/docs/capabilities.md +2 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +14 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +84 -0
- package/flutter_app/lib/main_chat.dart +156 -2
- package/flutter_app/lib/main_controller.dart +137 -10
- package/flutter_app/lib/main_models.dart +69 -0
- package/flutter_app/lib/main_operations.dart +248 -0
- package/flutter_app/lib/main_runtime.dart +11 -2
- package/flutter_app/lib/main_settings.dart +173 -176
- package/flutter_app/lib/main_shared.dart +78 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +39 -10
- package/flutter_app/lib/src/backend_client.dart +28 -0
- package/package.json +1 -1
- package/server/guest-agent.android.package.json +13 -0
- package/server/guest-agent.browser.package.json +14 -0
- package/server/guest_agent.js +61 -44
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +69936 -69277
- package/server/routes/android.js +2 -11
- package/server/routes/browser.js +2 -2
- package/server/routes/memory.js +90 -0
- package/server/routes/social_video.js +62 -0
- package/server/services/ai/capabilityHealth.js +6 -14
- package/server/services/ai/systemPrompt.js +1 -0
- package/server/services/ai/toolResult.js +20 -0
- package/server/services/ai/tools.js +29 -0
- package/server/services/android/android_bootstrap_worker.js +2 -2
- package/server/services/android/controller.js +528 -132
- package/server/services/browser/controller.js +51 -68
- package/server/services/manager.js +15 -0
- package/server/services/memory/llm_transfer.js +217 -0
- package/server/services/runtime/backends/local-vm.js +16 -3
- package/server/services/runtime/guest_bootstrap.js +224 -56
- package/server/services/runtime/manager.js +53 -15
- package/server/services/runtime/qemu.js +149 -24
- package/server/services/runtime/settings.js +9 -14
- package/server/services/runtime/validation.js +10 -11
- package/server/services/social_video/adapters/base.js +26 -0
- package/server/services/social_video/adapters/index.js +27 -0
- package/server/services/social_video/adapters/instagram.js +17 -0
- package/server/services/social_video/adapters/tiktok.js +17 -0
- package/server/services/social_video/adapters/x.js +17 -0
- package/server/services/social_video/adapters/youtube.js +17 -0
- package/server/services/social_video/captions.js +187 -0
- package/server/services/social_video/frame.js +42 -0
- package/server/services/social_video/index.js +7 -0
- package/server/services/social_video/metadata.js +63 -0
- package/server/services/social_video/result.js +63 -0
- package/server/services/social_video/service.js +576 -0
- package/server/services/social_video/url.js +83 -0
- package/server/utils/deployment.js +4 -4
- package/server/guest-agent.package.json +0 -15
|
@@ -6,15 +6,24 @@ const { DATA_DIR } = require('../../../runtime/paths');
|
|
|
6
6
|
const VM_ROOT = path.join(DATA_DIR, 'runtime-vms');
|
|
7
7
|
const GUEST_BOOTSTRAP_ROOT = path.join(VM_ROOT, 'guest-bootstrap');
|
|
8
8
|
const REPO_ROOT = path.resolve(__dirname, '../../..');
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
]
|
|
9
|
+
const GUEST_PAYLOAD_PROFILES = Object.freeze({
|
|
10
|
+
browser_cli: [
|
|
11
|
+
{ source: 'server/guest-agent.browser.package.json', target: 'package.json' },
|
|
12
|
+
{ source: 'runtime/env.js', target: 'runtime/env.js' },
|
|
13
|
+
{ source: 'runtime/paths.js', target: 'runtime/paths.js' },
|
|
14
|
+
{ source: 'server/guest_agent.js', target: 'server/guest_agent.js' },
|
|
15
|
+
{ source: 'server/services/cli', target: 'server/services/cli' },
|
|
16
|
+
{ source: 'server/services/browser', target: 'server/services/browser' },
|
|
17
|
+
],
|
|
18
|
+
android: [
|
|
19
|
+
{ source: 'server/guest-agent.android.package.json', target: 'package.json' },
|
|
20
|
+
{ source: 'runtime/env.js', target: 'runtime/env.js' },
|
|
21
|
+
{ source: 'runtime/paths.js', target: 'runtime/paths.js' },
|
|
22
|
+
{ source: 'server/guest_agent.js', target: 'server/guest_agent.js' },
|
|
23
|
+
{ source: 'server/services/cli', target: 'server/services/cli' },
|
|
24
|
+
{ source: 'server/services/android', target: 'server/services/android' },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
18
27
|
|
|
19
28
|
fs.mkdirSync(GUEST_BOOTSTRAP_ROOT, { recursive: true });
|
|
20
29
|
|
|
@@ -22,15 +31,20 @@ function encodeGuestToken(value) {
|
|
|
22
31
|
return Buffer.from(String(value || ''), 'utf8').toString('base64');
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
function
|
|
34
|
+
function normalizeRuntimeProfile(runtimeProfile) {
|
|
35
|
+
return runtimeProfile === 'android' ? 'android' : 'browser_cli';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createGuestPayloadArchive(seedDir, runtimeProfile = 'browser_cli') {
|
|
26
39
|
const seedRoot = path.dirname(seedDir);
|
|
27
40
|
const stagingRoot = path.join(seedRoot, 'guest-payload');
|
|
28
41
|
const archivePath = path.join(seedRoot, 'guest-payload.tar.gz');
|
|
42
|
+
const payloadEntries = GUEST_PAYLOAD_PROFILES[normalizeRuntimeProfile(runtimeProfile)];
|
|
29
43
|
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
30
44
|
fs.rmSync(archivePath, { force: true });
|
|
31
45
|
fs.mkdirSync(stagingRoot, { recursive: true });
|
|
32
46
|
|
|
33
|
-
for (const entry of
|
|
47
|
+
for (const entry of payloadEntries) {
|
|
34
48
|
const sourcePath = path.join(REPO_ROOT, entry.source);
|
|
35
49
|
const targetPath = path.join(stagingRoot, entry.target);
|
|
36
50
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
@@ -59,10 +73,17 @@ function createCloudInitScript({
|
|
|
59
73
|
guestToken,
|
|
60
74
|
guestPayloadPath = '/var/lib/neoagent/guest-payload.tar.gz',
|
|
61
75
|
guestAgentPort = 8421,
|
|
76
|
+
runtimeProfile = 'browser_cli',
|
|
62
77
|
}) {
|
|
78
|
+
const normalizedProfile = normalizeRuntimeProfile(runtimeProfile);
|
|
79
|
+
const includeBrowser = normalizedProfile === 'browser_cli';
|
|
80
|
+
const guestUtilityPackages = includeBrowser
|
|
81
|
+
? 'curl ca-certificates gnupg git rsync unzip xvfb dbus-x11'
|
|
82
|
+
: 'curl ca-certificates gnupg git rsync unzip dbus-x11 adb';
|
|
63
83
|
const guestTokenB64 = encodeGuestToken(guestToken);
|
|
64
84
|
const envFile = '/etc/neoagent/neoagent.env';
|
|
65
85
|
const appDir = '/opt/neoagent';
|
|
86
|
+
const playwrightBrowsersPath = `${appDir}/.playwright-browsers`;
|
|
66
87
|
const bootstrapMarker = '/var/lib/neoagent/bootstrap-complete';
|
|
67
88
|
const browserReadyMarker = '/var/lib/neoagent/browser-runtime-ready';
|
|
68
89
|
const browserDepsMarker = '/var/lib/neoagent/browser-deps-installed';
|
|
@@ -74,6 +95,7 @@ function createCloudInitScript({
|
|
|
74
95
|
'',
|
|
75
96
|
'export DEBIAN_FRONTEND=noninteractive',
|
|
76
97
|
`APP_DIR=${JSON.stringify(appDir)}`,
|
|
98
|
+
`PLAYWRIGHT_BROWSERS_PATH=${JSON.stringify(playwrightBrowsersPath)}`,
|
|
77
99
|
`BOOTSTRAP_MARKER=${JSON.stringify(bootstrapMarker)}`,
|
|
78
100
|
`BROWSER_READY_MARKER=${JSON.stringify(browserReadyMarker)}`,
|
|
79
101
|
`BROWSER_DEPS_MARKER=${JSON.stringify(browserDepsMarker)}`,
|
|
@@ -123,53 +145,47 @@ function createCloudInitScript({
|
|
|
123
145
|
'',
|
|
124
146
|
`printf '%s\n' ${JSON.stringify(`NEOAGENT_VM_GUEST_TOKEN_B64=${guestTokenB64}`)} > "$ENV_FILE"`,
|
|
125
147
|
`printf '%s\n' ${JSON.stringify(`NEOAGENT_GUEST_AGENT_PORT=${guestAgentPort}`)} >> "$ENV_FILE"`,
|
|
148
|
+
`printf '%s\n' ${JSON.stringify(`NEOAGENT_GUEST_PROFILE=${normalizedProfile}`)} >> "$ENV_FILE"`,
|
|
126
149
|
'chmod 0600 "$ENV_FILE"',
|
|
127
150
|
'',
|
|
128
151
|
'cd "$APP_DIR"',
|
|
129
152
|
'export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
|
|
153
|
+
'export PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH"',
|
|
130
154
|
'if [ ! -d node_modules ] || [ ! -f node_modules/.neoagent-bootstrap-stamp ] || [ package.json -nt node_modules/.neoagent-bootstrap-stamp ]; then',
|
|
131
155
|
' echo "Installing npm dependencies..."',
|
|
132
|
-
' retry_cmd npm
|
|
133
|
-
' retry_cmd npm install --omit=dev --no-audit --no-fund || { echo "Error: npm install failed." >&2; exit 1; }',
|
|
156
|
+
' retry_cmd npm install --omit=dev --ignore-scripts --prefer-offline --no-audit --no-fund || { echo "Error: npm install failed." >&2; exit 1; }',
|
|
134
157
|
' mkdir -p node_modules',
|
|
135
158
|
' date > node_modules/.neoagent-bootstrap-stamp',
|
|
136
159
|
'fi',
|
|
137
160
|
'',
|
|
138
|
-
'if [ ! -f "$BROWSER_DEPS_MARKER" ]; then',
|
|
139
|
-
' echo "Updating package lists..."',
|
|
140
|
-
' retry_cmd apt-get update || echo "Warning: apt-get update failed, proceeding with cached lists."',
|
|
141
|
-
'',
|
|
142
|
-
' echo "Installing browser runtime dependencies..."',
|
|
143
|
-
' retry_cmd apt-get install -y --no-install-recommends \\',
|
|
144
|
-
' curl ca-certificates gnupg git rsync unzip \\',
|
|
145
|
-
' dbus-x11 \\',
|
|
146
|
-
' xvfb \\',
|
|
147
|
-
' libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 libcups2 \\',
|
|
148
|
-
' libx11-xcb1 libgtk-3-0 libnss3 libnspr4 libxcomposite1 libxdamage1 \\',
|
|
149
|
-
' libxrandr2 libxkbcommon0 libasound2t64 libgbm1 libdrm2 libdbus-1-3 \\',
|
|
150
|
-
' libpango-1.0-0 libpangocairo-1.0-0 libxshmfence1 || echo "Warning: Some browser dependencies failed to install."',
|
|
151
|
-
' touch "$BROWSER_DEPS_MARKER"',
|
|
152
|
-
'else',
|
|
153
|
-
' echo "Browser runtime dependencies already installed; skipping apt install."',
|
|
154
|
-
'fi',
|
|
155
161
|
'',
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
|
|
159
|
-
' systemctl start neoagent-guest-agent.service || true',
|
|
160
|
-
'fi',
|
|
161
|
-
'echo "NeoAgent guest agent is available; continuing browser runtime provisioning..."',
|
|
162
|
+
'echo "Ensuring guest runtime utilities..."',
|
|
163
|
+
'retry_cmd apt-get update || echo "Warning: apt-get update failed, proceeding with cached lists."',
|
|
164
|
+
`retry_cmd apt-get install -y --no-install-recommends ${guestUtilityPackages} || { echo "Error: Failed to install required guest runtime utilities." >&2; exit 1; }`,
|
|
162
165
|
'',
|
|
163
|
-
'
|
|
164
|
-
'PLAYWRIGHT_STAMP="$PLAYWRIGHT_BROWSERS_PATH/.firefox-installed"',
|
|
165
|
-
'mkdir -p "$PLAYWRIGHT_BROWSERS_PATH"',
|
|
166
|
-
'if [ ! -f "$PLAYWRIGHT_STAMP" ] || [ package.json -nt "$PLAYWRIGHT_STAMP" ]; then',
|
|
167
|
-
' echo "Installing Playwright browsers..."',
|
|
168
|
-
' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" retry_cmd npx playwright install firefox || { echo "Error: Playwright browser install failed." >&2; exit 1; }',
|
|
169
|
-
' date > "$PLAYWRIGHT_STAMP"',
|
|
170
|
-
'fi',
|
|
166
|
+
'echo "NeoAgent guest runtime payload is ready."',
|
|
171
167
|
'',
|
|
172
|
-
|
|
168
|
+
...(includeBrowser
|
|
169
|
+
? [
|
|
170
|
+
'echo "Continuing browser runtime provisioning..."',
|
|
171
|
+
'PLAYWRIGHT_BROWSERS_PATH="$APP_DIR/.playwright-browsers"',
|
|
172
|
+
'PLAYWRIGHT_STAMP="$PLAYWRIGHT_BROWSERS_PATH/.chromium-installed"',
|
|
173
|
+
'mkdir -p "$PLAYWRIGHT_BROWSERS_PATH"',
|
|
174
|
+
'if [ ! -f "$BROWSER_DEPS_MARKER" ]; then',
|
|
175
|
+
' echo "Installing Playwright browser dependencies..."',
|
|
176
|
+
' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" retry_cmd npx playwright install-deps chromium || { echo "Error: Playwright dependency install failed." >&2; exit 1; }',
|
|
177
|
+
' touch "$BROWSER_DEPS_MARKER"',
|
|
178
|
+
'fi',
|
|
179
|
+
'if [ ! -f "$PLAYWRIGHT_STAMP" ] || [ package.json -nt "$PLAYWRIGHT_STAMP" ]; then',
|
|
180
|
+
' echo "Installing Playwright browsers..."',
|
|
181
|
+
' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" retry_cmd npx playwright install chromium || { echo "Error: Playwright browser install failed." >&2; exit 1; }',
|
|
182
|
+
' date > "$PLAYWRIGHT_STAMP"',
|
|
183
|
+
'fi',
|
|
184
|
+
'touch "$BROWSER_READY_MARKER"',
|
|
185
|
+
]
|
|
186
|
+
: [
|
|
187
|
+
'rm -f "$BROWSER_READY_MARKER"',
|
|
188
|
+
]),
|
|
173
189
|
'touch "$BOOTSTRAP_MARKER"',
|
|
174
190
|
'echo "NeoAgent guest bootstrap completed."',
|
|
175
191
|
'',
|
|
@@ -178,16 +194,109 @@ function createCloudInitScript({
|
|
|
178
194
|
|
|
179
195
|
function createCloudInitUserData({
|
|
180
196
|
guestToken,
|
|
181
|
-
guestPayloadBase64,
|
|
197
|
+
guestPayloadBase64 = '',
|
|
182
198
|
guestAgentPort = 8421,
|
|
199
|
+
runtimeMode = 'template',
|
|
200
|
+
runtimeProfile = 'browser_cli',
|
|
183
201
|
}) {
|
|
202
|
+
const normalizedProfile = normalizeRuntimeProfile(runtimeProfile);
|
|
203
|
+
const includeBrowser = normalizedProfile === 'browser_cli';
|
|
204
|
+
const guestAgentInnerCommand = includeBrowser
|
|
205
|
+
? 'set -a; . /etc/neoagent/neoagent.env; set +a; cd /opt/neoagent && env DISPLAY=:99 PLAYWRIGHT_BROWSERS_PATH=/opt/neoagent/.playwright-browsers /usr/bin/env node server/guest_agent.js 2>&1 | tee -a /var/log/neoagent-guest-agent.log >/dev/console'
|
|
206
|
+
: 'set -a; . /etc/neoagent/neoagent.env; set +a; cd /opt/neoagent && /usr/bin/env node server/guest_agent.js 2>&1 | tee -a /var/log/neoagent-guest-agent.log >/dev/console';
|
|
207
|
+
const guestAgentLaunchCommand = `nohup /bin/sh -lc ${JSON.stringify(guestAgentInnerCommand)} </dev/null >/dev/null 2>&1 &`;
|
|
184
208
|
const guestTokenB64 = encodeGuestToken(guestToken);
|
|
185
209
|
const bootstrapScript = createCloudInitScript({
|
|
186
210
|
guestToken,
|
|
187
211
|
guestPayloadPath: '/var/lib/neoagent/guest-payload.tar.gz',
|
|
188
212
|
guestAgentPort,
|
|
213
|
+
runtimeProfile: normalizedProfile,
|
|
189
214
|
});
|
|
190
215
|
|
|
216
|
+
if (runtimeMode === 'user') {
|
|
217
|
+
return [
|
|
218
|
+
'#cloud-config',
|
|
219
|
+
'package_update: false',
|
|
220
|
+
'write_files:',
|
|
221
|
+
' - path: /etc/neoagent/neoagent.env',
|
|
222
|
+
" permissions: '0600'",
|
|
223
|
+
' owner: root:root',
|
|
224
|
+
' content: |',
|
|
225
|
+
` NEOAGENT_VM_GUEST_TOKEN_B64=${guestTokenB64}`,
|
|
226
|
+
` NEOAGENT_GUEST_AGENT_PORT=${guestAgentPort}`,
|
|
227
|
+
` NEOAGENT_GUEST_PROFILE=${normalizedProfile}`,
|
|
228
|
+
...(includeBrowser
|
|
229
|
+
? [
|
|
230
|
+
' - path: /etc/systemd/system/neoagent-xvfb.service',
|
|
231
|
+
" permissions: '0644'",
|
|
232
|
+
' owner: root:root',
|
|
233
|
+
' content: |',
|
|
234
|
+
' [Unit]',
|
|
235
|
+
' Description=NeoAgent virtual display',
|
|
236
|
+
' After=network-online.target',
|
|
237
|
+
' Wants=network-online.target',
|
|
238
|
+
'',
|
|
239
|
+
' [Service]',
|
|
240
|
+
' Type=simple',
|
|
241
|
+
' ExecStart=/usr/bin/Xvfb :99 -screen 0 1440x900x24 -ac -nolisten tcp',
|
|
242
|
+
' Restart=always',
|
|
243
|
+
' RestartSec=2',
|
|
244
|
+
' StandardOutput=journal+console',
|
|
245
|
+
' StandardError=journal+console',
|
|
246
|
+
'',
|
|
247
|
+
' [Install]',
|
|
248
|
+
' WantedBy=multi-user.target',
|
|
249
|
+
]
|
|
250
|
+
: []),
|
|
251
|
+
' - path: /etc/systemd/system/neoagent-guest-agent.service',
|
|
252
|
+
" permissions: '0644'",
|
|
253
|
+
' owner: root:root',
|
|
254
|
+
' content: |',
|
|
255
|
+
' [Unit]',
|
|
256
|
+
' Description=NeoAgent guest agent',
|
|
257
|
+
' After=network-online.target',
|
|
258
|
+
...(includeBrowser ? [' After=neoagent-xvfb.service'] : []),
|
|
259
|
+
' ConditionPathExists=/etc/neoagent/neoagent.env',
|
|
260
|
+
' Wants=network-online.target',
|
|
261
|
+
'',
|
|
262
|
+
' [Service]',
|
|
263
|
+
' Type=simple',
|
|
264
|
+
' EnvironmentFile=/etc/neoagent/neoagent.env',
|
|
265
|
+
' ExecStartPre=/bin/mkdir -p /var/lib/neoagent',
|
|
266
|
+
...(includeBrowser
|
|
267
|
+
? [
|
|
268
|
+
' ExecStartPre=/usr/bin/touch /var/lib/neoagent/browser-runtime-ready',
|
|
269
|
+
' ExecStartPre=/bin/sh -lc \'for _ in $(seq 1 30); do [ -S /tmp/.X11-unix/X99 ] && exit 0; sleep 1; done; exit 1\'',
|
|
270
|
+
' Environment=DISPLAY=:99',
|
|
271
|
+
' Environment=PLAYWRIGHT_BROWSERS_PATH=/opt/neoagent/.playwright-browsers',
|
|
272
|
+
]
|
|
273
|
+
: [
|
|
274
|
+
' ExecStartPre=/bin/sh -lc \'rm -f /var/lib/neoagent/browser-runtime-ready || true\'',
|
|
275
|
+
]),
|
|
276
|
+
' ExecStartPre=/usr/bin/touch /var/lib/neoagent/bootstrap-complete',
|
|
277
|
+
' WorkingDirectory=/opt/neoagent',
|
|
278
|
+
' ExecStart=/usr/bin/env node /opt/neoagent/server/guest_agent.js',
|
|
279
|
+
' Restart=always',
|
|
280
|
+
' RestartSec=5',
|
|
281
|
+
' StandardOutput=journal+console',
|
|
282
|
+
' StandardError=journal+console',
|
|
283
|
+
'',
|
|
284
|
+
' [Install]',
|
|
285
|
+
' WantedBy=multi-user.target',
|
|
286
|
+
'runcmd:',
|
|
287
|
+
' - [bash, -lc, "systemctl daemon-reload"]',
|
|
288
|
+
...(includeBrowser
|
|
289
|
+
? [
|
|
290
|
+
' - [bash, -lc, "systemctl enable neoagent-xvfb.service"]',
|
|
291
|
+
' - [bash, -lc, "systemctl start neoagent-xvfb.service"]',
|
|
292
|
+
]
|
|
293
|
+
: []),
|
|
294
|
+
' - [bash, -lc, "systemctl enable neoagent-guest-agent.service"]',
|
|
295
|
+
' - [bash, -lc, "systemctl start --no-block neoagent-guest-agent.service"]',
|
|
296
|
+
'',
|
|
297
|
+
].join('\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
191
300
|
return [
|
|
192
301
|
'#cloud-config',
|
|
193
302
|
'package_update: false',
|
|
@@ -195,9 +304,10 @@ function createCloudInitUserData({
|
|
|
195
304
|
' - path: /etc/neoagent/neoagent.env',
|
|
196
305
|
" permissions: '0600'",
|
|
197
306
|
' owner: root:root',
|
|
198
|
-
|
|
307
|
+
' content: |',
|
|
199
308
|
` NEOAGENT_VM_GUEST_TOKEN_B64=${guestTokenB64}`,
|
|
200
309
|
` NEOAGENT_GUEST_AGENT_PORT=${guestAgentPort}`,
|
|
310
|
+
` NEOAGENT_GUEST_PROFILE=${normalizedProfile}`,
|
|
201
311
|
' - path: /var/lib/neoagent/guest-payload.tar.gz',
|
|
202
312
|
" permissions: '0644'",
|
|
203
313
|
' owner: root:root',
|
|
@@ -216,12 +326,22 @@ function createCloudInitUserData({
|
|
|
216
326
|
' [Unit]',
|
|
217
327
|
' Description=NeoAgent guest agent',
|
|
218
328
|
' After=network-online.target',
|
|
329
|
+
' After=cloud-final.service',
|
|
330
|
+
' After=neoagent-guest-bootstrap.service',
|
|
331
|
+
...(includeBrowser ? [' After=neoagent-xvfb.service'] : []),
|
|
332
|
+
' ConditionPathExists=/etc/neoagent/neoagent.env',
|
|
333
|
+
...(includeBrowser ? [' Requires=neoagent-xvfb.service'] : []),
|
|
219
334
|
' Wants=network-online.target',
|
|
220
335
|
'',
|
|
221
336
|
' [Service]',
|
|
222
337
|
' Type=simple',
|
|
223
338
|
' EnvironmentFile=/etc/neoagent/neoagent.env',
|
|
224
|
-
|
|
339
|
+
...(includeBrowser
|
|
340
|
+
? [
|
|
341
|
+
' Environment=DISPLAY=:99',
|
|
342
|
+
' Environment=PLAYWRIGHT_BROWSERS_PATH=/opt/neoagent/.playwright-browsers',
|
|
343
|
+
]
|
|
344
|
+
: []),
|
|
225
345
|
' WorkingDirectory=/opt/neoagent',
|
|
226
346
|
' ExecStart=/usr/bin/env node /opt/neoagent/server/guest_agent.js',
|
|
227
347
|
' Restart=always',
|
|
@@ -231,6 +351,29 @@ function createCloudInitUserData({
|
|
|
231
351
|
'',
|
|
232
352
|
' [Install]',
|
|
233
353
|
' WantedBy=multi-user.target',
|
|
354
|
+
...(includeBrowser
|
|
355
|
+
? [
|
|
356
|
+
' - path: /etc/systemd/system/neoagent-xvfb.service',
|
|
357
|
+
" permissions: '0644'",
|
|
358
|
+
' owner: root:root',
|
|
359
|
+
' content: |',
|
|
360
|
+
' [Unit]',
|
|
361
|
+
' Description=NeoAgent virtual display',
|
|
362
|
+
' After=network-online.target',
|
|
363
|
+
' Wants=network-online.target',
|
|
364
|
+
'',
|
|
365
|
+
' [Service]',
|
|
366
|
+
' Type=simple',
|
|
367
|
+
' ExecStart=/usr/bin/Xvfb :99 -screen 0 1440x900x24 -ac -nolisten tcp',
|
|
368
|
+
' Restart=always',
|
|
369
|
+
' RestartSec=2',
|
|
370
|
+
' StandardOutput=journal+console',
|
|
371
|
+
' StandardError=journal+console',
|
|
372
|
+
'',
|
|
373
|
+
' [Install]',
|
|
374
|
+
' WantedBy=multi-user.target',
|
|
375
|
+
]
|
|
376
|
+
: []),
|
|
234
377
|
' - path: /etc/systemd/system/neoagent-guest-bootstrap.service',
|
|
235
378
|
" permissions: '0644'",
|
|
236
379
|
' owner: root:root',
|
|
@@ -249,8 +392,14 @@ function createCloudInitUserData({
|
|
|
249
392
|
' WantedBy=multi-user.target',
|
|
250
393
|
'runcmd:',
|
|
251
394
|
' - [bash, -lc, "systemctl daemon-reload"]',
|
|
252
|
-
|
|
253
|
-
|
|
395
|
+
...(includeBrowser
|
|
396
|
+
? [
|
|
397
|
+
' - [bash, -lc, "systemctl enable neoagent-xvfb.service"]',
|
|
398
|
+
' - [bash, -lc, "systemctl start neoagent-xvfb.service"]',
|
|
399
|
+
]
|
|
400
|
+
: []),
|
|
401
|
+
' - [bash, -lc, "/usr/local/bin/neoagent-guest-bootstrap.sh"]',
|
|
402
|
+
` - [bash, -lc, ${JSON.stringify(guestAgentLaunchCommand)}]`,
|
|
254
403
|
'',
|
|
255
404
|
].join('\n');
|
|
256
405
|
}
|
|
@@ -263,6 +412,21 @@ function createCloudInitMetaData({ instanceId, localHostName }) {
|
|
|
263
412
|
].join('\n');
|
|
264
413
|
}
|
|
265
414
|
|
|
415
|
+
function resolveCloudInitIdentity(userRoot) {
|
|
416
|
+
const relativePath = path.relative(VM_ROOT, path.resolve(userRoot || ''));
|
|
417
|
+
const normalized = relativePath
|
|
418
|
+
.split(path.sep)
|
|
419
|
+
.filter(Boolean)
|
|
420
|
+
.map((segment) => segment.replace(/[^a-z0-9_-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 32))
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
.join('-');
|
|
423
|
+
const scope = normalized || 'default';
|
|
424
|
+
return {
|
|
425
|
+
instanceId: `neoagent-${scope}`,
|
|
426
|
+
localHostName: `neoagent-${scope}`,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
266
430
|
function commandExists(command) {
|
|
267
431
|
const probe = spawnSync(
|
|
268
432
|
process.platform === 'win32' ? 'where' : 'bash',
|
|
@@ -413,6 +577,8 @@ function ensureGuestBootstrapSeed({
|
|
|
413
577
|
guestToken,
|
|
414
578
|
guestAgentPort = 8421,
|
|
415
579
|
guestArch = 'x64',
|
|
580
|
+
runtimeMode = 'template',
|
|
581
|
+
runtimeProfile = 'browser_cli',
|
|
416
582
|
}) {
|
|
417
583
|
const seedRoot = path.join(userRoot, 'cloud-init');
|
|
418
584
|
const seedDir = path.join(seedRoot, 'seed');
|
|
@@ -423,17 +589,18 @@ function ensureGuestBootstrapSeed({
|
|
|
423
589
|
const userDataPath = path.join(seedDir, 'user-data');
|
|
424
590
|
const metaDataPath = path.join(seedDir, 'meta-data');
|
|
425
591
|
const startupNshPath = path.join(seedDir, 'startup.nsh');
|
|
426
|
-
const
|
|
427
|
-
|
|
592
|
+
const guestPayloadBase64 = runtimeMode === 'user'
|
|
593
|
+
? ''
|
|
594
|
+
: fs.readFileSync(createGuestPayloadArchive(seedDir, runtimeProfile)).toString('base64');
|
|
428
595
|
const userData = createCloudInitUserData({
|
|
429
596
|
guestToken,
|
|
430
597
|
guestPayloadBase64,
|
|
431
598
|
guestAgentPort,
|
|
599
|
+
runtimeMode,
|
|
600
|
+
runtimeProfile,
|
|
432
601
|
});
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
localHostName: `neoagent-${path.basename(userRoot)}`,
|
|
436
|
-
});
|
|
602
|
+
const identity = resolveCloudInitIdentity(userRoot);
|
|
603
|
+
const metaData = createCloudInitMetaData(identity);
|
|
437
604
|
const startupNsh = guestArch === 'arm64'
|
|
438
605
|
? [
|
|
439
606
|
'@echo -off',
|
|
@@ -480,6 +647,7 @@ function ensureGuestBootstrapSeed({
|
|
|
480
647
|
module.exports = {
|
|
481
648
|
createCloudInitMetaData,
|
|
482
649
|
createCloudInitUserData,
|
|
650
|
+
createCloudInitScript,
|
|
483
651
|
createSeedIso,
|
|
484
652
|
ensureGuestBootstrapSeed,
|
|
485
653
|
GUEST_BOOTSTRAP_ROOT,
|
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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,
|
|
47
|
+
const backend = this.resolveBackend(userId, 'browser_cli');
|
|
40
48
|
return backend.executeCommand(userId, command, options);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
hasVmForUser(userId) {
|
|
44
|
-
|
|
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.
|
|
59
|
+
return this.browserBackend.killCommand(userId, pid, reason);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
async getCommandExecutorForUser(userId) {
|
|
52
|
-
return this.
|
|
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.
|
|
71
|
+
return this.browserBackend.getBrowserProviderForUser(userId);
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
async getAndroidProviderForUser(userId) {
|
|
64
|
-
|
|
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 (
|
|
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.
|
|
105
|
+
return this.browserBackend.isGuestAgentReadyForUser(userId, timeoutMs);
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
async shutdown() {
|
|
75
|
-
await Promise.allSettled([
|
|
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
|
|