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.
Files changed (57) hide show
  1. package/LICENSE +619 -21
  2. package/README.md +1 -1
  3. package/extensions/chrome-browser/background.mjs +19 -7
  4. package/extensions/chrome-browser/icons/icon128.png +0 -0
  5. package/extensions/chrome-browser/icons/icon16.png +0 -0
  6. package/extensions/chrome-browser/icons/icon48.png +0 -0
  7. package/extensions/chrome-browser/icons/logo.svg +12 -0
  8. package/extensions/chrome-browser/manifest.json +13 -2
  9. package/extensions/chrome-browser/popup.css +5 -0
  10. package/extensions/chrome-browser/popup.html +7 -5
  11. package/extensions/chrome-browser/popup.js +16 -7
  12. package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +721 -0
  13. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +6 -0
  14. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +1 -1
  15. package/flutter_app/lib/main.dart +1 -0
  16. package/flutter_app/lib/main_controller.dart +156 -3
  17. package/flutter_app/lib/main_devices.dart +485 -119
  18. package/flutter_app/lib/main_settings.dart +289 -30
  19. package/flutter_app/lib/src/backend_client.dart +89 -0
  20. package/flutter_app/lib/src/desktop_companion_actions.dart +153 -3
  21. package/flutter_app/lib/src/desktop_companion_io.dart +145 -4
  22. package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
  23. package/flutter_app/lib/src/stream_renderer.dart +286 -0
  24. package/flutter_app/macos/Runner/AppDelegate.swift +56 -1
  25. package/package.json +2 -2
  26. package/server/guest_agent.js +19 -1
  27. package/server/http/routes.js +191 -0
  28. package/server/http/socket.js +1 -1
  29. package/server/index.js +4 -1
  30. package/server/public/.last_build_id +1 -1
  31. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  32. package/server/public/flutter_bootstrap.js +1 -1
  33. package/server/public/main.dart.js +75438 -74005
  34. package/server/routes/browser.js +14 -0
  35. package/server/routes/browser_extension.js +21 -4
  36. package/server/routes/desktop.js +10 -0
  37. package/server/routes/settings.js +4 -0
  38. package/server/routes/stream.js +187 -0
  39. package/server/services/ai/tools.js +40 -29
  40. package/server/services/android/controller.js +41 -2
  41. package/server/services/browser/controller.js +34 -0
  42. package/server/services/browser/extension/manifest.js +33 -0
  43. package/server/services/browser/extension/provider.js +12 -6
  44. package/server/services/browser/extension/registry.js +188 -18
  45. package/server/services/desktop/gateway.js +28 -3
  46. package/server/services/desktop/protocol.js +34 -0
  47. package/server/services/desktop/provider.js +25 -0
  48. package/server/services/desktop/registry.js +92 -10
  49. package/server/services/manager.js +19 -2
  50. package/server/services/runtime/backends/local-vm.js +6 -0
  51. package/server/services/runtime/docker-vm-manager.js +26 -3
  52. package/server/services/runtime/manager.js +36 -5
  53. package/server/services/runtime/settings.js +17 -0
  54. package/server/services/streaming/android-stream.js +298 -0
  55. package/server/services/streaming/browser-stream.js +87 -0
  56. package/server/services/streaming/stream-hub.js +231 -0
  57. 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: 'NeoAgent Browser' }),
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: 'NeoAgent Browser' }),
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 currentVersion = chrome.runtime.getManifest().version;
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
- latestVersion: latest.version || currentVersion,
263
+ currentVersionName,
264
+ latestVersion,
265
+ latestVersionName,
257
266
  downloadUrl: latest.downloadUrl || `${serverUrl}/api/browser-extension/download`,
258
- updateAvailable: compareVersions(latest.version, currentVersion) > 0,
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':
@@ -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.0.0",
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",
@@ -46,6 +46,11 @@ header {
46
46
  gap: 6px;
47
47
  }
48
48
 
49
+ .logo {
50
+ width: 40px;
51
+ height: 40px;
52
+ }
53
+
49
54
  h1,
50
55
  h2,
51
56
  p {
@@ -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 &amp; 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
- <div class="settings-actions">
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
- <p class="hint">Pairing opens NeoAgent in a tab. This extension never stores your NeoAgent password.</p>
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, openAppEl, disconnectEl, checkUpdateEl, downloadEl].forEach((button) => {
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
  });