neoagent 3.2.1-beta.4 → 3.2.1-beta.6

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 (34) hide show
  1. package/extensions/chrome-browser/protocol.mjs +143 -1
  2. package/flutter_app/lib/main_controller.dart +82 -0
  3. package/flutter_app/lib/main_integrations.dart +607 -8
  4. package/flutter_app/lib/main_security.dart +266 -112
  5. package/flutter_app/lib/src/backend_client.dart +78 -0
  6. package/landing/index.html +3 -1
  7. package/lib/schema_migrations.js +48 -0
  8. package/package.json +10 -2
  9. package/server/guest-agent.cli.package.json +13 -0
  10. package/server/guest_agent.js +33 -10
  11. package/server/public/.last_build_id +1 -1
  12. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  13. package/server/public/flutter_bootstrap.js +1 -1
  14. package/server/public/main.dart.js +69495 -68619
  15. package/server/routes/integrations.js +102 -0
  16. package/server/services/ai/systemPrompt.js +2 -2
  17. package/server/services/ai/tools.js +77 -0
  18. package/server/services/browser/controller.js +107 -0
  19. package/server/services/browser/extension/protocol.js +3 -0
  20. package/server/services/browser/extension/provider.js +12 -0
  21. package/server/services/credentials/bitwarden_cli.js +322 -0
  22. package/server/services/credentials/broker.js +594 -0
  23. package/server/services/integrations/bitwarden/constants.js +14 -0
  24. package/server/services/integrations/bitwarden/provider.js +197 -0
  25. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  26. package/server/services/integrations/manager.js +1 -0
  27. package/server/services/integrations/registry.js +2 -0
  28. package/server/services/manager.js +23 -0
  29. package/server/services/runtime/backends/local-vm.js +13 -1
  30. package/server/services/runtime/guest_bootstrap.js +23 -4
  31. package/server/services/runtime/guest_image.js +4 -3
  32. package/server/services/runtime/manager.js +25 -11
  33. package/server/services/runtime/validation.js +7 -6
  34. package/server/services/security/tool_categories.js +6 -0
@@ -22,6 +22,9 @@ export const COMMANDS = Object.freeze({
22
22
  CLOSE: 'close',
23
23
  GET_PAGE_INFO: 'getPageInfo',
24
24
  GET_COOKIES: 'getCookies',
25
+ FILL_CREDENTIAL: 'fillCredential',
26
+ SUBMIT_CREDENTIAL: 'submitCredential',
27
+ CANCEL_CREDENTIAL: 'cancelCredential',
25
28
  CANCEL_COMMAND: 'cancelCommand',
26
29
  });
27
30
 
@@ -217,11 +220,15 @@ const PAGE_ACCESS_COMMANDS = new Set([
217
220
  COMMANDS.EVALUATE,
218
221
  COMMANDS.SCREENSHOT,
219
222
  COMMANDS.GET_PAGE_INFO,
223
+ COMMANDS.FILL_CREDENTIAL,
224
+ COMMANDS.SUBMIT_CREDENTIAL,
225
+ COMMANDS.CANCEL_CREDENTIAL,
220
226
  ]);
221
227
 
222
228
  export function createBrowserProtocol(chromeApi, options = {}) {
223
229
  let attachedTabId = null;
224
230
  let activeTabId = null;
231
+ let protectedCredentialFill = null;
225
232
  const pausedRequests = new Map();
226
233
  const maxPausedRequests = Math.max(1, Math.min(Number(options.maxPausedRequests) || 256, 1024));
227
234
  const validateUrl = typeof options.validateUrl === 'function'
@@ -586,6 +593,27 @@ export function createBrowserProtocol(chromeApi, options = {}) {
586
593
  async function run(command, payload = {}, options = {}) {
587
594
  const signal = options.signal || null;
588
595
  throwIfAborted(signal);
596
+ if (protectedCredentialFill?.expiresAt <= Date.now()) {
597
+ const expired = protectedCredentialFill;
598
+ await evalJs(`(() => {
599
+ for (const selector of [${jsString(expired.usernameSelector)}, ${jsString(expired.passwordSelector)}].filter(Boolean)) {
600
+ const el = document.querySelector(selector);
601
+ if (el && 'value' in el) el.value = '';
602
+ }
603
+ })()`, {}, signal).catch(() => {});
604
+ protectedCredentialFill = null;
605
+ }
606
+ if (
607
+ protectedCredentialFill
608
+ && ![
609
+ COMMANDS.GET_PAGE_INFO,
610
+ COMMANDS.SUBMIT_CREDENTIAL,
611
+ COMMANDS.CANCEL_CREDENTIAL,
612
+ COMMANDS.CLOSE,
613
+ ].includes(command)
614
+ ) {
615
+ throw new Error('Browser control is paused while a protected credential fill is active. Submit or cancel it first.');
616
+ }
589
617
  if (PAGE_ACCESS_COMMANDS.has(command)) {
590
618
  await assertCurrentPageAllowed(signal);
591
619
  }
@@ -694,6 +722,107 @@ export function createBrowserProtocol(chromeApi, options = {}) {
694
722
  throwIfAborted(signal);
695
723
  if (payload.pressEnter) await typeKey('Enter', signal);
696
724
  return pageSnapshot({ screenshot: payload.screenshot !== false }, signal);
725
+ case COMMANDS.FILL_CREDENTIAL: {
726
+ if (protectedCredentialFill) throw new Error('A protected credential fill is already active.');
727
+ const allowedOrigin = new URL(String(payload.allowedOrigin || '')).origin;
728
+ const tab = await currentTab(signal);
729
+ if (new URL(String(tab.url || '')).origin !== allowedOrigin) {
730
+ throw new Error('The browser origin changed before credential fill.');
731
+ }
732
+ const usernameSelector = String(payload.usernameSelector || '').trim();
733
+ const passwordSelector = String(payload.passwordSelector || '').trim();
734
+ if (!usernameSelector && !passwordSelector) throw new Error('At least one credential field selector is required.');
735
+ await attach(signal);
736
+ const marker = await markCurrentDocument(signal);
737
+ await send('Page.reload', {}, signal);
738
+ await waitForDocumentReplacement(marker, 30000, signal);
739
+ await waitForLoad(30000, signal);
740
+ const reloaded = await currentTab(signal);
741
+ if (new URL(String(reloaded.url || '')).origin !== allowedOrigin) {
742
+ throw new Error('The browser origin changed while preparing credential fill.');
743
+ }
744
+ if (usernameSelector) {
745
+ await waitForSelector(usernameSelector, 10000, signal);
746
+ await evalJs(`(() => {
747
+ const el = document.querySelector(${jsString(usernameSelector)});
748
+ if (!el) throw new Error('Username field not found.');
749
+ el.value = ${jsString(String(payload.username || ''))};
750
+ el.dispatchEvent(new Event('input', { bubbles: true }));
751
+ el.dispatchEvent(new Event('change', { bubbles: true }));
752
+ })()`, {}, signal);
753
+ }
754
+ if (passwordSelector) {
755
+ await waitForSelector(passwordSelector, 10000, signal);
756
+ await evalJs(`(() => {
757
+ const el = document.querySelector(${jsString(passwordSelector)});
758
+ if (!el) throw new Error('Password field not found.');
759
+ el.value = ${jsString(String(payload.password || ''))};
760
+ el.dispatchEvent(new Event('input', { bubbles: true }));
761
+ el.dispatchEvent(new Event('change', { bubbles: true }));
762
+ })()`, {}, signal);
763
+ }
764
+ const protectedFillId = globalThis.crypto.randomUUID();
765
+ protectedCredentialFill = {
766
+ id: protectedFillId,
767
+ allowedOrigin,
768
+ usernameSelector,
769
+ passwordSelector,
770
+ submitSelector: passwordSelector || usernameSelector,
771
+ expiresAt: Date.now() + 5 * 60 * 1000,
772
+ };
773
+ return { success: true, protectedFillId, origin: allowedOrigin };
774
+ }
775
+ case COMMANDS.SUBMIT_CREDENTIAL: {
776
+ const fill = protectedCredentialFill;
777
+ if (!fill || fill.id !== String(payload.protectedFillId || '')) {
778
+ throw new Error('Protected credential fill is missing or expired.');
779
+ }
780
+ const tab = await currentTab(signal);
781
+ if (new URL(String(tab.url || '')).origin !== fill.allowedOrigin) {
782
+ protectedCredentialFill = null;
783
+ throw new Error('The protected credential page changed before submission.');
784
+ }
785
+ try {
786
+ await evalJs(`(() => {
787
+ const el = document.querySelector(${jsString(fill.submitSelector)});
788
+ if (!el) throw new Error('Credential field not found.');
789
+ const form = el.form;
790
+ if (form && typeof form.requestSubmit === 'function') form.requestSubmit();
791
+ else el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
792
+ })()`, {}, signal);
793
+ await delay(500, signal);
794
+ await waitForLoad(10000, signal).catch(() => {});
795
+ const resultTab = await currentTab(signal);
796
+ return {
797
+ success: true,
798
+ url: resultTab.url || null,
799
+ title: resultTab.title || null,
800
+ protected: false,
801
+ };
802
+ } finally {
803
+ await evalJs(`(() => {
804
+ for (const selector of [${jsString(fill.usernameSelector)}, ${jsString(fill.passwordSelector)}].filter(Boolean)) {
805
+ const el = document.querySelector(selector);
806
+ if (el && 'value' in el) el.value = '';
807
+ }
808
+ })()`, {}, signal).catch(() => {});
809
+ protectedCredentialFill = null;
810
+ }
811
+ }
812
+ case COMMANDS.CANCEL_CREDENTIAL: {
813
+ const fill = protectedCredentialFill;
814
+ if (!fill || fill.id !== String(payload.protectedFillId || '')) {
815
+ throw new Error('Protected credential fill is missing or expired.');
816
+ }
817
+ await evalJs(`(() => {
818
+ for (const selector of [${jsString(fill.usernameSelector)}, ${jsString(fill.passwordSelector)}].filter(Boolean)) {
819
+ const el = document.querySelector(selector);
820
+ if (el && 'value' in el) el.value = '';
821
+ }
822
+ })()`, {}, signal).catch(() => {});
823
+ protectedCredentialFill = null;
824
+ return { success: true, protected: false };
825
+ }
697
826
  case COMMANDS.PRESS_KEY:
698
827
  await typeKey(payload.key, signal);
699
828
  throwIfAborted(signal);
@@ -742,9 +871,21 @@ export function createBrowserProtocol(chromeApi, options = {}) {
742
871
  return { screenshotDataUrl: await screenshotDataUrl(payload, signal), fullPage: payload.fullPage === true };
743
872
  case COMMANDS.GET_PAGE_INFO: {
744
873
  const tab = await currentTab(signal);
745
- return { url: tab.url || null, title: tab.title || null };
874
+ return {
875
+ url: tab.url || null,
876
+ title: tab.title || null,
877
+ protectedCredentialFill: Boolean(protectedCredentialFill),
878
+ };
746
879
  }
747
880
  case COMMANDS.CLOSE:
881
+ if (protectedCredentialFill) {
882
+ await evalJs(`(() => {
883
+ for (const selector of [${jsString(protectedCredentialFill.usernameSelector)}, ${jsString(protectedCredentialFill.passwordSelector)}].filter(Boolean)) {
884
+ const el = document.querySelector(selector);
885
+ if (el && 'value' in el) el.value = '';
886
+ }
887
+ })()`, {}, signal).catch(() => {});
888
+ }
748
889
  if (attachedTabId != null) {
749
890
  abortPausedRequests(attachedTabId, new Error('Browser control closed.'));
750
891
  await optionalResult(
@@ -754,6 +895,7 @@ export function createBrowserProtocol(chromeApi, options = {}) {
754
895
  );
755
896
  }
756
897
  attachedTabId = null;
898
+ protectedCredentialFill = null;
757
899
  return { success: true };
758
900
  default:
759
901
  throw new Error(`Unsupported command: ${command}`);
@@ -4890,6 +4890,88 @@ class NeoAgentController extends ChangeNotifier {
4890
4890
  }
4891
4891
  }
4892
4892
 
4893
+ Future<Map<String, dynamic>> unlockBitwarden(
4894
+ String masterPassword, {
4895
+ required int idleTimeoutMinutes,
4896
+ }) async {
4897
+ try {
4898
+ errorMessage = null;
4899
+ return await _backendClient.unlockBitwarden(
4900
+ backendUrl,
4901
+ masterPassword: masterPassword,
4902
+ idleTimeoutMinutes: idleTimeoutMinutes,
4903
+ agentId: _scopedAgentId,
4904
+ );
4905
+ } catch (error) {
4906
+ errorMessage = _friendlyErrorMessage(error);
4907
+ rethrow;
4908
+ } finally {
4909
+ notifyListeners();
4910
+ }
4911
+ }
4912
+
4913
+ Future<void> lockBitwarden() async {
4914
+ try {
4915
+ errorMessage = null;
4916
+ await _backendClient.lockBitwarden(backendUrl, agentId: _scopedAgentId);
4917
+ } catch (error) {
4918
+ errorMessage = _friendlyErrorMessage(error);
4919
+ rethrow;
4920
+ } finally {
4921
+ notifyListeners();
4922
+ }
4923
+ }
4924
+
4925
+ Future<List<Map<String, dynamic>>> fetchBitwardenItems() async {
4926
+ final response = await _backendClient.fetchBitwardenItems(
4927
+ backendUrl,
4928
+ agentId: _scopedAgentId,
4929
+ );
4930
+ return _jsonMapList(
4931
+ response['items'],
4932
+ ).map((row) => Map<String, dynamic>.from(row)).toList();
4933
+ }
4934
+
4935
+ Future<List<Map<String, dynamic>>> fetchCredentialBindings() async {
4936
+ final response = await _backendClient.fetchCredentialBindings(
4937
+ backendUrl,
4938
+ agentId: _scopedAgentId,
4939
+ );
4940
+ return _jsonMapList(
4941
+ response['bindings'],
4942
+ ).map((row) => Map<String, dynamic>.from(row)).toList();
4943
+ }
4944
+
4945
+ Future<void> createCredentialBinding(Map<String, dynamic> binding) async {
4946
+ try {
4947
+ errorMessage = null;
4948
+ await _backendClient.createCredentialBinding(
4949
+ backendUrl,
4950
+ binding: binding,
4951
+ agentId: _scopedAgentId,
4952
+ );
4953
+ await refreshSkills();
4954
+ } catch (error) {
4955
+ errorMessage = _friendlyErrorMessage(error);
4956
+ rethrow;
4957
+ }
4958
+ }
4959
+
4960
+ Future<void> deleteCredentialBinding(String bindingId) async {
4961
+ try {
4962
+ errorMessage = null;
4963
+ await _backendClient.deleteCredentialBinding(
4964
+ backendUrl,
4965
+ bindingId,
4966
+ agentId: _scopedAgentId,
4967
+ );
4968
+ await refreshSkills();
4969
+ } catch (error) {
4970
+ errorMessage = _friendlyErrorMessage(error);
4971
+ rethrow;
4972
+ }
4973
+ }
4974
+
4893
4975
  Future<void> disconnectOfficialIntegration(
4894
4976
  String providerId, {
4895
4977
  required int connectionId,