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.
- package/extensions/chrome-browser/protocol.mjs +143 -1
- package/flutter_app/lib/main_controller.dart +82 -0
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/landing/index.html +3 -1
- package/lib/schema_migrations.js +48 -0
- package/package.json +10 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +33 -10
- 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 +69495 -68619
- package/server/routes/integrations.js +102 -0
- package/server/services/ai/systemPrompt.js +2 -2
- package/server/services/ai/tools.js +77 -0
- package/server/services/browser/controller.js +107 -0
- package/server/services/browser/extension/protocol.js +3 -0
- package/server/services/browser/extension/provider.js +12 -0
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/manager.js +1 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/manager.js +23 -0
- package/server/services/runtime/backends/local-vm.js +13 -1
- package/server/services/runtime/guest_bootstrap.js +23 -4
- package/server/services/runtime/guest_image.js +4 -3
- package/server/services/runtime/manager.js +25 -11
- package/server/services/runtime/validation.js +7 -6
- 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 {
|
|
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,
|