neoagent 2.4.0 → 2.4.1-beta.11
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/background.mjs +19 -7
- 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 +13 -2
- package/extensions/chrome-browser/popup.css +5 -0
- package/extensions/chrome-browser/popup.html +7 -5
- package/extensions/chrome-browser/popup.js +16 -7
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +721 -0
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +6 -0
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +1 -1
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_controller.dart +156 -3
- package/flutter_app/lib/main_devices.dart +485 -119
- package/flutter_app/lib/main_settings.dart +289 -30
- package/flutter_app/lib/src/backend_client.dart +89 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +153 -3
- package/flutter_app/lib/src/desktop_companion_io.dart +145 -4
- package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
- package/flutter_app/lib/src/stream_renderer.dart +286 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +56 -1
- package/package.json +2 -2
- package/server/guest_agent.js +19 -1
- package/server/http/routes.js +191 -0
- package/server/http/socket.js +1 -1
- package/server/index.js +4 -1
- 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 +75438 -74005
- package/server/routes/browser.js +14 -0
- package/server/routes/browser_extension.js +21 -4
- package/server/routes/desktop.js +10 -0
- package/server/routes/settings.js +4 -0
- package/server/routes/stream.js +187 -0
- package/server/services/ai/tools.js +40 -29
- package/server/services/android/controller.js +41 -2
- package/server/services/browser/controller.js +34 -0
- package/server/services/browser/extension/manifest.js +33 -0
- package/server/services/browser/extension/provider.js +12 -6
- package/server/services/browser/extension/registry.js +188 -18
- package/server/services/desktop/gateway.js +28 -3
- package/server/services/desktop/protocol.js +34 -0
- package/server/services/desktop/provider.js +25 -0
- package/server/services/desktop/registry.js +92 -10
- package/server/services/manager.js +19 -2
- package/server/services/runtime/backends/local-vm.js +6 -0
- package/server/services/runtime/docker-vm-manager.js +26 -3
- package/server/services/runtime/manager.js +36 -5
- package/server/services/runtime/settings.js +17 -0
- package/server/services/streaming/android-stream.js +298 -0
- package/server/services/streaming/browser-stream.js +87 -0
- package/server/services/streaming/stream-hub.js +231 -0
- package/server/services/websocket.js +73 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createBrowserProtocol } from './protocol.mjs';
|
|
2
2
|
import { DEFAULT_SERVER_URL } from './config.mjs';
|
|
3
3
|
|
|
4
|
-
const STORAGE_KEYS = ['serverUrl', 'configuredServerUrl', 'token', 'pairingId', 'pairingSecret', 'approvalUrl', 'status'];
|
|
4
|
+
const STORAGE_KEYS = ['serverUrl', 'configuredServerUrl', 'token', 'pairingId', 'pairingSecret', 'approvalUrl', 'status', 'extensionName'];
|
|
5
5
|
const protocol = createBrowserProtocol(chrome);
|
|
6
6
|
let socket = null;
|
|
7
7
|
let reconnectTimer = null;
|
|
@@ -188,10 +188,12 @@ async function handleSocketMessage(raw) {
|
|
|
188
188
|
async function startPairing(serverUrl) {
|
|
189
189
|
const normalized = await resolveServerUrl(serverUrl);
|
|
190
190
|
if (!normalized) throw new Error('NeoAgent server URL required.');
|
|
191
|
+
const { extensionName } = await getStorage(['extensionName']);
|
|
192
|
+
const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension';
|
|
191
193
|
const response = await fetchWithTimeout(`${normalized}/api/browser-extension/pairing/request`, {
|
|
192
194
|
method: 'POST',
|
|
193
195
|
headers: { 'content-type': 'application/json' },
|
|
194
|
-
body: JSON.stringify({ extensionName:
|
|
196
|
+
body: JSON.stringify({ extensionName: nameToUse }),
|
|
195
197
|
});
|
|
196
198
|
const payload = await response.json().catch(() => ({}));
|
|
197
199
|
if (!response.ok) throw new Error(payload.error || `Pairing failed: ${response.status}`);
|
|
@@ -212,14 +214,15 @@ async function startPairing(serverUrl) {
|
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
async function claimPairing() {
|
|
215
|
-
const { serverUrl, pairingId, pairingSecret } = await getStorage(['serverUrl', 'pairingId', 'pairingSecret']);
|
|
217
|
+
const { serverUrl, pairingId, pairingSecret, extensionName } = await getStorage(['serverUrl', 'pairingId', 'pairingSecret', 'extensionName']);
|
|
216
218
|
if (!serverUrl || !pairingId || !pairingSecret) {
|
|
217
219
|
throw new Error('No pending pairing request.');
|
|
218
220
|
}
|
|
221
|
+
const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension';
|
|
219
222
|
const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/pairing/${encodeURIComponent(pairingId)}/claim`, {
|
|
220
223
|
method: 'POST',
|
|
221
224
|
headers: { 'content-type': 'application/json' },
|
|
222
|
-
body: JSON.stringify({ pairingSecret, extensionName:
|
|
225
|
+
body: JSON.stringify({ pairingSecret, extensionName: nameToUse }),
|
|
223
226
|
});
|
|
224
227
|
const payload = await response.json().catch(() => ({}));
|
|
225
228
|
if (!response.ok) throw new Error(payload.error || `Claim failed: ${response.status}`);
|
|
@@ -250,12 +253,18 @@ async function checkForUpdates(preferredServerUrl) {
|
|
|
250
253
|
const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/latest`);
|
|
251
254
|
const latest = await response.json().catch(() => ({}));
|
|
252
255
|
if (!response.ok) throw new Error(latest.error || `Update check failed: ${response.status}`);
|
|
253
|
-
const
|
|
256
|
+
const manifest = chrome.runtime.getManifest();
|
|
257
|
+
const currentVersion = manifest.version;
|
|
258
|
+
const currentVersionName = manifest.version_name || currentVersion;
|
|
259
|
+
const latestVersion = latest.version || currentVersion;
|
|
260
|
+
const latestVersionName = latest.versionName || latestVersion;
|
|
254
261
|
return {
|
|
255
262
|
currentVersion,
|
|
256
|
-
|
|
263
|
+
currentVersionName,
|
|
264
|
+
latestVersion,
|
|
265
|
+
latestVersionName,
|
|
257
266
|
downloadUrl: latest.downloadUrl || `${serverUrl}/api/browser-extension/download`,
|
|
258
|
-
updateAvailable: compareVersions(
|
|
267
|
+
updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
|
|
259
268
|
};
|
|
260
269
|
}
|
|
261
270
|
|
|
@@ -279,6 +288,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
279
288
|
return disconnect();
|
|
280
289
|
case 'checkForUpdates':
|
|
281
290
|
return checkForUpdates(message.serverUrl);
|
|
291
|
+
case 'saveExtensionName':
|
|
292
|
+
await setStorage({ extensionName: message.extensionName });
|
|
293
|
+
return { success: true };
|
|
282
294
|
case 'openDownload':
|
|
283
295
|
return openDownload(message.serverUrl);
|
|
284
296
|
case 'getState':
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#8f6d3e"/>
|
|
5
|
+
<stop offset="100%" stop-color="#2f7d6e"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="5.76" y="5.76" width="20.48" height="20.48" rx="6.96" fill="url(#bg)" stroke="#ffffff" stroke-opacity="0.16" stroke-width="1"/>
|
|
9
|
+
<polygon points="16,9.76 9.35,13.12 16,16.48 22.65,13.12" fill="white"/>
|
|
10
|
+
<polyline points="9.35,16.48 16,19.68 22.65,16.48" fill="none" stroke="white" stroke-width="1.44" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
<polyline points="9.35,19.68 16,22.72 22.65,19.68" fill="none" stroke="white" stroke-width="1.44" stroke-linecap="round" stroke-linejoin="round"/>
|
|
12
|
+
</svg>
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "NeoAgent Browser",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "2.4.1.7",
|
|
5
|
+
"version_name": "2.4.1-beta.7",
|
|
5
6
|
"description": "Connect this Chrome browser to NeoAgent for browser automation.",
|
|
6
7
|
"minimum_chrome_version": "118",
|
|
7
8
|
"permissions": ["debugger", "storage", "tabs"],
|
|
8
9
|
"host_permissions": ["http://*/*", "https://*/*"],
|
|
10
|
+
"icons": {
|
|
11
|
+
"16": "icons/icon16.png",
|
|
12
|
+
"48": "icons/icon48.png",
|
|
13
|
+
"128": "icons/icon128.png"
|
|
14
|
+
},
|
|
9
15
|
"action": {
|
|
10
16
|
"default_title": "NeoAgent Browser",
|
|
11
|
-
"default_popup": "popup.html"
|
|
17
|
+
"default_popup": "popup.html",
|
|
18
|
+
"default_icon": {
|
|
19
|
+
"16": "icons/icon16.png",
|
|
20
|
+
"48": "icons/icon48.png",
|
|
21
|
+
"128": "icons/icon128.png"
|
|
22
|
+
}
|
|
12
23
|
},
|
|
13
24
|
"background": {
|
|
14
25
|
"service_worker": "background.mjs",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<body>
|
|
9
9
|
<main>
|
|
10
10
|
<header>
|
|
11
|
+
<img src="icons/logo.svg" alt="NeoAgent" class="logo">
|
|
11
12
|
<p class="eyebrow">NeoAgent Browser</p>
|
|
12
13
|
<h1>Connect this Chrome</h1>
|
|
13
14
|
<p class="intro">Pair once, then let NeoAgent control this browser when you ask it to.</p>
|
|
@@ -31,15 +32,17 @@
|
|
|
31
32
|
</div>
|
|
32
33
|
</section>
|
|
33
34
|
|
|
34
|
-
<button id="openApp" type="button" class="link-button">Open NeoAgent web page</button>
|
|
35
|
-
|
|
36
35
|
<details id="settings" class="settings">
|
|
37
36
|
<summary>Settings & updates</summary>
|
|
38
37
|
<label>
|
|
39
38
|
Server URL
|
|
40
39
|
<input id="serverUrl" type="url" placeholder="https://neoagent.example.com">
|
|
41
40
|
</label>
|
|
42
|
-
<
|
|
41
|
+
<label style="margin-top: 8px;">
|
|
42
|
+
Extension Name
|
|
43
|
+
<input id="extensionName" type="text" placeholder="e.g. Work Laptop, Personal Mac" value="Chrome Extension">
|
|
44
|
+
</label>
|
|
45
|
+
<div class="settings-actions" style="margin-top: 12px;">
|
|
43
46
|
<button id="checkUpdate" type="button" class="secondary">Check for update</button>
|
|
44
47
|
<button id="download" type="button" class="secondary">Download latest ZIP</button>
|
|
45
48
|
<button id="disconnect" type="button" class="secondary danger">Disconnect</button>
|
|
@@ -47,8 +50,7 @@
|
|
|
47
50
|
<p class="hint">The server URL is usually filled in by the ZIP downloaded from NeoAgent.</p>
|
|
48
51
|
</details>
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
<p id="message" class="message"></p>
|
|
53
|
+
<p id="message" class="message"></p>
|
|
52
54
|
</main>
|
|
53
55
|
<script src="popup.js" type="module"></script>
|
|
54
56
|
</body>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const statusEl = document.querySelector('#status');
|
|
2
2
|
const statusDotEl = document.querySelector('#statusDot');
|
|
3
3
|
const serverUrlEl = document.querySelector('#serverUrl');
|
|
4
|
+
const extensionNameEl = document.querySelector('#extensionName');
|
|
4
5
|
const serverLabelEl = document.querySelector('#serverLabel');
|
|
5
6
|
const messageEl = document.querySelector('#message');
|
|
6
7
|
const settingsEl = document.querySelector('#settings');
|
|
@@ -9,7 +10,6 @@ const flowTitleEl = document.querySelector('#flowTitle');
|
|
|
9
10
|
const flowDescriptionEl = document.querySelector('#flowDescription');
|
|
10
11
|
const primaryActionEl = document.querySelector('#primaryAction');
|
|
11
12
|
const secondaryActionEl = document.querySelector('#secondaryAction');
|
|
12
|
-
const openAppEl = document.querySelector('#openApp');
|
|
13
13
|
const disconnectEl = document.querySelector('#disconnect');
|
|
14
14
|
const checkUpdateEl = document.querySelector('#checkUpdate');
|
|
15
15
|
const downloadEl = document.querySelector('#download');
|
|
@@ -59,7 +59,7 @@ function setBusy(isBusy, label = 'Working...') {
|
|
|
59
59
|
}
|
|
60
60
|
const busy = pendingActions > 0;
|
|
61
61
|
|
|
62
|
-
[primaryActionEl, secondaryActionEl,
|
|
62
|
+
[primaryActionEl, secondaryActionEl, disconnectEl, checkUpdateEl, downloadEl].forEach((button) => {
|
|
63
63
|
if (!button || button.hidden) return;
|
|
64
64
|
if (busy) {
|
|
65
65
|
if (!Object.prototype.hasOwnProperty.call(button.dataset, 'wasDisabled')) {
|
|
@@ -99,8 +99,6 @@ function updateFlow() {
|
|
|
99
99
|
const hasToken = Boolean(currentState.token || currentState.tokenId);
|
|
100
100
|
const approvalUrl = currentState.approvalUrl || '';
|
|
101
101
|
|
|
102
|
-
openAppEl.disabled = !hasServerUrl;
|
|
103
|
-
|
|
104
102
|
if (!hasServerUrl) {
|
|
105
103
|
stepLabelEl.textContent = 'Step 1 of 3';
|
|
106
104
|
flowTitleEl.textContent = 'Add your NeoAgent server';
|
|
@@ -217,6 +215,10 @@ async function refresh() {
|
|
|
217
215
|
if (serverUrl && document.activeElement !== serverUrlEl) {
|
|
218
216
|
serverUrlEl.value = serverUrl;
|
|
219
217
|
}
|
|
218
|
+
const extensionName = currentState.extensionName || 'Chrome Extension';
|
|
219
|
+
if (document.activeElement !== extensionNameEl) {
|
|
220
|
+
extensionNameEl.value = extensionName;
|
|
221
|
+
}
|
|
220
222
|
if (!serverUrl) {
|
|
221
223
|
settingsEl.open = true;
|
|
222
224
|
}
|
|
@@ -240,10 +242,17 @@ function bindAsyncClick(element, handler) {
|
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
serverUrlEl.addEventListener('input', updateFlow);
|
|
245
|
+
extensionNameEl.addEventListener('input', async () => {
|
|
246
|
+
const name = String(extensionNameEl.value || '').trim();
|
|
247
|
+
try {
|
|
248
|
+
await send('saveExtensionName', { extensionName: name });
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error('Failed to save extension name', err);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
243
253
|
|
|
244
254
|
bindAsyncClick(primaryActionEl, () => runAction(primaryActionEl.dataset.action));
|
|
245
255
|
bindAsyncClick(secondaryActionEl, () => runAction(secondaryActionEl.dataset.action));
|
|
246
|
-
bindAsyncClick(openAppEl, () => runAction('openApp'));
|
|
247
256
|
bindAsyncClick(disconnectEl, async () => {
|
|
248
257
|
await send('disconnect');
|
|
249
258
|
await refresh();
|
|
@@ -253,8 +262,8 @@ bindAsyncClick(checkUpdateEl, async () => {
|
|
|
253
262
|
const result = await send('checkForUpdates', { serverUrl: effectiveServerUrl() });
|
|
254
263
|
setMessage(
|
|
255
264
|
result.updateAvailable
|
|
256
|
-
? `Update available: ${result.currentVersion} -> ${result.latestVersion}.`
|
|
257
|
-
: `Current version ${result.currentVersion} is up to date.`,
|
|
265
|
+
? `Update available: ${result.currentVersionName || result.currentVersion} -> ${result.latestVersionName || result.latestVersion}.`
|
|
266
|
+
: `Current version ${result.currentVersionName || result.currentVersion} is up to date.`,
|
|
258
267
|
result.updateAvailable ? '' : 'success',
|
|
259
268
|
);
|
|
260
269
|
});
|