neoagent 2.4.0 → 2.4.1-beta.5
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/LICENSE +619 -21
- package/README.md +1 -1
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +12 -0
- package/extensions/chrome-browser/manifest.json +11 -1
- package/extensions/chrome-browser/popup.css +5 -0
- package/extensions/chrome-browser/popup.html +2 -4
- package/extensions/chrome-browser/popup.js +1 -5
- package/flutter_app/lib/main_controller.dart +9 -0
- package/flutter_app/lib/main_devices.dart +70 -1
- package/flutter_app/lib/main_settings.dart +172 -31
- package/flutter_app/lib/src/backend_client.dart +12 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +72 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +9 -4
- package/flutter_app/macos/Runner/AppDelegate.swift +12 -1
- package/package.json +2 -2
- package/server/guest_agent.js +12 -1
- package/server/http/routes.js +190 -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 +43860 -43589
- package/server/services/ai/tools.js +39 -28
- package/server/services/desktop/protocol.js +1 -0
- package/server/services/desktop/provider.js +11 -0
- package/server/services/desktop/registry.js +51 -10
- package/server/services/runtime/docker-vm-manager.js +26 -3
- package/server/services/runtime/manager.js +25 -2
package/server/http/routes.js
CHANGED
|
@@ -67,6 +67,196 @@ function registerApiRoutes(app) {
|
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
app.get('/api/system/health-check', requireAuth, async (req, res) => {
|
|
71
|
+
const userId = req.session?.userId;
|
|
72
|
+
const runtimeManager = req.app?.locals?.runtimeManager;
|
|
73
|
+
const desktopRegistry = req.app?.locals?.desktopCompanionRegistry;
|
|
74
|
+
const extensionRegistry = req.app?.locals?.browserExtensionRegistry;
|
|
75
|
+
const results = [];
|
|
76
|
+
|
|
77
|
+
// 1. Backend connectivity — trivially true if we got here.
|
|
78
|
+
results.push({ id: 'backend', label: 'Backend server', passed: true, detail: 'Reachable' });
|
|
79
|
+
|
|
80
|
+
// 2. Cloud VM runtime availability.
|
|
81
|
+
const runtimeValidation = getRuntimeValidation(runtimeManager);
|
|
82
|
+
const runtimeReady = Boolean(runtimeValidation?.ready);
|
|
83
|
+
results.push({
|
|
84
|
+
id: 'vm_runtime',
|
|
85
|
+
label: 'Cloud VM runtime',
|
|
86
|
+
passed: runtimeReady,
|
|
87
|
+
detail: runtimeReady ? 'Available' : String(runtimeValidation?.issues?.[0] || 'Not configured'),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 3. Cloud VM CLI execution — actually run a command.
|
|
91
|
+
if (runtimeManager && typeof runtimeManager.executeCommand === 'function') {
|
|
92
|
+
try {
|
|
93
|
+
const cmdResult = await runtimeManager.executeCommand(userId, 'echo "health_check_ok"', { timeout: 15000 });
|
|
94
|
+
const exitOk = cmdResult?.exitCode === 0;
|
|
95
|
+
const outputOk = String(cmdResult?.stdout || '').includes('health_check_ok');
|
|
96
|
+
results.push({
|
|
97
|
+
id: 'vm_cli',
|
|
98
|
+
label: 'Cloud VM — command execution',
|
|
99
|
+
passed: exitOk && outputOk,
|
|
100
|
+
detail: exitOk && outputOk
|
|
101
|
+
? 'Commands running'
|
|
102
|
+
: `Exit ${cmdResult?.exitCode ?? '?'}: ${String(cmdResult?.stderr || cmdResult?.stdout || '').slice(0, 120)}`,
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
results.push({ id: 'vm_cli', label: 'Cloud VM — command execution', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
results.push({ id: 'vm_cli', label: 'Cloud VM — command execution', passed: false, detail: 'VM runtime unavailable' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 4. Desktop companion (macOS app / remote device) connectivity + permissions.
|
|
112
|
+
if (desktopRegistry) {
|
|
113
|
+
try {
|
|
114
|
+
const desktopStatus = desktopRegistry.getStatus(userId);
|
|
115
|
+
const connected = Boolean(desktopStatus?.connected);
|
|
116
|
+
results.push({
|
|
117
|
+
id: 'desktop_connected',
|
|
118
|
+
label: 'Desktop companion',
|
|
119
|
+
passed: connected,
|
|
120
|
+
detail: connected
|
|
121
|
+
? `${desktopStatus.onlineCount} device${desktopStatus.onlineCount !== 1 ? 's' : ''} connected`
|
|
122
|
+
: 'No device connected — open the desktop app',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (connected && Array.isArray(desktopStatus?.devices)) {
|
|
126
|
+
const onlineDevice = desktopStatus.devices.find((d) => d.online && !d.revokedAt);
|
|
127
|
+
const perms = onlineDevice?.permissions || {};
|
|
128
|
+
const screenOk = Boolean(perms.screenCapture || perms.screen_capture);
|
|
129
|
+
const inputOk = Boolean(perms.accessibility || perms.inputControl || perms.input_control);
|
|
130
|
+
results.push({
|
|
131
|
+
id: 'desktop_screen',
|
|
132
|
+
label: 'Desktop — screen capture',
|
|
133
|
+
passed: screenOk,
|
|
134
|
+
detail: screenOk ? 'Granted' : 'Not granted — open System Settings › Privacy › Screen Recording',
|
|
135
|
+
});
|
|
136
|
+
results.push({
|
|
137
|
+
id: 'desktop_input',
|
|
138
|
+
label: 'Desktop — input control',
|
|
139
|
+
passed: inputOk,
|
|
140
|
+
detail: inputOk ? 'Granted' : 'Not granted — open System Settings › Privacy › Accessibility',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
results.push({ id: 'desktop_connected', label: 'Desktop companion', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 5. Chrome extension connectivity.
|
|
149
|
+
if (extensionRegistry) {
|
|
150
|
+
try {
|
|
151
|
+
const extStatus = extensionRegistry.getStatus(userId);
|
|
152
|
+
const extConnected = Boolean(extStatus?.connected);
|
|
153
|
+
results.push({
|
|
154
|
+
id: 'chrome_extension',
|
|
155
|
+
label: 'Chrome extension',
|
|
156
|
+
passed: extConnected,
|
|
157
|
+
detail: extConnected ? 'Connected' : 'Not connected — install the NeoAgent extension in Chrome',
|
|
158
|
+
});
|
|
159
|
+
} catch (err) {
|
|
160
|
+
results.push({ id: 'chrome_extension', label: 'Chrome extension', passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const allPassed = results.every((r) => r.passed);
|
|
165
|
+
res.json({ passed: allPassed, results });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Targeted runtime self-tests — one check per endpoint so the UI can embed
|
|
169
|
+
// results inline next to the relevant settings control.
|
|
170
|
+
|
|
171
|
+
app.get('/api/system/test/cli', requireAuth, async (req, res) => {
|
|
172
|
+
const userId = req.session?.userId;
|
|
173
|
+
const runtimeManager = req.app?.locals?.runtimeManager;
|
|
174
|
+
if (!runtimeManager || typeof runtimeManager.executeCommand !== 'function') {
|
|
175
|
+
return res.json({ passed: false, backendUsed: 'vm', detail: 'Runtime not configured on this server.' });
|
|
176
|
+
}
|
|
177
|
+
// Note: executeCommand always routes through the VM backend regardless of
|
|
178
|
+
// the cli_backend setting — desktop CLI routing is not yet implemented.
|
|
179
|
+
try {
|
|
180
|
+
const result = await runtimeManager.executeCommand(userId, 'echo "cli_test_ok"', { timeout: 15000 });
|
|
181
|
+
const exitOk = result?.exitCode === 0;
|
|
182
|
+
const outputOk = String(result?.stdout || '').includes('cli_test_ok');
|
|
183
|
+
return res.json({
|
|
184
|
+
passed: exitOk && outputOk,
|
|
185
|
+
backendUsed: 'vm',
|
|
186
|
+
detail: exitOk && outputOk
|
|
187
|
+
? 'Command executed successfully'
|
|
188
|
+
: `Exit ${result?.exitCode ?? '?'}: ${String(result?.stderr || result?.stdout || '').slice(0, 120)}`,
|
|
189
|
+
});
|
|
190
|
+
} catch (err) {
|
|
191
|
+
return res.json({ passed: false, backendUsed: 'vm', detail: String(err?.message || err).slice(0, 120) });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
app.get('/api/system/test/extension', requireAuth, (req, res) => {
|
|
196
|
+
const userId = req.session?.userId;
|
|
197
|
+
const extensionRegistry = req.app?.locals?.browserExtensionRegistry;
|
|
198
|
+
if (!extensionRegistry) {
|
|
199
|
+
return res.json({ passed: false, detail: 'Extension registry not available on this server.' });
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const status = extensionRegistry.getStatus(userId);
|
|
203
|
+
const connected = Boolean(status?.connected);
|
|
204
|
+
return res.json({
|
|
205
|
+
passed: connected,
|
|
206
|
+
detail: connected ? 'Extension is connected and live' : 'Extension is not connected',
|
|
207
|
+
tokenId: status?.activeTokenId || null,
|
|
208
|
+
meta: status?.connectedMeta || null,
|
|
209
|
+
});
|
|
210
|
+
} catch (err) {
|
|
211
|
+
return res.json({ passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
app.get('/api/system/test/desktop', requireAuth, (req, res) => {
|
|
216
|
+
const userId = req.session?.userId;
|
|
217
|
+
const desktopRegistry = req.app?.locals?.desktopCompanionRegistry;
|
|
218
|
+
if (!desktopRegistry) {
|
|
219
|
+
return res.json({ passed: false, detail: 'Desktop registry not available on this server.' });
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const status = desktopRegistry.getStatus(userId);
|
|
223
|
+
const connected = Boolean(status?.connected);
|
|
224
|
+
const devices = Array.isArray(status?.devices)
|
|
225
|
+
? status.devices.filter((d) => d.online && !d.revokedAt)
|
|
226
|
+
: [];
|
|
227
|
+
const selected = status?.selectedDeviceId || null;
|
|
228
|
+
const activeDevice = selected
|
|
229
|
+
? devices.find((d) => d.deviceId === selected)
|
|
230
|
+
: devices.length === 1 ? devices[0] : null;
|
|
231
|
+
const perms = activeDevice?.permissions || {};
|
|
232
|
+
const screenOk = Boolean(perms.screenCapture || perms.screen_capture);
|
|
233
|
+
const inputOk = Boolean(perms.accessibility || perms.inputControl || perms.input_control);
|
|
234
|
+
return res.json({
|
|
235
|
+
passed: connected,
|
|
236
|
+
connected,
|
|
237
|
+
onlineCount: devices.length,
|
|
238
|
+
selectedDeviceId: selected,
|
|
239
|
+
activeDevice: activeDevice ? {
|
|
240
|
+
deviceId: activeDevice.deviceId,
|
|
241
|
+
label: activeDevice.label || activeDevice.hostname || activeDevice.deviceId,
|
|
242
|
+
platform: activeDevice.platform || null,
|
|
243
|
+
paused: activeDevice.paused || false,
|
|
244
|
+
permissions: { screenCapture: screenOk, inputControl: inputOk },
|
|
245
|
+
} : null,
|
|
246
|
+
multipleOnline: devices.length > 1 && !activeDevice,
|
|
247
|
+
detail: !connected
|
|
248
|
+
? 'No device connected'
|
|
249
|
+
: devices.length > 1 && !activeDevice
|
|
250
|
+
? `${devices.length} devices online — select one in Desktop settings`
|
|
251
|
+
: activeDevice?.paused
|
|
252
|
+
? `${activeDevice.label || 'Device'} is paused`
|
|
253
|
+
: `${activeDevice?.label || 'Device'} connected`,
|
|
254
|
+
});
|
|
255
|
+
} catch (err) {
|
|
256
|
+
return res.json({ passed: false, detail: String(err?.message || err).slice(0, 120) });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
70
260
|
app.get('/api/version', requireAuth, (req, res) => {
|
|
71
261
|
res.json(getVersionInfo());
|
|
72
262
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
097601d84771e93deda096bce9ab4570
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"4c525dac5ebe5971c5708ef73558ed8edcf4a3
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "622837629" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|