@quanta-intellect/vessel-browser 0.1.63 → 0.1.67
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/out/main/index.js
CHANGED
|
@@ -14,6 +14,42 @@ const http = require("node:http");
|
|
|
14
14
|
const os = require("node:os");
|
|
15
15
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
16
16
|
const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
17
|
+
function getEnvFlag(name) {
|
|
18
|
+
const globalProcess = typeof globalThis === "object" && "process" in globalThis ? globalThis.process : void 0;
|
|
19
|
+
return globalProcess?.env?.[name];
|
|
20
|
+
}
|
|
21
|
+
function isDebugEnabled() {
|
|
22
|
+
const value = getEnvFlag("VESSEL_DEBUG")?.trim().toLowerCase();
|
|
23
|
+
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
24
|
+
}
|
|
25
|
+
function writeLog(level, scope, args) {
|
|
26
|
+
if (level === "debug" && !isDebugEnabled()) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const prefix = `[Vessel ${scope}]`;
|
|
30
|
+
switch (level) {
|
|
31
|
+
case "debug":
|
|
32
|
+
console.debug(prefix, ...args);
|
|
33
|
+
return;
|
|
34
|
+
case "info":
|
|
35
|
+
console.info(prefix, ...args);
|
|
36
|
+
return;
|
|
37
|
+
case "warn":
|
|
38
|
+
console.warn(prefix, ...args);
|
|
39
|
+
return;
|
|
40
|
+
case "error":
|
|
41
|
+
console.error(prefix, ...args);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function createLogger(scope) {
|
|
46
|
+
return {
|
|
47
|
+
debug: (...args) => writeLog("debug", scope, args),
|
|
48
|
+
info: (...args) => writeLog("info", scope, args),
|
|
49
|
+
warn: (...args) => writeLog("warn", scope, args),
|
|
50
|
+
error: (...args) => writeLog("error", scope, args)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
17
53
|
const defaults = {
|
|
18
54
|
defaultUrl: "https://start.duckduckgo.com",
|
|
19
55
|
theme: "dark",
|
|
@@ -40,6 +76,7 @@ const defaults = {
|
|
|
40
76
|
};
|
|
41
77
|
const SAVE_DEBOUNCE_MS$5 = 150;
|
|
42
78
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
79
|
+
const logger$j = createLogger("Settings");
|
|
43
80
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
44
81
|
let settings = null;
|
|
45
82
|
let settingsIssues = [];
|
|
@@ -185,7 +222,7 @@ function persistNow() {
|
|
|
185
222
|
getSettingsPath(),
|
|
186
223
|
JSON.stringify(buildPersistedSettings(settings), null, 2)
|
|
187
224
|
)
|
|
188
|
-
).catch((err) =>
|
|
225
|
+
).catch((err) => logger$j.error("Failed to save settings:", err));
|
|
189
226
|
}
|
|
190
227
|
function saveSettings() {
|
|
191
228
|
saveDirty = true;
|
|
@@ -289,6 +326,7 @@ function assertPermittedNavigationURL(url) {
|
|
|
289
326
|
}
|
|
290
327
|
const MAX_CUSTOM_HISTORY = 50;
|
|
291
328
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
329
|
+
const logger$i = createLogger("Tab");
|
|
292
330
|
class Tab {
|
|
293
331
|
id;
|
|
294
332
|
view;
|
|
@@ -329,7 +367,7 @@ class Tab {
|
|
|
329
367
|
guardedLoadURL(url, options) {
|
|
330
368
|
const blockReason = this.getNavigationBlockReason(url);
|
|
331
369
|
if (blockReason) {
|
|
332
|
-
|
|
370
|
+
logger$i.warn(blockReason);
|
|
333
371
|
return blockReason;
|
|
334
372
|
}
|
|
335
373
|
void this.view.webContents.loadURL(url, options);
|
|
@@ -390,7 +428,7 @@ class Tab {
|
|
|
390
428
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
391
429
|
const error = this.getNavigationBlockReason(url);
|
|
392
430
|
if (error) {
|
|
393
|
-
|
|
431
|
+
logger$i.warn(error);
|
|
394
432
|
return { action: "deny" };
|
|
395
433
|
}
|
|
396
434
|
this.onOpenUrl?.({
|
|
@@ -404,7 +442,7 @@ class Tab {
|
|
|
404
442
|
const error = this.getNavigationBlockReason(url);
|
|
405
443
|
if (!error) return;
|
|
406
444
|
event.preventDefault();
|
|
407
|
-
|
|
445
|
+
logger$i.warn(`${context}: ${error}`);
|
|
408
446
|
};
|
|
409
447
|
wc.on("will-navigate", (event, url) => {
|
|
410
448
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
@@ -468,8 +506,7 @@ class Tab {
|
|
|
468
506
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
469
507
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
470
508
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
471
|
-
`).catch(() =>
|
|
472
|
-
});
|
|
509
|
+
`).catch((err) => logger$i.warn("Failed to inject scrollbar CSS:", err));
|
|
473
510
|
});
|
|
474
511
|
wc.on("page-favicon-updated", (_, favicons) => {
|
|
475
512
|
this._state.favicon = favicons[0] || "";
|
|
@@ -491,7 +528,8 @@ class Tab {
|
|
|
491
528
|
})()`
|
|
492
529
|
).then((highlightedText) => {
|
|
493
530
|
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
494
|
-
}).catch(() => {
|
|
531
|
+
}).catch((err) => {
|
|
532
|
+
logger$i.warn("Failed to inspect highlighted text for context menu:", err);
|
|
495
533
|
this.buildContextMenu(wc, params, "");
|
|
496
534
|
});
|
|
497
535
|
});
|
|
@@ -722,8 +760,7 @@ class Tab {
|
|
|
722
760
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
723
761
|
}
|
|
724
762
|
})()
|
|
725
|
-
`).catch(() =>
|
|
726
|
-
});
|
|
763
|
+
`).catch((err) => logger$i.warn("Failed to inject highlight listener:", err));
|
|
727
764
|
} else {
|
|
728
765
|
void wc.executeJavaScript(`
|
|
729
766
|
(function() {
|
|
@@ -734,8 +771,7 @@ class Tab {
|
|
|
734
771
|
delete window.__vesselHighlightHandler;
|
|
735
772
|
}
|
|
736
773
|
})()
|
|
737
|
-
`).catch(() =>
|
|
738
|
-
});
|
|
774
|
+
`).catch((err) => logger$i.warn("Failed to remove highlight listener:", err));
|
|
739
775
|
}
|
|
740
776
|
}
|
|
741
777
|
get webContentsId() {
|
|
@@ -746,6 +782,7 @@ class Tab {
|
|
|
746
782
|
this.view.webContents.close();
|
|
747
783
|
}
|
|
748
784
|
}
|
|
785
|
+
const logger$h = createLogger("JsonPersistence");
|
|
749
786
|
function canUseSafeStorage() {
|
|
750
787
|
try {
|
|
751
788
|
return electron.safeStorage.isEncryptionAvailable();
|
|
@@ -810,9 +847,7 @@ function createDebouncedJsonPersistence({
|
|
|
810
847
|
data,
|
|
811
848
|
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
812
849
|
)
|
|
813
|
-
).catch(
|
|
814
|
-
(err) => console.error(`[Vessel] Failed to save ${logLabel}:`, err)
|
|
815
|
-
);
|
|
850
|
+
).catch((err) => logger$h.error(`Failed to save ${logLabel}:`, err));
|
|
816
851
|
};
|
|
817
852
|
const schedule = () => {
|
|
818
853
|
saveDirty2 = true;
|
|
@@ -2306,6 +2341,7 @@ function destroyAllSessions() {
|
|
|
2306
2341
|
}
|
|
2307
2342
|
sessions.clear();
|
|
2308
2343
|
}
|
|
2344
|
+
const logger$g = createLogger("TabManager");
|
|
2309
2345
|
class TabManager {
|
|
2310
2346
|
tabs = /* @__PURE__ */ new Map();
|
|
2311
2347
|
order = [];
|
|
@@ -2498,8 +2534,9 @@ class TabManager {
|
|
|
2498
2534
|
color: h.color
|
|
2499
2535
|
}));
|
|
2500
2536
|
if (entries.length > 0) {
|
|
2501
|
-
void highlightBatchOnPage(wc, entries).catch(
|
|
2502
|
-
|
|
2537
|
+
void highlightBatchOnPage(wc, entries).catch(
|
|
2538
|
+
(err) => logger$g.warn("Failed to batch highlight:", err)
|
|
2539
|
+
);
|
|
2503
2540
|
}
|
|
2504
2541
|
}
|
|
2505
2542
|
onHighlightCapture(callback) {
|
|
@@ -2519,11 +2556,13 @@ class TabManager {
|
|
|
2519
2556
|
try {
|
|
2520
2557
|
const result = await captureSelectionHighlight(wc);
|
|
2521
2558
|
if (result.success && result.text) {
|
|
2522
|
-
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
2523
|
-
|
|
2559
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
2560
|
+
(err) => logger$g.warn("Failed to capture highlight:", err)
|
|
2561
|
+
);
|
|
2524
2562
|
}
|
|
2525
2563
|
this.highlightCaptureCallback?.(result);
|
|
2526
|
-
} catch {
|
|
2564
|
+
} catch (err) {
|
|
2565
|
+
logger$g.warn("Failed to capture highlight from page:", err);
|
|
2527
2566
|
this.highlightCaptureCallback?.({
|
|
2528
2567
|
success: false,
|
|
2529
2568
|
message: "Could not capture selection"
|
|
@@ -2547,7 +2586,8 @@ class TabManager {
|
|
|
2547
2586
|
if (tabUrl === normalized) {
|
|
2548
2587
|
void this.removeHighlightMarksForText(wc, text);
|
|
2549
2588
|
}
|
|
2550
|
-
} catch {
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
logger$g.warn("Failed to remove highlight from matching tab:", err);
|
|
2551
2591
|
}
|
|
2552
2592
|
}
|
|
2553
2593
|
this.highlightCaptureCallback?.({
|
|
@@ -2577,11 +2617,13 @@ class TabManager {
|
|
|
2577
2617
|
void 0,
|
|
2578
2618
|
void 0,
|
|
2579
2619
|
color
|
|
2580
|
-
).catch(
|
|
2581
|
-
|
|
2620
|
+
).catch(
|
|
2621
|
+
(err) => logger$g.warn("Failed to update highlight color:", err)
|
|
2622
|
+
);
|
|
2582
2623
|
});
|
|
2583
2624
|
}
|
|
2584
|
-
} catch {
|
|
2625
|
+
} catch (err) {
|
|
2626
|
+
logger$g.warn("Failed to iterate highlights for color change:", err);
|
|
2585
2627
|
}
|
|
2586
2628
|
}
|
|
2587
2629
|
this.highlightCaptureCallback?.({
|
|
@@ -2602,8 +2644,9 @@ class TabManager {
|
|
|
2602
2644
|
}
|
|
2603
2645
|
});
|
|
2604
2646
|
})()`
|
|
2605
|
-
).catch(
|
|
2606
|
-
|
|
2647
|
+
).catch(
|
|
2648
|
+
(err) => logger$g.warn("Failed to remove highlight marks:", err)
|
|
2649
|
+
);
|
|
2607
2650
|
}
|
|
2608
2651
|
broadcastState() {
|
|
2609
2652
|
const states = this.getAllStates();
|
|
@@ -3685,6 +3728,23 @@ function addIfPresent(target, key, value) {
|
|
|
3685
3728
|
target[key] = value;
|
|
3686
3729
|
}
|
|
3687
3730
|
}
|
|
3731
|
+
function okResult(value) {
|
|
3732
|
+
return {
|
|
3733
|
+
ok: true,
|
|
3734
|
+
...value ?? {}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
function errorResult(error, value) {
|
|
3738
|
+
return {
|
|
3739
|
+
ok: false,
|
|
3740
|
+
error,
|
|
3741
|
+
...value ?? {}
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function getErrorMessage(error, fallback = "Unknown error") {
|
|
3745
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
3746
|
+
}
|
|
3747
|
+
const logger$f = createLogger("Premium");
|
|
3688
3748
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
3689
3749
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
3690
3750
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -3752,22 +3812,18 @@ async function getCheckoutUrl(email) {
|
|
|
3752
3812
|
});
|
|
3753
3813
|
if (!res.ok) {
|
|
3754
3814
|
const body = await res.text();
|
|
3755
|
-
return
|
|
3815
|
+
return errorResult(body || `HTTP ${res.status}`);
|
|
3756
3816
|
}
|
|
3757
3817
|
const { url } = await res.json();
|
|
3758
|
-
return {
|
|
3818
|
+
return okResult({ url });
|
|
3759
3819
|
} catch (err) {
|
|
3760
|
-
return
|
|
3761
|
-
ok: false,
|
|
3762
|
-
error: err instanceof Error ? err.message : "Failed to create checkout"
|
|
3763
|
-
};
|
|
3820
|
+
return errorResult(getErrorMessage(err, "Failed to create checkout"));
|
|
3764
3821
|
}
|
|
3765
3822
|
}
|
|
3766
3823
|
async function getPortalUrl() {
|
|
3767
|
-
return
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
};
|
|
3824
|
+
return errorResult(
|
|
3825
|
+
"Billing portal access is temporarily disabled until authenticated customer access is implemented."
|
|
3826
|
+
);
|
|
3771
3827
|
}
|
|
3772
3828
|
async function verifySubscription(identifier) {
|
|
3773
3829
|
const current = loadSettings().premium;
|
|
@@ -3782,7 +3838,7 @@ async function verifySubscription(identifier) {
|
|
|
3782
3838
|
body: JSON.stringify({ identifier: verificationIdentifier })
|
|
3783
3839
|
});
|
|
3784
3840
|
if (!res.ok) {
|
|
3785
|
-
|
|
3841
|
+
logger$f.warn("Verification API returned a non-OK status:", res.status);
|
|
3786
3842
|
return current;
|
|
3787
3843
|
}
|
|
3788
3844
|
const data = await res.json();
|
|
@@ -3797,14 +3853,14 @@ async function verifySubscription(identifier) {
|
|
|
3797
3853
|
setSetting("premium", updated);
|
|
3798
3854
|
return updated;
|
|
3799
3855
|
} catch (err) {
|
|
3800
|
-
|
|
3856
|
+
logger$f.warn("Verification failed:", err);
|
|
3801
3857
|
return current;
|
|
3802
3858
|
}
|
|
3803
3859
|
}
|
|
3804
3860
|
async function requestActivationCode(email) {
|
|
3805
3861
|
const normalizedEmail = email.trim().toLowerCase();
|
|
3806
3862
|
if (!normalizedEmail) {
|
|
3807
|
-
return
|
|
3863
|
+
return errorResult("Email is required");
|
|
3808
3864
|
}
|
|
3809
3865
|
try {
|
|
3810
3866
|
const res = await fetch(`${VERIFICATION_API}/activate/start`, {
|
|
@@ -3814,38 +3870,29 @@ async function requestActivationCode(email) {
|
|
|
3814
3870
|
});
|
|
3815
3871
|
const data = await res.json().catch(() => ({}));
|
|
3816
3872
|
if (!res.ok || !data.challengeToken) {
|
|
3817
|
-
return {
|
|
3818
|
-
ok: false,
|
|
3819
|
-
error: data.error || `HTTP ${res.status}`
|
|
3820
|
-
};
|
|
3873
|
+
return errorResult(data.error || `HTTP ${res.status}`);
|
|
3821
3874
|
}
|
|
3822
|
-
return {
|
|
3823
|
-
ok: true,
|
|
3875
|
+
return okResult({
|
|
3824
3876
|
email: normalizedEmail,
|
|
3825
3877
|
challengeToken: data.challengeToken
|
|
3826
|
-
};
|
|
3878
|
+
});
|
|
3827
3879
|
} catch (err) {
|
|
3828
|
-
return
|
|
3829
|
-
ok: false,
|
|
3830
|
-
error: err instanceof Error ? err.message : "Failed to send code"
|
|
3831
|
-
};
|
|
3880
|
+
return errorResult(getErrorMessage(err, "Failed to send code"));
|
|
3832
3881
|
}
|
|
3833
3882
|
}
|
|
3834
3883
|
async function verifyActivationCode(email, code, challengeToken) {
|
|
3835
3884
|
const normalizedEmail = email.trim().toLowerCase();
|
|
3836
3885
|
const trimmedCode = code.trim();
|
|
3837
3886
|
if (!normalizedEmail) {
|
|
3838
|
-
return
|
|
3887
|
+
return errorResult("Email is required", { state: getPremiumState() });
|
|
3839
3888
|
}
|
|
3840
3889
|
if (!trimmedCode) {
|
|
3841
|
-
return
|
|
3890
|
+
return errorResult("Code is required", { state: getPremiumState() });
|
|
3842
3891
|
}
|
|
3843
3892
|
if (!challengeToken.trim()) {
|
|
3844
|
-
return {
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
error: "Request a new activation code and try again."
|
|
3848
|
-
};
|
|
3893
|
+
return errorResult("Request a new activation code and try again.", {
|
|
3894
|
+
state: getPremiumState()
|
|
3895
|
+
});
|
|
3849
3896
|
}
|
|
3850
3897
|
try {
|
|
3851
3898
|
const res = await fetch(`${VERIFICATION_API}/activate/verify`, {
|
|
@@ -3859,11 +3906,9 @@ async function verifyActivationCode(email, code, challengeToken) {
|
|
|
3859
3906
|
});
|
|
3860
3907
|
const data = await res.json().catch(() => ({}));
|
|
3861
3908
|
if (!res.ok) {
|
|
3862
|
-
return {
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
error: data.error || `HTTP ${res.status}`
|
|
3866
|
-
};
|
|
3909
|
+
return errorResult(data.error || `HTTP ${res.status}`, {
|
|
3910
|
+
state: getPremiumState()
|
|
3911
|
+
});
|
|
3867
3912
|
}
|
|
3868
3913
|
const updated = {
|
|
3869
3914
|
status: data.status ?? "free",
|
|
@@ -3874,13 +3919,11 @@ async function verifyActivationCode(email, code, challengeToken) {
|
|
|
3874
3919
|
expiresAt: data.expiresAt || ""
|
|
3875
3920
|
};
|
|
3876
3921
|
setSetting("premium", updated);
|
|
3877
|
-
return {
|
|
3922
|
+
return isPremiumActiveState(updated) ? okResult({ state: updated }) : errorResult("Subscription is not active.", { state: updated });
|
|
3878
3923
|
} catch (err) {
|
|
3879
|
-
return {
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
error: err instanceof Error ? err.message : "Failed to verify code"
|
|
3883
|
-
};
|
|
3924
|
+
return errorResult(getErrorMessage(err, "Failed to verify code"), {
|
|
3925
|
+
state: getPremiumState()
|
|
3926
|
+
});
|
|
3884
3927
|
}
|
|
3885
3928
|
}
|
|
3886
3929
|
let revalidationTimer = null;
|
|
@@ -4376,6 +4419,14 @@ function inferPageSchema(page) {
|
|
|
4376
4419
|
confidence
|
|
4377
4420
|
};
|
|
4378
4421
|
}
|
|
4422
|
+
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1 = 1500;
|
|
4423
|
+
const EXTRACT_SCRIPT_TIMEOUT_MS = 3e3;
|
|
4424
|
+
const EXTRACT_TIMEOUT_BASE_MS = 12e3;
|
|
4425
|
+
const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
4426
|
+
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
4427
|
+
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
4428
|
+
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
4429
|
+
const logger$e = createLogger("Extractor");
|
|
4379
4430
|
const EMPTY_PAGE_CONTENT = {
|
|
4380
4431
|
title: "",
|
|
4381
4432
|
content: "",
|
|
@@ -5092,8 +5143,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
5092
5143
|
function delay(ms) {
|
|
5093
5144
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5094
5145
|
}
|
|
5095
|
-
|
|
5096
|
-
async function waitForDomReady(webContents, timeoutMs = 1500) {
|
|
5146
|
+
async function waitForDomReady(webContents, timeoutMs = DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1) {
|
|
5097
5147
|
const deadline = Date.now() + timeoutMs;
|
|
5098
5148
|
while (Date.now() < deadline) {
|
|
5099
5149
|
const readyState = await executeScript(
|
|
@@ -5119,10 +5169,14 @@ async function executeScript(webContents, script) {
|
|
|
5119
5169
|
return await Promise.race([
|
|
5120
5170
|
webContents.executeJavaScript(script),
|
|
5121
5171
|
new Promise((resolve) => {
|
|
5122
|
-
timer = setTimeout(
|
|
5172
|
+
timer = setTimeout(
|
|
5173
|
+
() => resolve(null),
|
|
5174
|
+
EXTRACT_SCRIPT_TIMEOUT_MS
|
|
5175
|
+
);
|
|
5123
5176
|
})
|
|
5124
5177
|
]);
|
|
5125
|
-
} catch {
|
|
5178
|
+
} catch (err) {
|
|
5179
|
+
logger$e.warn("Failed to execute page script:", err);
|
|
5126
5180
|
return null;
|
|
5127
5181
|
} finally {
|
|
5128
5182
|
if (timer) {
|
|
@@ -5215,8 +5269,6 @@ function mergePageContent(candidates, webContents) {
|
|
|
5215
5269
|
url: mergedBase.url || webContents.getURL() || ""
|
|
5216
5270
|
};
|
|
5217
5271
|
}
|
|
5218
|
-
const EXTRACT_TIMEOUT_BASE_MS = 12e3;
|
|
5219
|
-
const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
5220
5272
|
async function estimateExtractionTimeout(webContents) {
|
|
5221
5273
|
try {
|
|
5222
5274
|
const elementCount = await executeScript(
|
|
@@ -5230,7 +5282,8 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5230
5282
|
);
|
|
5231
5283
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
5232
5284
|
}
|
|
5233
|
-
} catch {
|
|
5285
|
+
} catch (err) {
|
|
5286
|
+
logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
5234
5287
|
}
|
|
5235
5288
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
5236
5289
|
}
|
|
@@ -5324,8 +5377,6 @@ function attachDestroyCleanup(wc) {
|
|
|
5324
5377
|
cleanupTimersForWcId(wc.id);
|
|
5325
5378
|
});
|
|
5326
5379
|
}
|
|
5327
|
-
const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5328
|
-
const SETTLE_AFTER_ACTIVITY_MS = 1500;
|
|
5329
5380
|
function getLatestPageDiff(rawUrl) {
|
|
5330
5381
|
if (!shouldTrackSnapshotUrl(rawUrl)) return null;
|
|
5331
5382
|
return latestPageDiffs.get(normalizeUrl(rawUrl)) ?? null;
|
|
@@ -5383,8 +5434,8 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
|
5383
5434
|
function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
5384
5435
|
const lastCaptureAt = lastMutationSnapshotAt.get(wcId) || 0;
|
|
5385
5436
|
const lastActivityAt = lastMutationActivityAt.get(wcId) || 0;
|
|
5386
|
-
const earliestAllowedAt = lastCaptureAt +
|
|
5387
|
-
const stableAfterActivityAt = lastActivityAt ? lastActivityAt +
|
|
5437
|
+
const earliestAllowedAt = lastCaptureAt + MUTATION_CAPTURE_INTERVAL_MS;
|
|
5438
|
+
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
5388
5439
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
5389
5440
|
}
|
|
5390
5441
|
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
@@ -6019,6 +6070,9 @@ function isClickReadLoop(names) {
|
|
|
6019
6070
|
}
|
|
6020
6071
|
return clickReadPairs >= 2;
|
|
6021
6072
|
}
|
|
6073
|
+
function isRecord(value) {
|
|
6074
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6075
|
+
}
|
|
6022
6076
|
class AnthropicProvider {
|
|
6023
6077
|
agentToolProfile = "default";
|
|
6024
6078
|
client;
|
|
@@ -6086,7 +6140,7 @@ class AnthropicProvider {
|
|
|
6086
6140
|
let textContent = "";
|
|
6087
6141
|
const toolUseBlocks = [];
|
|
6088
6142
|
let currentToolUse = null;
|
|
6089
|
-
const STREAM_IDLE_TIMEOUT_MS =
|
|
6143
|
+
const STREAM_IDLE_TIMEOUT_MS = AGENT_STREAM_IDLE_TIMEOUT_MS;
|
|
6090
6144
|
let idleTimer = null;
|
|
6091
6145
|
const resetIdleTimer = () => {
|
|
6092
6146
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -6115,10 +6169,14 @@ class AnthropicProvider {
|
|
|
6115
6169
|
}
|
|
6116
6170
|
} else if (event.type === "content_block_stop" && currentToolUse) {
|
|
6117
6171
|
try {
|
|
6172
|
+
const input = JSON.parse(currentToolUse.inputJson || "{}");
|
|
6173
|
+
if (!isRecord(input)) {
|
|
6174
|
+
throw new Error("Tool input must be a JSON object");
|
|
6175
|
+
}
|
|
6118
6176
|
toolUseBlocks.push({
|
|
6119
6177
|
id: currentToolUse.id,
|
|
6120
6178
|
name: currentToolUse.name,
|
|
6121
|
-
input
|
|
6179
|
+
input
|
|
6122
6180
|
});
|
|
6123
6181
|
} catch {
|
|
6124
6182
|
toolUseBlocks.push({
|
|
@@ -6165,7 +6223,7 @@ class AnthropicProvider {
|
|
|
6165
6223
|
});
|
|
6166
6224
|
continue;
|
|
6167
6225
|
}
|
|
6168
|
-
const argSummary = tb.input.url
|
|
6226
|
+
const argSummary = [tb.input.url, tb.input.text, tb.input.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
6169
6227
|
onChunk(`
|
|
6170
6228
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
6171
6229
|
`);
|
|
@@ -6392,6 +6450,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
6392
6450
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
6393
6451
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
6394
6452
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
6453
|
+
const logger$d = createLogger("OpenAIProvider");
|
|
6395
6454
|
function shouldDebugAgentLoop() {
|
|
6396
6455
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
6397
6456
|
return value === "1" || value === "true";
|
|
@@ -6798,9 +6857,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
6798
6857
|
function logAgentLoopDebug(payload) {
|
|
6799
6858
|
if (!shouldDebugAgentLoop()) return;
|
|
6800
6859
|
try {
|
|
6801
|
-
|
|
6860
|
+
logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
6802
6861
|
} catch (err) {
|
|
6803
|
-
|
|
6862
|
+
logger$d.warn("Failed to serialize debug payload:", err);
|
|
6804
6863
|
}
|
|
6805
6864
|
}
|
|
6806
6865
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -6820,7 +6879,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
|
6820
6879
|
const argsJson = match[2] ?? "{}";
|
|
6821
6880
|
let parsedArgs = {};
|
|
6822
6881
|
try {
|
|
6823
|
-
|
|
6882
|
+
const raw = JSON.parse(argsJson);
|
|
6883
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
6884
|
+
parsedArgs = raw;
|
|
6885
|
+
}
|
|
6824
6886
|
} catch {
|
|
6825
6887
|
continue;
|
|
6826
6888
|
}
|
|
@@ -6934,9 +6996,16 @@ class OpenAICompatProvider {
|
|
|
6934
6996
|
constructor(config) {
|
|
6935
6997
|
const meta = PROVIDERS[config.id];
|
|
6936
6998
|
const baseURL = config.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
6999
|
+
const isOpenRouter = baseURL.includes("openrouter.ai");
|
|
6937
7000
|
this.client = new OpenAI({
|
|
6938
7001
|
apiKey: config.apiKey || "ollama",
|
|
6939
|
-
baseURL
|
|
7002
|
+
baseURL,
|
|
7003
|
+
...isOpenRouter && {
|
|
7004
|
+
defaultHeaders: {
|
|
7005
|
+
"HTTP-Referer": "https://github.com/unmodeled/vessel-browser",
|
|
7006
|
+
"X-Title": "Vessel"
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
6940
7009
|
});
|
|
6941
7010
|
this.providerId = config.id;
|
|
6942
7011
|
this.model = config.model || meta?.defaultModel || "gpt-4o";
|
|
@@ -7217,7 +7286,7 @@ class OpenAICompatProvider {
|
|
|
7217
7286
|
}
|
|
7218
7287
|
continue;
|
|
7219
7288
|
}
|
|
7220
|
-
const argSummary = args.url
|
|
7289
|
+
const argSummary = [args.url, args.query, args.text, args.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
7221
7290
|
onChunk(`
|
|
7222
7291
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
7223
7292
|
`);
|
|
@@ -7375,7 +7444,7 @@ async function fetchProviderModels(config) {
|
|
|
7375
7444
|
if (normalized.id === "anthropic") {
|
|
7376
7445
|
const client2 = new Anthropic({ apiKey: normalized.apiKey });
|
|
7377
7446
|
const page2 = await client2.models.list();
|
|
7378
|
-
return {
|
|
7447
|
+
return okResult({ models: page2.data.map((model) => model.id) });
|
|
7379
7448
|
}
|
|
7380
7449
|
const meta = PROVIDERS[normalized.id];
|
|
7381
7450
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
@@ -7387,9 +7456,10 @@ async function fetchProviderModels(config) {
|
|
|
7387
7456
|
const models = page.data.map((model) => model.id);
|
|
7388
7457
|
const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
|
|
7389
7458
|
return {
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7459
|
+
...okResult({
|
|
7460
|
+
models,
|
|
7461
|
+
...warning ? { warning } : {}
|
|
7462
|
+
})
|
|
7393
7463
|
};
|
|
7394
7464
|
}
|
|
7395
7465
|
function createProvider(config) {
|
|
@@ -7404,6 +7474,7 @@ function createProvider(config) {
|
|
|
7404
7474
|
return new OpenAICompatProvider(normalized);
|
|
7405
7475
|
}
|
|
7406
7476
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
7477
|
+
const logger$c = createLogger("DevTrace");
|
|
7407
7478
|
let cachedFactory;
|
|
7408
7479
|
function createNoopTraceSession() {
|
|
7409
7480
|
return {
|
|
@@ -7436,7 +7507,7 @@ function loadLocalFactory() {
|
|
|
7436
7507
|
return cachedFactory;
|
|
7437
7508
|
}
|
|
7438
7509
|
} catch (err) {
|
|
7439
|
-
|
|
7510
|
+
logger$c.warn("Failed to load local trace logger:", err);
|
|
7440
7511
|
}
|
|
7441
7512
|
}
|
|
7442
7513
|
return cachedFactory;
|
|
@@ -11171,22 +11242,35 @@ function formatDeadLinkMessage(label, result) {
|
|
|
11171
11242
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
11172
11243
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
11173
11244
|
}
|
|
11245
|
+
const logger$b = createLogger("Screenshot");
|
|
11246
|
+
const SCREENSHOT_RETRY_COUNT = 3;
|
|
11247
|
+
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
11174
11248
|
async function captureScreenshot(wc) {
|
|
11175
|
-
for (let attempt = 0; attempt <
|
|
11176
|
-
await new Promise(
|
|
11249
|
+
for (let attempt = 0; attempt < SCREENSHOT_RETRY_COUNT; attempt += 1) {
|
|
11250
|
+
await new Promise(
|
|
11251
|
+
(resolve) => setTimeout(resolve, SCREENSHOT_RETRY_BASE_DELAY_MS * (attempt + 1))
|
|
11252
|
+
);
|
|
11177
11253
|
try {
|
|
11178
11254
|
const image = await wc.capturePage();
|
|
11179
11255
|
if (!image.isEmpty()) {
|
|
11180
11256
|
const size = image.getSize();
|
|
11181
11257
|
const base64 = image.toPNG().toString("base64");
|
|
11182
11258
|
if (base64) {
|
|
11183
|
-
return {
|
|
11259
|
+
return okResult({
|
|
11260
|
+
base64,
|
|
11261
|
+
width: size.width,
|
|
11262
|
+
height: size.height
|
|
11263
|
+
});
|
|
11184
11264
|
}
|
|
11185
11265
|
}
|
|
11186
|
-
} catch {
|
|
11266
|
+
} catch (err) {
|
|
11267
|
+
logger$b.debug(
|
|
11268
|
+
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
11269
|
+
getErrorMessage(err)
|
|
11270
|
+
);
|
|
11187
11271
|
}
|
|
11188
11272
|
}
|
|
11189
|
-
return
|
|
11273
|
+
return errorResult("Page image was empty after 3 attempts");
|
|
11190
11274
|
}
|
|
11191
11275
|
const SESSION_VERSION = 1;
|
|
11192
11276
|
function getSessionsDir() {
|
|
@@ -12081,17 +12165,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
12081
12165
|
appliedFilters
|
|
12082
12166
|
};
|
|
12083
12167
|
}
|
|
12168
|
+
const logger$a = createLogger("PageActions");
|
|
12084
12169
|
function getBookmarkMetadataFromArgs(args) {
|
|
12085
12170
|
return normalizeBookmarkMetadata({
|
|
12086
|
-
intent: args.intent,
|
|
12087
|
-
expectedContent: args.expectedContent,
|
|
12088
|
-
keyFields: args.keyFields,
|
|
12089
|
-
agentHints: args.agentHints
|
|
12171
|
+
intent: args.intent ?? args.intent,
|
|
12172
|
+
expectedContent: args.expectedContent ?? args.expected_content,
|
|
12173
|
+
keyFields: args.keyFields ?? args.key_fields,
|
|
12174
|
+
agentHints: args.agentHints ?? args.agent_hints
|
|
12090
12175
|
});
|
|
12091
12176
|
}
|
|
12092
12177
|
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
12093
12178
|
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
12094
|
-
async function loadPermittedUrl
|
|
12179
|
+
async function loadPermittedUrl(wc, url) {
|
|
12095
12180
|
assertPermittedNavigationURL(url);
|
|
12096
12181
|
await wc.loadURL(url);
|
|
12097
12182
|
}
|
|
@@ -12264,7 +12349,9 @@ async function executePageScript(wc, script, options) {
|
|
|
12264
12349
|
return PAGE_SCRIPT_TIMEOUT;
|
|
12265
12350
|
}
|
|
12266
12351
|
return result;
|
|
12267
|
-
} catch {
|
|
12352
|
+
} catch (err) {
|
|
12353
|
+
const label = options?.label ? ` (${options.label})` : "";
|
|
12354
|
+
logger$a.warn(`Failed to execute page script${label}:`, err);
|
|
12268
12355
|
return null;
|
|
12269
12356
|
} finally {
|
|
12270
12357
|
if (timer) {
|
|
@@ -12364,7 +12451,8 @@ async function getPostSearchSummary(wc) {
|
|
|
12364
12451
|
Search results snapshot:
|
|
12365
12452
|
${truncated}`;
|
|
12366
12453
|
}
|
|
12367
|
-
} catch {
|
|
12454
|
+
} catch (err) {
|
|
12455
|
+
logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
12368
12456
|
}
|
|
12369
12457
|
const fallback = await getPostNavSummary(wc);
|
|
12370
12458
|
return fallback ? `${fallback}
|
|
@@ -12386,11 +12474,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
|
|
|
12386
12474
|
Page snapshot after navigation:
|
|
12387
12475
|
${truncated}`;
|
|
12388
12476
|
}
|
|
12389
|
-
} catch {
|
|
12477
|
+
} catch (err) {
|
|
12478
|
+
logger$a.warn("Failed to build post-click navigation summary:", err);
|
|
12390
12479
|
}
|
|
12391
12480
|
return "";
|
|
12392
12481
|
}
|
|
12393
|
-
async function scrollPage
|
|
12482
|
+
async function scrollPage(wc, deltaY) {
|
|
12394
12483
|
const getScrollY = async () => {
|
|
12395
12484
|
const scrollY = await executePageScript(
|
|
12396
12485
|
wc,
|
|
@@ -12434,7 +12523,7 @@ async function scrollPage$1(wc, deltaY) {
|
|
|
12434
12523
|
movedY: Math.round(afterY - beforeY)
|
|
12435
12524
|
};
|
|
12436
12525
|
}
|
|
12437
|
-
async function clickElement
|
|
12526
|
+
async function clickElement(wc, selector) {
|
|
12438
12527
|
const target = await executePageScript(
|
|
12439
12528
|
wc,
|
|
12440
12529
|
`
|
|
@@ -12523,7 +12612,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12523
12612
|
return "Error: Could not resolve click coordinates";
|
|
12524
12613
|
}
|
|
12525
12614
|
if (hiddenWindow) {
|
|
12526
|
-
const activationResult = await activateElement
|
|
12615
|
+
const activationResult = await activateElement(wc, selector);
|
|
12527
12616
|
if (activationResult.startsWith("Error:")) {
|
|
12528
12617
|
return activationResult;
|
|
12529
12618
|
}
|
|
@@ -12538,7 +12627,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12538
12627
|
await sleep(80);
|
|
12539
12628
|
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
12540
12629
|
}
|
|
12541
|
-
async function activateElement
|
|
12630
|
+
async function activateElement(wc, selector) {
|
|
12542
12631
|
const activated = await executePageScript(
|
|
12543
12632
|
wc,
|
|
12544
12633
|
`
|
|
@@ -12570,7 +12659,7 @@ async function activateElement$1(wc, selector) {
|
|
|
12570
12659
|
}
|
|
12571
12660
|
return "Activated element via DOM click";
|
|
12572
12661
|
}
|
|
12573
|
-
async function describeElementForClick
|
|
12662
|
+
async function describeElementForClick(wc, selector) {
|
|
12574
12663
|
const result = await executePageScript(
|
|
12575
12664
|
wc,
|
|
12576
12665
|
`
|
|
@@ -12879,7 +12968,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12879
12968
|
return;
|
|
12880
12969
|
}
|
|
12881
12970
|
}
|
|
12882
|
-
} catch {
|
|
12971
|
+
} catch (err) {
|
|
12972
|
+
logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
12883
12973
|
}
|
|
12884
12974
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
12885
12975
|
try {
|
|
@@ -12887,14 +12977,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12887
12977
|
await wc.loadURL(snapshot.url);
|
|
12888
12978
|
await waitForLoad(wc, 3e3);
|
|
12889
12979
|
return;
|
|
12890
|
-
} catch {
|
|
12980
|
+
} catch (err) {
|
|
12981
|
+
logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
12891
12982
|
}
|
|
12892
12983
|
}
|
|
12893
12984
|
if (snapshot.url) {
|
|
12894
12985
|
try {
|
|
12895
12986
|
await wc.reload();
|
|
12896
12987
|
await waitForLoad(wc, 3e3);
|
|
12897
|
-
} catch {
|
|
12988
|
+
} catch (err) {
|
|
12989
|
+
logger$a.warn("Failed to restore locale via page reload:", err);
|
|
12898
12990
|
}
|
|
12899
12991
|
}
|
|
12900
12992
|
}
|
|
@@ -13025,14 +13117,14 @@ Go back to search results to select the next product.`;
|
|
|
13025
13117
|
if (!overlayHint) {
|
|
13026
13118
|
return cartSummary;
|
|
13027
13119
|
}
|
|
13028
|
-
const dialogActions = await getCartDialogActions
|
|
13120
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13029
13121
|
const actionsSuffix = dialogActions ? `
|
|
13030
13122
|
${dialogActions}
|
|
13031
13123
|
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
13032
13124
|
return `
|
|
13033
13125
|
${overlayHint}${actionsSuffix}${cartSummary}`;
|
|
13034
13126
|
}
|
|
13035
|
-
async function clickResolvedSelector
|
|
13127
|
+
async function clickResolvedSelector(wc, selector) {
|
|
13036
13128
|
if (selector.startsWith("__vessel_idx:")) {
|
|
13037
13129
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
13038
13130
|
const beforeUrl2 = wc.getURL();
|
|
@@ -13065,10 +13157,10 @@ Go back and select a different product.`;
|
|
|
13065
13157
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
13066
13158
|
const afterUrl2 = wc.getURL();
|
|
13067
13159
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
13068
|
-
let idxOverlay = await detectPostClickOverlay
|
|
13160
|
+
let idxOverlay = await detectPostClickOverlay(wc);
|
|
13069
13161
|
if (!idxOverlay && idxCartMatch) {
|
|
13070
13162
|
await sleep(1200);
|
|
13071
|
-
idxOverlay = await detectPostClickOverlay
|
|
13163
|
+
idxOverlay = await detectPostClickOverlay(wc);
|
|
13072
13164
|
}
|
|
13073
13165
|
if (idxCartMatch) {
|
|
13074
13166
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
|
|
@@ -13077,7 +13169,7 @@ Go back and select a different product.`;
|
|
|
13077
13169
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
13078
13170
|
if (hrefMatch) {
|
|
13079
13171
|
try {
|
|
13080
|
-
await loadPermittedUrl
|
|
13172
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13081
13173
|
await waitForLoad(wc, 8e3);
|
|
13082
13174
|
const hrefUrl = wc.getURL();
|
|
13083
13175
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13132,10 +13224,10 @@ Go back and select a different product.`;
|
|
|
13132
13224
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
13133
13225
|
const afterUrl2 = wc.getURL();
|
|
13134
13226
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
13135
|
-
let shadowOverlay = await detectPostClickOverlay
|
|
13227
|
+
let shadowOverlay = await detectPostClickOverlay(wc);
|
|
13136
13228
|
if (!shadowOverlay && shadowCartMatch) {
|
|
13137
13229
|
await sleep(1200);
|
|
13138
|
-
shadowOverlay = await detectPostClickOverlay
|
|
13230
|
+
shadowOverlay = await detectPostClickOverlay(wc);
|
|
13139
13231
|
}
|
|
13140
13232
|
if (shadowCartMatch) {
|
|
13141
13233
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
|
|
@@ -13144,7 +13236,7 @@ Go back and select a different product.`;
|
|
|
13144
13236
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
13145
13237
|
if (hrefMatch) {
|
|
13146
13238
|
try {
|
|
13147
|
-
await loadPermittedUrl
|
|
13239
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13148
13240
|
await waitForLoad(wc, 8e3);
|
|
13149
13241
|
const hrefUrl = wc.getURL();
|
|
13150
13242
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13157,14 +13249,14 @@ ${shadowOverlay}` : `${result}
|
|
|
13157
13249
|
Note: Page did not change after click.`;
|
|
13158
13250
|
}
|
|
13159
13251
|
const beforeUrl = wc.getURL();
|
|
13160
|
-
const elInfo = await describeElementForClick
|
|
13252
|
+
const elInfo = await describeElementForClick(wc, selector);
|
|
13161
13253
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
13162
13254
|
const cartMatch = isAddToCartText(elInfo.text);
|
|
13163
13255
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
13164
13256
|
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
13165
13257
|
}
|
|
13166
13258
|
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
13167
|
-
const dialogActions = await getCartDialogActions
|
|
13259
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13168
13260
|
if (dialogActions) {
|
|
13169
13261
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
13170
13262
|
${dialogActions}
|
|
@@ -13187,14 +13279,14 @@ Go back and select a different product.`;
|
|
|
13187
13279
|
}
|
|
13188
13280
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
13189
13281
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
13190
|
-
const clickResult = await clickElement
|
|
13282
|
+
const clickResult = await clickElement(wc, selector);
|
|
13191
13283
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
13192
13284
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13193
13285
|
const afterUrl = wc.getURL();
|
|
13194
13286
|
if (afterUrl !== beforeUrl) {
|
|
13195
13287
|
return `${clickText} -> ${afterUrl}`;
|
|
13196
13288
|
}
|
|
13197
|
-
const overlayHint = await detectPostClickOverlay
|
|
13289
|
+
const overlayHint = await detectPostClickOverlay(wc);
|
|
13198
13290
|
if (overlayHint) {
|
|
13199
13291
|
if (cartMatch) {
|
|
13200
13292
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
@@ -13208,7 +13300,7 @@ ${overlayHint}`;
|
|
|
13208
13300
|
}
|
|
13209
13301
|
if (cartMatch) {
|
|
13210
13302
|
await sleep(1200);
|
|
13211
|
-
const delayedOverlayHint = await detectPostClickOverlay
|
|
13303
|
+
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
13212
13304
|
if (delayedOverlayHint) {
|
|
13213
13305
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
13214
13306
|
wc,
|
|
@@ -13221,7 +13313,7 @@ ${overlayHint}`;
|
|
|
13221
13313
|
beforeUrl
|
|
13222
13314
|
)}`;
|
|
13223
13315
|
}
|
|
13224
|
-
const activationResult = await activateElement
|
|
13316
|
+
const activationResult = await activateElement(wc, selector);
|
|
13225
13317
|
if (!activationResult.startsWith("Error:")) {
|
|
13226
13318
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13227
13319
|
const fallbackUrl = wc.getURL();
|
|
@@ -13229,7 +13321,7 @@ ${overlayHint}`;
|
|
|
13229
13321
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
13230
13322
|
}
|
|
13231
13323
|
}
|
|
13232
|
-
const postActivationOverlayHint = await detectPostClickOverlay
|
|
13324
|
+
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
13233
13325
|
if (postActivationOverlayHint) {
|
|
13234
13326
|
return `${clickText} (${clickResult})
|
|
13235
13327
|
${postActivationOverlayHint}`;
|
|
@@ -13239,13 +13331,14 @@ ${postActivationOverlayHint}`;
|
|
|
13239
13331
|
const validation = await validateLinkDestination(elInfo.href);
|
|
13240
13332
|
if (validation.status !== "dead") {
|
|
13241
13333
|
try {
|
|
13242
|
-
await loadPermittedUrl
|
|
13334
|
+
await loadPermittedUrl(wc, elInfo.href);
|
|
13243
13335
|
await waitForLoad(wc, 8e3);
|
|
13244
13336
|
const hrefFallbackUrl = wc.getURL();
|
|
13245
13337
|
if (hrefFallbackUrl !== beforeUrl) {
|
|
13246
13338
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
13247
13339
|
}
|
|
13248
|
-
} catch {
|
|
13340
|
+
} catch (err) {
|
|
13341
|
+
logger$a.warn("Failed href fallback after click, returning generic click result:", err);
|
|
13249
13342
|
}
|
|
13250
13343
|
}
|
|
13251
13344
|
}
|
|
@@ -13289,11 +13382,12 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
13289
13382
|
await sleep(500);
|
|
13290
13383
|
return result;
|
|
13291
13384
|
}
|
|
13292
|
-
} catch {
|
|
13385
|
+
} catch (err) {
|
|
13386
|
+
logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
13293
13387
|
}
|
|
13294
13388
|
return null;
|
|
13295
13389
|
}
|
|
13296
|
-
async function getCartDialogActions
|
|
13390
|
+
async function getCartDialogActions(wc) {
|
|
13297
13391
|
const result = await executePageScript(
|
|
13298
13392
|
wc,
|
|
13299
13393
|
`
|
|
@@ -13338,7 +13432,7 @@ async function getCartDialogActions$1(wc) {
|
|
|
13338
13432
|
return `Available dialog actions:
|
|
13339
13433
|
${result.actions.join("\n")}`;
|
|
13340
13434
|
}
|
|
13341
|
-
async function detectPostClickOverlay
|
|
13435
|
+
async function detectPostClickOverlay(wc) {
|
|
13342
13436
|
const result = await executePageScript(
|
|
13343
13437
|
wc,
|
|
13344
13438
|
`
|
|
@@ -13444,7 +13538,7 @@ async function detectPostClickOverlay$1(wc) {
|
|
|
13444
13538
|
const desc = result.label ? ` ("${result.label}")` : "";
|
|
13445
13539
|
return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
|
|
13446
13540
|
}
|
|
13447
|
-
async function dismissPopup
|
|
13541
|
+
async function dismissPopup(wc) {
|
|
13448
13542
|
const before = await extractContent(wc);
|
|
13449
13543
|
const initialBlocking = before.overlays.filter(
|
|
13450
13544
|
(overlay) => overlay.blocksInteraction
|
|
@@ -13497,7 +13591,7 @@ async function dismissPopup$1(wc) {
|
|
|
13497
13591
|
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
13498
13592
|
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
13499
13593
|
}
|
|
13500
|
-
const dialogActions = await getCartDialogActions
|
|
13594
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13501
13595
|
return `Cannot dismiss: this is a cart confirmation dialog. Item is in your cart.${dialogActions ? "\n" + dialogActions + "\nClick one of these instead." : " Use read_page to see dialog actions."}`;
|
|
13502
13596
|
}
|
|
13503
13597
|
}
|
|
@@ -13657,7 +13751,7 @@ async function dismissPopup$1(wc) {
|
|
|
13657
13751
|
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
13658
13752
|
continue;
|
|
13659
13753
|
}
|
|
13660
|
-
const result = await clickElement
|
|
13754
|
+
const result = await clickElement(wc, candidate.selector);
|
|
13661
13755
|
if (result.startsWith("Error:")) continue;
|
|
13662
13756
|
await sleep(250);
|
|
13663
13757
|
const postClickLocale = await getLocaleSnapshot(wc);
|
|
@@ -13699,7 +13793,7 @@ function describeOverlayState(page) {
|
|
|
13699
13793
|
}
|
|
13700
13794
|
async function clickOverlayCandidate(wc, action) {
|
|
13701
13795
|
if (!action?.selector) return null;
|
|
13702
|
-
const result = await clickResolvedSelector
|
|
13796
|
+
const result = await clickResolvedSelector(wc, action.selector);
|
|
13703
13797
|
return `${action.label || action.selector}: ${result}`;
|
|
13704
13798
|
}
|
|
13705
13799
|
async function tryDismissConsentIframe(wc) {
|
|
@@ -13910,7 +14004,7 @@ Submitted modal: ${submitResult}`;
|
|
|
13910
14004
|
}
|
|
13911
14005
|
}
|
|
13912
14006
|
if (!actionMessage) {
|
|
13913
|
-
actionMessage = `Fallback popup handling: ${await dismissPopup
|
|
14007
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
|
|
13914
14008
|
}
|
|
13915
14009
|
steps.push(actionMessage);
|
|
13916
14010
|
if (overlay.kind === "cookie_consent") {
|
|
@@ -14085,7 +14179,7 @@ async function fillFormFields(wc, fields) {
|
|
|
14085
14179
|
});
|
|
14086
14180
|
continue;
|
|
14087
14181
|
}
|
|
14088
|
-
const result = await setElementValue
|
|
14182
|
+
const result = await setElementValue(
|
|
14089
14183
|
wc,
|
|
14090
14184
|
selector,
|
|
14091
14185
|
String(field.value || "")
|
|
@@ -14094,14 +14188,14 @@ async function fillFormFields(wc, fields) {
|
|
|
14094
14188
|
}
|
|
14095
14189
|
return results;
|
|
14096
14190
|
}
|
|
14097
|
-
function getTabByMatch
|
|
14191
|
+
function getTabByMatch(tabManager, match) {
|
|
14098
14192
|
if (!match) return null;
|
|
14099
14193
|
const lowered = match.toLowerCase();
|
|
14100
14194
|
return tabManager.getAllStates().find(
|
|
14101
14195
|
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
14102
14196
|
) || null;
|
|
14103
14197
|
}
|
|
14104
|
-
function isDangerousAction
|
|
14198
|
+
function isDangerousAction(name) {
|
|
14105
14199
|
return [
|
|
14106
14200
|
"navigate",
|
|
14107
14201
|
"open_bookmark",
|
|
@@ -14112,6 +14206,7 @@ function isDangerousAction$1(name) {
|
|
|
14112
14206
|
"press_key",
|
|
14113
14207
|
"create_tab",
|
|
14114
14208
|
"switch_tab",
|
|
14209
|
+
"close_tab",
|
|
14115
14210
|
"restore_checkpoint",
|
|
14116
14211
|
"load_session",
|
|
14117
14212
|
"login",
|
|
@@ -14120,7 +14215,7 @@ function isDangerousAction$1(name) {
|
|
|
14120
14215
|
"paginate"
|
|
14121
14216
|
].includes(name);
|
|
14122
14217
|
}
|
|
14123
|
-
async function setElementValue
|
|
14218
|
+
async function setElementValue(wc, selector, value) {
|
|
14124
14219
|
if (selector.startsWith("__vessel_idx:")) {
|
|
14125
14220
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
14126
14221
|
const result2 = await executePageScript(
|
|
@@ -14224,7 +14319,7 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
14224
14319
|
);
|
|
14225
14320
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14226
14321
|
}
|
|
14227
|
-
async function typeKeystroke
|
|
14322
|
+
async function typeKeystroke(wc, selector, value) {
|
|
14228
14323
|
const result = await executePageScript(
|
|
14229
14324
|
wc,
|
|
14230
14325
|
`
|
|
@@ -14272,7 +14367,7 @@ async function typeKeystroke$1(wc, selector, value) {
|
|
|
14272
14367
|
);
|
|
14273
14368
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14274
14369
|
}
|
|
14275
|
-
async function hoverElement
|
|
14370
|
+
async function hoverElement(wc, selector) {
|
|
14276
14371
|
const pos = await wc.executeJavaScript(`
|
|
14277
14372
|
(function() {
|
|
14278
14373
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14302,7 +14397,7 @@ async function hoverElement$1(wc, selector) {
|
|
|
14302
14397
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
14303
14398
|
return `Hovered: ${label}`;
|
|
14304
14399
|
}
|
|
14305
|
-
async function focusElement
|
|
14400
|
+
async function focusElement(wc, selector) {
|
|
14306
14401
|
return wc.executeJavaScript(`
|
|
14307
14402
|
(function() {
|
|
14308
14403
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14316,7 +14411,7 @@ async function focusElement$1(wc, selector) {
|
|
|
14316
14411
|
})()
|
|
14317
14412
|
`);
|
|
14318
14413
|
}
|
|
14319
|
-
async function waitForCondition
|
|
14414
|
+
async function waitForCondition(wc, args) {
|
|
14320
14415
|
const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
|
|
14321
14416
|
const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
|
|
14322
14417
|
const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
@@ -14375,8 +14470,8 @@ function findCheckpoint(checkpoints, args) {
|
|
|
14375
14470
|
}
|
|
14376
14471
|
return null;
|
|
14377
14472
|
}
|
|
14378
|
-
function resolveBookmarkFolderTarget
|
|
14379
|
-
const folderId = typeof args.folderId === "string" ? args.folderId.trim() : "";
|
|
14473
|
+
function resolveBookmarkFolderTarget(args) {
|
|
14474
|
+
const folderId = typeof args.folderId === "string" ? args.folderId.trim() : typeof args.folder_id === "string" ? args.folder_id.trim() : "";
|
|
14380
14475
|
if (folderId) {
|
|
14381
14476
|
if (folderId === UNSORTED_ID) {
|
|
14382
14477
|
return {
|
|
@@ -14390,7 +14485,7 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14390
14485
|
}
|
|
14391
14486
|
return { folderId: folder2.id, folderName: folder2.name };
|
|
14392
14487
|
}
|
|
14393
|
-
const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
14488
|
+
const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
14394
14489
|
if (!folderName || folderName.toLowerCase() === "unsorted") {
|
|
14395
14490
|
return {
|
|
14396
14491
|
folderId: UNSORTED_ID,
|
|
@@ -14401,10 +14496,11 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14401
14496
|
if (existing) {
|
|
14402
14497
|
return { folderId: existing.id, folderName: existing.name };
|
|
14403
14498
|
}
|
|
14404
|
-
|
|
14499
|
+
const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
|
|
14500
|
+
if (createIfMissing === false) {
|
|
14405
14501
|
return { folderName, error: `Folder "${folderName}" not found` };
|
|
14406
14502
|
}
|
|
14407
|
-
const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : void 0;
|
|
14503
|
+
const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
|
|
14408
14504
|
const { folder } = ensureFolder(folderName, folderSummary);
|
|
14409
14505
|
return {
|
|
14410
14506
|
folderId: folder.id,
|
|
@@ -14412,27 +14508,27 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14412
14508
|
createdFolder: folder.name
|
|
14413
14509
|
};
|
|
14414
14510
|
}
|
|
14415
|
-
function formatFolderStatus
|
|
14511
|
+
function formatFolderStatus(limit = 6) {
|
|
14416
14512
|
const folders = listFolderOverviews();
|
|
14417
14513
|
const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
|
|
14418
14514
|
return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
|
|
14419
14515
|
}
|
|
14420
|
-
function describeFolder
|
|
14516
|
+
function describeFolder(folderId) {
|
|
14421
14517
|
if (!folderId || folderId === UNSORTED_ID) {
|
|
14422
14518
|
return "Unsorted";
|
|
14423
14519
|
}
|
|
14424
14520
|
return getFolder(folderId)?.name ?? folderId;
|
|
14425
14521
|
}
|
|
14426
|
-
function composeDuplicateBookmarkResponse
|
|
14522
|
+
function composeDuplicateBookmarkResponse(args) {
|
|
14427
14523
|
return `Bookmark already exists for ${args.url} in "${args.folderName}" (id=${args.bookmarkId}). Retry with onDuplicate="update" to refresh the existing bookmark or onDuplicate="duplicate" to keep both entries.`;
|
|
14428
14524
|
}
|
|
14429
|
-
function composeFolderAwareResponse
|
|
14525
|
+
function composeFolderAwareResponse(message, createdFolder) {
|
|
14430
14526
|
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
14431
14527
|
` : "";
|
|
14432
14528
|
return `${prefix}${message}
|
|
14433
|
-
${formatFolderStatus
|
|
14529
|
+
${formatFolderStatus()}`;
|
|
14434
14530
|
}
|
|
14435
|
-
async function selectOption
|
|
14531
|
+
async function selectOption(wc, args) {
|
|
14436
14532
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
14437
14533
|
if (!selector) return "Error: No select element index or selector provided";
|
|
14438
14534
|
const result = await executePageScript(
|
|
@@ -14468,7 +14564,7 @@ async function selectOption$1(wc, args) {
|
|
|
14468
14564
|
);
|
|
14469
14565
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
|
|
14470
14566
|
}
|
|
14471
|
-
async function submitForm
|
|
14567
|
+
async function submitForm(wc, args) {
|
|
14472
14568
|
const beforeUrl = wc.getURL();
|
|
14473
14569
|
let selector = await resolveSelector(wc, args.index, args.selector);
|
|
14474
14570
|
if (!selector) {
|
|
@@ -14609,7 +14705,7 @@ async function submitForm$1(wc, args) {
|
|
|
14609
14705
|
if (formInfo.params) {
|
|
14610
14706
|
url.search = formInfo.params;
|
|
14611
14707
|
}
|
|
14612
|
-
await loadPermittedUrl
|
|
14708
|
+
await loadPermittedUrl(wc, url.toString());
|
|
14613
14709
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
14614
14710
|
const afterUrl = wc.getURL();
|
|
14615
14711
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
@@ -14651,8 +14747,20 @@ async function submitForm$1(wc, args) {
|
|
|
14651
14747
|
}
|
|
14652
14748
|
return "Submitted form";
|
|
14653
14749
|
}
|
|
14750
|
+
async function pressKeyDirect(wc, key, index, selector) {
|
|
14751
|
+
return pressKey(wc, { key, index, selector });
|
|
14752
|
+
}
|
|
14753
|
+
async function submitFormDirect(wc, index, selector) {
|
|
14754
|
+
return submitForm(wc, { index, selector });
|
|
14755
|
+
}
|
|
14756
|
+
async function selectOptionDirect(wc, index, selector, label, value) {
|
|
14757
|
+
return selectOption(wc, { index, selector, label, value });
|
|
14758
|
+
}
|
|
14759
|
+
async function waitForConditionDirect(wc, text, selector, timeoutMs) {
|
|
14760
|
+
return waitForCondition(wc, { text, selector, timeoutMs });
|
|
14761
|
+
}
|
|
14654
14762
|
async function clickElementBySelector(wc, selector) {
|
|
14655
|
-
return clickResolvedSelector
|
|
14763
|
+
return clickResolvedSelector(wc, selector);
|
|
14656
14764
|
}
|
|
14657
14765
|
function normalizeSearchQuery(query) {
|
|
14658
14766
|
return query.replace(/\s+/g, " ").trim();
|
|
@@ -14936,7 +15044,7 @@ async function searchPage(wc, args) {
|
|
|
14936
15044
|
const shortcut = buildSearchShortcut(wc.getURL(), query);
|
|
14937
15045
|
if (shortcut) {
|
|
14938
15046
|
const beforeUrl2 = wc.getURL();
|
|
14939
|
-
await loadPermittedUrl
|
|
15047
|
+
await loadPermittedUrl(wc, shortcut.url);
|
|
14940
15048
|
await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
|
|
14941
15049
|
const afterUrl2 = wc.getURL();
|
|
14942
15050
|
const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
|
|
@@ -14954,13 +15062,13 @@ async function searchPage(wc, args) {
|
|
|
14954
15062
|
if (!searchInfo?.selector) {
|
|
14955
15063
|
return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
|
|
14956
15064
|
}
|
|
14957
|
-
const fillResult = await setElementValue
|
|
15065
|
+
const fillResult = await setElementValue(wc, searchInfo.selector, query);
|
|
14958
15066
|
if (fillResult.startsWith("Error:")) {
|
|
14959
15067
|
return fillResult;
|
|
14960
15068
|
}
|
|
14961
15069
|
await sleep(100);
|
|
14962
15070
|
const beforeUrl = wc.getURL();
|
|
14963
|
-
const keyResult = await pressKey
|
|
15071
|
+
const keyResult = await pressKey(wc, {
|
|
14964
15072
|
key: "Enter",
|
|
14965
15073
|
selector: searchInfo.selector
|
|
14966
15074
|
});
|
|
@@ -14984,7 +15092,7 @@ async function searchPage(wc, args) {
|
|
|
14984
15092
|
}
|
|
14985
15093
|
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
|
|
14986
15094
|
}
|
|
14987
|
-
async function pressKey
|
|
15095
|
+
async function pressKey(wc, args) {
|
|
14988
15096
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
14989
15097
|
if (!key) return "Error: No key provided";
|
|
14990
15098
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
@@ -15196,9 +15304,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
15196
15304
|
"metrics",
|
|
15197
15305
|
"wait_for_navigation"
|
|
15198
15306
|
]);
|
|
15199
|
-
async function executeAction(name,
|
|
15307
|
+
async function executeAction(name, args, ctx) {
|
|
15200
15308
|
name = normalizeToolAlias(name);
|
|
15201
|
-
const args = rawArgs;
|
|
15202
15309
|
if (!KNOWN_TOOLS.has(name)) {
|
|
15203
15310
|
for (const known of KNOWN_TOOLS) {
|
|
15204
15311
|
if (name.startsWith(known) && name.length > known.length) {
|
|
@@ -15247,7 +15354,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15247
15354
|
name,
|
|
15248
15355
|
args,
|
|
15249
15356
|
tabId,
|
|
15250
|
-
dangerous: isDangerousAction
|
|
15357
|
+
dangerous: isDangerousAction(name),
|
|
15251
15358
|
executor: async () => {
|
|
15252
15359
|
switch (name) {
|
|
15253
15360
|
case "screenshot": {
|
|
@@ -15295,7 +15402,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15295
15402
|
case "switch_tab": {
|
|
15296
15403
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15297
15404
|
if (!targetId) {
|
|
15298
|
-
targetId = getTabByMatch
|
|
15405
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15299
15406
|
}
|
|
15300
15407
|
if (!targetId) return "Error: No matching tab found";
|
|
15301
15408
|
ctx.tabManager.switchTab(targetId);
|
|
@@ -15389,7 +15496,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15389
15496
|
if (!selector) {
|
|
15390
15497
|
return "Error: No element index, selector, or visible text provided";
|
|
15391
15498
|
}
|
|
15392
|
-
return clickResolvedSelector
|
|
15499
|
+
return clickResolvedSelector(wc, selector);
|
|
15393
15500
|
}
|
|
15394
15501
|
case "inspect_element": {
|
|
15395
15502
|
if (!wc) return "Error: No active tab";
|
|
@@ -15421,18 +15528,18 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15421
15528
|
if (!selector) return "Error: No element index or selector provided";
|
|
15422
15529
|
const mode = typeof args.mode === "string" ? args.mode : "default";
|
|
15423
15530
|
if (mode === "keystroke") {
|
|
15424
|
-
return typeKeystroke
|
|
15531
|
+
return typeKeystroke(wc, selector, String(args.text || ""));
|
|
15425
15532
|
}
|
|
15426
|
-
return setElementValue
|
|
15533
|
+
return setElementValue(wc, selector, String(args.text || ""));
|
|
15427
15534
|
}
|
|
15428
15535
|
case "select_option": {
|
|
15429
15536
|
if (!wc) return "Error: No active tab";
|
|
15430
|
-
return selectOption
|
|
15537
|
+
return selectOption(wc, args);
|
|
15431
15538
|
}
|
|
15432
15539
|
case "submit_form": {
|
|
15433
15540
|
if (!wc) return "Error: No active tab";
|
|
15434
15541
|
const beforeUrl = wc.getURL();
|
|
15435
|
-
const result2 = await submitForm
|
|
15542
|
+
const result2 = await submitForm(wc, args);
|
|
15436
15543
|
if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
|
|
15437
15544
|
return result2;
|
|
15438
15545
|
}
|
|
@@ -15443,7 +15550,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15443
15550
|
case "press_key": {
|
|
15444
15551
|
if (!wc) return "Error: No active tab";
|
|
15445
15552
|
const beforeUrl = wc.getURL();
|
|
15446
|
-
const result2 = await pressKey
|
|
15553
|
+
const result2 = await pressKey(wc, args);
|
|
15447
15554
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
15448
15555
|
if (key === "Enter") {
|
|
15449
15556
|
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
@@ -15460,20 +15567,20 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15460
15567
|
if (!wc) return "Error: No active tab";
|
|
15461
15568
|
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
15462
15569
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
15463
|
-
const result2 = await scrollPage
|
|
15570
|
+
const result2 = await scrollPage(wc, dir);
|
|
15464
15571
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
15465
15572
|
}
|
|
15466
15573
|
case "hover": {
|
|
15467
15574
|
if (!wc) return "Error: No active tab";
|
|
15468
15575
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15469
15576
|
if (!selector) return "Error: No element index or selector provided";
|
|
15470
|
-
return hoverElement
|
|
15577
|
+
return hoverElement(wc, selector);
|
|
15471
15578
|
}
|
|
15472
15579
|
case "focus": {
|
|
15473
15580
|
if (!wc) return "Error: No active tab";
|
|
15474
15581
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15475
15582
|
if (!selector) return "Error: No element index or selector provided";
|
|
15476
|
-
return focusElement
|
|
15583
|
+
return focusElement(wc, selector);
|
|
15477
15584
|
}
|
|
15478
15585
|
case "set_ad_blocking": {
|
|
15479
15586
|
const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
|
|
@@ -15482,7 +15589,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15482
15589
|
}
|
|
15483
15590
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15484
15591
|
if (!targetId) {
|
|
15485
|
-
targetId = getTabByMatch
|
|
15592
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15486
15593
|
}
|
|
15487
15594
|
if (!targetId) {
|
|
15488
15595
|
targetId = ctx.tabManager.getActiveTabId() || "";
|
|
@@ -15501,7 +15608,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15501
15608
|
}
|
|
15502
15609
|
case "dismiss_popup": {
|
|
15503
15610
|
if (!wc) return "Error: No active tab";
|
|
15504
|
-
return dismissPopup
|
|
15611
|
+
return dismissPopup(wc);
|
|
15505
15612
|
}
|
|
15506
15613
|
case "clear_overlays": {
|
|
15507
15614
|
if (!wc) return "Error: No active tab";
|
|
@@ -15524,7 +15631,8 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15524
15631
|
}, 6e3)
|
|
15525
15632
|
)
|
|
15526
15633
|
]);
|
|
15527
|
-
} catch {
|
|
15634
|
+
} catch (err) {
|
|
15635
|
+
logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
15528
15636
|
content = null;
|
|
15529
15637
|
}
|
|
15530
15638
|
if (!content || content.content.length === 0) {
|
|
@@ -15540,11 +15648,13 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15540
15648
|
extractContent(wc),
|
|
15541
15649
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
15542
15650
|
]);
|
|
15543
|
-
} catch {
|
|
15651
|
+
} catch (err) {
|
|
15652
|
+
logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
15544
15653
|
content = null;
|
|
15545
15654
|
}
|
|
15546
15655
|
}
|
|
15547
|
-
} catch {
|
|
15656
|
+
} catch (err) {
|
|
15657
|
+
logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
15548
15658
|
}
|
|
15549
15659
|
}
|
|
15550
15660
|
if (content && content.content.length > 0) {
|
|
@@ -15584,7 +15694,7 @@ ${truncated}`;
|
|
|
15584
15694
|
}
|
|
15585
15695
|
case "wait_for": {
|
|
15586
15696
|
if (!wc) return "Error: No active tab";
|
|
15587
|
-
return waitForCondition
|
|
15697
|
+
return waitForCondition(wc, args);
|
|
15588
15698
|
}
|
|
15589
15699
|
case "wait_for_navigation": {
|
|
15590
15700
|
if (!wc) return "Error: No active tab";
|
|
@@ -15727,12 +15837,12 @@ ${truncated}`;
|
|
|
15727
15837
|
(folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
|
|
15728
15838
|
);
|
|
15729
15839
|
if (existing) {
|
|
15730
|
-
return composeFolderAwareResponse
|
|
15840
|
+
return composeFolderAwareResponse(
|
|
15731
15841
|
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
15732
15842
|
);
|
|
15733
15843
|
}
|
|
15734
15844
|
const folder = createFolderWithSummary(name2, summary);
|
|
15735
|
-
return composeFolderAwareResponse
|
|
15845
|
+
return composeFolderAwareResponse(
|
|
15736
15846
|
`Created folder "${folder.name}" (id=${folder.id})`
|
|
15737
15847
|
);
|
|
15738
15848
|
}
|
|
@@ -15744,7 +15854,7 @@ ${truncated}`;
|
|
|
15744
15854
|
resolvedSelector
|
|
15745
15855
|
});
|
|
15746
15856
|
if ("error" in source) return `Error: ${source.error}`;
|
|
15747
|
-
const target = resolveBookmarkFolderTarget
|
|
15857
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15748
15858
|
if (target.error) return target.error;
|
|
15749
15859
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
15750
15860
|
const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
|
|
@@ -15759,10 +15869,10 @@ ${truncated}`;
|
|
|
15759
15869
|
}
|
|
15760
15870
|
);
|
|
15761
15871
|
if (result2.status === "conflict" && result2.existing) {
|
|
15762
|
-
return composeFolderAwareResponse
|
|
15763
|
-
composeDuplicateBookmarkResponse
|
|
15872
|
+
return composeFolderAwareResponse(
|
|
15873
|
+
composeDuplicateBookmarkResponse({
|
|
15764
15874
|
url: source.url,
|
|
15765
|
-
folderName: describeFolder
|
|
15875
|
+
folderName: describeFolder(target.folderId),
|
|
15766
15876
|
bookmarkId: result2.existing.id
|
|
15767
15877
|
}),
|
|
15768
15878
|
target.createdFolder
|
|
@@ -15771,13 +15881,13 @@ ${truncated}`;
|
|
|
15771
15881
|
const bookmark = result2.bookmark;
|
|
15772
15882
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15773
15883
|
const verb = result2.status === "updated" ? "Updated" : "Saved";
|
|
15774
|
-
return composeFolderAwareResponse
|
|
15775
|
-
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder
|
|
15884
|
+
return composeFolderAwareResponse(
|
|
15885
|
+
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15776
15886
|
target.createdFolder
|
|
15777
15887
|
);
|
|
15778
15888
|
}
|
|
15779
15889
|
case "organize_bookmark": {
|
|
15780
|
-
const target = resolveBookmarkFolderTarget
|
|
15890
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15781
15891
|
if (target.error) return target.error;
|
|
15782
15892
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15783
15893
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15801,8 +15911,8 @@ ${truncated}`;
|
|
|
15801
15911
|
if (!updated) {
|
|
15802
15912
|
return `Bookmark ${existing.id} not found`;
|
|
15803
15913
|
}
|
|
15804
|
-
return composeFolderAwareResponse
|
|
15805
|
-
`Organized existing bookmark "${updated.title}" into "${describeFolder
|
|
15914
|
+
return composeFolderAwareResponse(
|
|
15915
|
+
`Organized existing bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15806
15916
|
target.createdFolder
|
|
15807
15917
|
);
|
|
15808
15918
|
}
|
|
@@ -15819,13 +15929,13 @@ ${truncated}`;
|
|
|
15819
15929
|
);
|
|
15820
15930
|
const bookmark = result2.bookmark;
|
|
15821
15931
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15822
|
-
return composeFolderAwareResponse
|
|
15823
|
-
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder
|
|
15932
|
+
return composeFolderAwareResponse(
|
|
15933
|
+
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15824
15934
|
target.createdFolder
|
|
15825
15935
|
);
|
|
15826
15936
|
}
|
|
15827
15937
|
case "archive_bookmark": {
|
|
15828
|
-
const target = resolveBookmarkFolderTarget
|
|
15938
|
+
const target = resolveBookmarkFolderTarget({ archive: true });
|
|
15829
15939
|
if (target.error) return target.error;
|
|
15830
15940
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15831
15941
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15848,8 +15958,8 @@ ${truncated}`;
|
|
|
15848
15958
|
if (!updated) {
|
|
15849
15959
|
return `Bookmark ${existing.id} not found`;
|
|
15850
15960
|
}
|
|
15851
|
-
return composeFolderAwareResponse
|
|
15852
|
-
`Archived bookmark "${updated.title}" into "${describeFolder
|
|
15961
|
+
return composeFolderAwareResponse(
|
|
15962
|
+
`Archived bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15853
15963
|
target.createdFolder
|
|
15854
15964
|
);
|
|
15855
15965
|
}
|
|
@@ -15862,8 +15972,8 @@ ${truncated}`;
|
|
|
15862
15972
|
target.folderId,
|
|
15863
15973
|
note
|
|
15864
15974
|
);
|
|
15865
|
-
return composeFolderAwareResponse
|
|
15866
|
-
`Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder
|
|
15975
|
+
return composeFolderAwareResponse(
|
|
15976
|
+
`Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15867
15977
|
target.createdFolder
|
|
15868
15978
|
);
|
|
15869
15979
|
}
|
|
@@ -15951,7 +16061,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15951
16061
|
let page;
|
|
15952
16062
|
try {
|
|
15953
16063
|
page = await extractContent(wc);
|
|
15954
|
-
} catch {
|
|
16064
|
+
} catch (err) {
|
|
16065
|
+
logger$a.warn("Failed to extract content for suggest:", err);
|
|
15955
16066
|
return "Could not read page. Try navigate to a working URL.";
|
|
15956
16067
|
}
|
|
15957
16068
|
const suggestions = [];
|
|
@@ -16056,7 +16167,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
16056
16167
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
16057
16168
|
if (firstSel) {
|
|
16058
16169
|
const beforeUrl = wc.getURL();
|
|
16059
|
-
const submitResult = await submitForm
|
|
16170
|
+
const submitResult = await submitForm(wc, { selector: firstSel });
|
|
16060
16171
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
16061
16172
|
const afterUrl = wc.getURL();
|
|
16062
16173
|
results.push(
|
|
@@ -16104,13 +16215,13 @@ ${results.join("\n")}`;
|
|
|
16104
16215
|
);
|
|
16105
16216
|
if (!passSel)
|
|
16106
16217
|
return "Error: Could not find password field. Try providing password_selector.";
|
|
16107
|
-
const userResult = await setElementValue
|
|
16218
|
+
const userResult = await setElementValue(
|
|
16108
16219
|
wc,
|
|
16109
16220
|
userSel,
|
|
16110
16221
|
String(args.username || "")
|
|
16111
16222
|
);
|
|
16112
16223
|
steps.push(userResult);
|
|
16113
|
-
const passResult = await setElementValue
|
|
16224
|
+
const passResult = await setElementValue(
|
|
16114
16225
|
wc,
|
|
16115
16226
|
passSel,
|
|
16116
16227
|
String(args.password || "")
|
|
@@ -16118,7 +16229,7 @@ ${results.join("\n")}`;
|
|
|
16118
16229
|
steps.push(passResult);
|
|
16119
16230
|
const beforeUrl = wc.getURL();
|
|
16120
16231
|
if (args.submit_selector) {
|
|
16121
|
-
await clickResolvedSelector
|
|
16232
|
+
await clickResolvedSelector(wc, args.submit_selector);
|
|
16122
16233
|
} else {
|
|
16123
16234
|
const clicked = await executePageScript(
|
|
16124
16235
|
wc,
|
|
@@ -16157,7 +16268,7 @@ ${steps.join("\n")}`;
|
|
|
16157
16268
|
if (!wc) return "Error: No active tab";
|
|
16158
16269
|
const beforeUrl = wc.getURL();
|
|
16159
16270
|
if (args.selector) {
|
|
16160
|
-
return clickResolvedSelector
|
|
16271
|
+
return clickResolvedSelector(wc, args.selector);
|
|
16161
16272
|
}
|
|
16162
16273
|
const isNext = args.direction === "next";
|
|
16163
16274
|
const clicked = await executePageScript(
|
|
@@ -17282,6 +17393,7 @@ const ALGORITHM = "aes-256-gcm";
|
|
|
17282
17393
|
const IV_LENGTH = 12;
|
|
17283
17394
|
const AUTH_TAG_LENGTH = 16;
|
|
17284
17395
|
let cachedEntries = null;
|
|
17396
|
+
const logger$9 = createLogger("Vault");
|
|
17285
17397
|
function getVaultDir() {
|
|
17286
17398
|
return electron.app.getPath("userData");
|
|
17287
17399
|
}
|
|
@@ -17348,7 +17460,7 @@ function loadVault() {
|
|
|
17348
17460
|
cachedEntries = JSON.parse(json);
|
|
17349
17461
|
return cachedEntries;
|
|
17350
17462
|
} catch (err) {
|
|
17351
|
-
|
|
17463
|
+
logger$9.error("Failed to load vault:", err);
|
|
17352
17464
|
throw new Error(
|
|
17353
17465
|
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
17354
17466
|
);
|
|
@@ -17490,6 +17602,7 @@ async function requestConsent(request) {
|
|
|
17490
17602
|
}
|
|
17491
17603
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
17492
17604
|
const MAX_ENTRIES = 1e3;
|
|
17605
|
+
const logger$8 = createLogger("VaultAudit");
|
|
17493
17606
|
function getAuditPath() {
|
|
17494
17607
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
17495
17608
|
}
|
|
@@ -17499,7 +17612,7 @@ function appendAuditEntry(entry) {
|
|
|
17499
17612
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
17500
17613
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
17501
17614
|
} catch (err) {
|
|
17502
|
-
|
|
17615
|
+
logger$8.error("Failed to write audit log:", err);
|
|
17503
17616
|
}
|
|
17504
17617
|
}
|
|
17505
17618
|
function readAuditLog(limit = 100) {
|
|
@@ -17509,20 +17622,13 @@ function readAuditLog(limit = 100) {
|
|
|
17509
17622
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
17510
17623
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
17511
17624
|
} catch (err) {
|
|
17512
|
-
|
|
17625
|
+
logger$8.error("Failed to read audit log:", err);
|
|
17513
17626
|
return [];
|
|
17514
17627
|
}
|
|
17515
17628
|
}
|
|
17516
17629
|
let httpServer = null;
|
|
17517
17630
|
let mcpAuthToken = null;
|
|
17518
|
-
|
|
17519
|
-
return normalizeBookmarkMetadata({
|
|
17520
|
-
intent: args.intent,
|
|
17521
|
-
expectedContent: args.expected_content,
|
|
17522
|
-
keyFields: args.key_fields,
|
|
17523
|
-
agentHints: args.agent_hints
|
|
17524
|
-
});
|
|
17525
|
-
}
|
|
17631
|
+
const logger$7 = createLogger("MCP");
|
|
17526
17632
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
17527
17633
|
function getMcpAuthFilePath() {
|
|
17528
17634
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -17558,7 +17664,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
17558
17664
|
{ mode: 384 }
|
|
17559
17665
|
);
|
|
17560
17666
|
} catch (err) {
|
|
17561
|
-
|
|
17667
|
+
logger$7.warn("Failed to write auth file:", err);
|
|
17562
17668
|
}
|
|
17563
17669
|
}
|
|
17564
17670
|
function clearMcpAuthFile() {
|
|
@@ -17583,15 +17689,17 @@ function clearMcpAuthFile() {
|
|
|
17583
17689
|
{ mode: 384 }
|
|
17584
17690
|
);
|
|
17585
17691
|
} catch (err) {
|
|
17586
|
-
|
|
17692
|
+
logger$7.warn("Failed to clear auth file:", err);
|
|
17587
17693
|
}
|
|
17588
17694
|
}
|
|
17589
17695
|
function asTextResponse(text) {
|
|
17590
17696
|
return { content: [{ type: "text", text }] };
|
|
17591
17697
|
}
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17698
|
+
function asErrorTextResponse(message) {
|
|
17699
|
+
return asTextResponse(`Error: ${message}`);
|
|
17700
|
+
}
|
|
17701
|
+
function asNoActiveTabResponse() {
|
|
17702
|
+
return asErrorTextResponse("No active tab");
|
|
17595
17703
|
}
|
|
17596
17704
|
function asPromptResponse(text) {
|
|
17597
17705
|
return {
|
|
@@ -17606,6 +17714,9 @@ function asPromptResponse(text) {
|
|
|
17606
17714
|
]
|
|
17607
17715
|
};
|
|
17608
17716
|
}
|
|
17717
|
+
function isDangerousMcpAction(name) {
|
|
17718
|
+
return name === "close_tab" || isDangerousAction(name);
|
|
17719
|
+
}
|
|
17609
17720
|
function getActiveTabSummary(tabManager) {
|
|
17610
17721
|
const activeTab = tabManager.getActiveTab();
|
|
17611
17722
|
const activeTabId = tabManager.getActiveTabId();
|
|
@@ -17622,1298 +17733,162 @@ function getActiveTabSummary(tabManager) {
|
|
|
17622
17733
|
humanFocused: true
|
|
17623
17734
|
};
|
|
17624
17735
|
}
|
|
17625
|
-
function
|
|
17626
|
-
const
|
|
17627
|
-
|
|
17628
|
-
const
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
const
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17736
|
+
async function getPostActionState(tabManager, name) {
|
|
17737
|
+
const tab = tabManager.getActiveTab();
|
|
17738
|
+
if (!tab) return "";
|
|
17739
|
+
const wc = tab.view.webContents;
|
|
17740
|
+
const navActions = [
|
|
17741
|
+
"navigate",
|
|
17742
|
+
"go_back",
|
|
17743
|
+
"go_forward",
|
|
17744
|
+
"click",
|
|
17745
|
+
"submit_form",
|
|
17746
|
+
"reload",
|
|
17747
|
+
"press_key"
|
|
17748
|
+
];
|
|
17749
|
+
const interactActions = [
|
|
17750
|
+
"type",
|
|
17751
|
+
"type_text",
|
|
17752
|
+
"select_option",
|
|
17753
|
+
"hover",
|
|
17754
|
+
"focus"
|
|
17755
|
+
];
|
|
17756
|
+
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
17757
|
+
if (navActions.includes(name)) {
|
|
17758
|
+
let warning = "";
|
|
17759
|
+
try {
|
|
17760
|
+
const page = await extractContent(wc);
|
|
17761
|
+
const issue = getRecoverableAccessIssue(page);
|
|
17762
|
+
if (issue) {
|
|
17763
|
+
const blockedUrl = wc.getURL();
|
|
17764
|
+
const canRecover = [
|
|
17765
|
+
"navigate",
|
|
17766
|
+
"open_bookmark",
|
|
17767
|
+
"click",
|
|
17768
|
+
"submit_form",
|
|
17769
|
+
"reload",
|
|
17770
|
+
"press_key"
|
|
17771
|
+
].includes(name) && tab.canGoBack();
|
|
17772
|
+
if (canRecover && tab.goBack()) {
|
|
17773
|
+
await waitForLoad(wc);
|
|
17774
|
+
warning = `
|
|
17775
|
+
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
17776
|
+
} else {
|
|
17777
|
+
warning = `
|
|
17778
|
+
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
17779
|
+
}
|
|
17780
|
+
}
|
|
17781
|
+
} catch (err) {
|
|
17782
|
+
logger$7.warn("Failed to compute post-action state warning:", err);
|
|
17649
17783
|
}
|
|
17650
|
-
return {
|
|
17651
|
-
|
|
17652
|
-
const requestedName = typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
17653
|
-
if (!requestedName || requestedName.toLowerCase() === "unsorted") {
|
|
17654
|
-
return {
|
|
17655
|
-
folderId: UNSORTED_ID,
|
|
17656
|
-
folderName: "Unsorted"
|
|
17657
|
-
};
|
|
17784
|
+
return `${warning}
|
|
17785
|
+
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
17658
17786
|
}
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17787
|
+
if (interactActions.includes(name)) {
|
|
17788
|
+
return `
|
|
17789
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
17662
17790
|
}
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
}
|
|
17670
|
-
const folderSummary = typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
|
|
17671
|
-
const { folder } = ensureFolder(requestedName, folderSummary);
|
|
17672
|
-
return {
|
|
17673
|
-
folderId: folder.id,
|
|
17674
|
-
folderName: folder.name,
|
|
17675
|
-
createdFolder: folder.name
|
|
17676
|
-
};
|
|
17677
|
-
}
|
|
17678
|
-
function composeFolderAwareResponse(message, createdFolder) {
|
|
17679
|
-
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
17680
|
-
` : "";
|
|
17681
|
-
return `${prefix}${message}
|
|
17682
|
-
${formatFolderStatus()}`;
|
|
17683
|
-
}
|
|
17684
|
-
function composeDuplicateBookmarkResponse(args) {
|
|
17685
|
-
return `Bookmark already exists for ${args.url} in "${args.folderName}" (id=${args.bookmarkId}). Retry with on_duplicate="update" to refresh the existing bookmark or on_duplicate="duplicate" to keep both entries.`;
|
|
17686
|
-
}
|
|
17687
|
-
async function scrollPage(wc, deltaY) {
|
|
17688
|
-
const getScrollY = () => wc.executeJavaScript(`
|
|
17689
|
-
(function() {
|
|
17690
|
-
return Math.max(
|
|
17691
|
-
window.scrollY || 0,
|
|
17692
|
-
window.pageYOffset || 0,
|
|
17693
|
-
document.scrollingElement?.scrollTop || 0,
|
|
17694
|
-
document.documentElement?.scrollTop || 0,
|
|
17695
|
-
document.body?.scrollTop || 0,
|
|
17696
|
-
);
|
|
17697
|
-
})()
|
|
17698
|
-
`);
|
|
17699
|
-
const beforeY = await getScrollY();
|
|
17700
|
-
await wc.executeJavaScript(`window.scrollBy(0, ${deltaY})`);
|
|
17701
|
-
await sleep(100);
|
|
17702
|
-
const afterY = await getScrollY();
|
|
17703
|
-
return {
|
|
17704
|
-
beforeY,
|
|
17705
|
-
afterY,
|
|
17706
|
-
movedY: Math.round(afterY - beforeY)
|
|
17707
|
-
};
|
|
17708
|
-
}
|
|
17709
|
-
async function clickElement(wc, selector) {
|
|
17710
|
-
const target = await wc.executeJavaScript(`
|
|
17711
|
-
(async function() {
|
|
17712
|
-
function matchesTarget(candidate, el) {
|
|
17713
|
-
return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
|
|
17714
|
-
}
|
|
17715
|
-
|
|
17716
|
-
function samplePoints(rect) {
|
|
17717
|
-
const width = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
17718
|
-
const height = window.innerHeight || document.documentElement?.clientHeight || 0;
|
|
17719
|
-
const insetX = Math.min(12, rect.width / 4);
|
|
17720
|
-
const insetY = Math.min(12, rect.height / 4);
|
|
17721
|
-
const raw = [
|
|
17722
|
-
[rect.left + rect.width / 2, rect.top + rect.height / 2],
|
|
17723
|
-
[rect.left + insetX, rect.top + insetY],
|
|
17724
|
-
[rect.right - insetX, rect.top + insetY],
|
|
17725
|
-
[rect.left + insetX, rect.bottom - insetY],
|
|
17726
|
-
[rect.right - insetX, rect.bottom - insetY],
|
|
17727
|
-
];
|
|
17728
|
-
return raw.map(([x, y]) => ({
|
|
17729
|
-
x: Math.min(Math.max(1, x), Math.max(1, width - 1)),
|
|
17730
|
-
y: Math.min(Math.max(1, y), Math.max(1, height - 1)),
|
|
17731
|
-
}));
|
|
17732
|
-
}
|
|
17733
|
-
|
|
17734
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17735
|
-
if (!el) return { error: "Element not found" };
|
|
17736
|
-
|
|
17737
|
-
if (el instanceof HTMLElement) {
|
|
17738
|
-
el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
17739
|
-
}
|
|
17740
|
-
|
|
17741
|
-
await new Promise((resolve) => {
|
|
17742
|
-
let settled = false;
|
|
17743
|
-
const finish = () => {
|
|
17744
|
-
if (settled) return;
|
|
17745
|
-
settled = true;
|
|
17746
|
-
resolve(undefined);
|
|
17747
|
-
};
|
|
17748
|
-
if (
|
|
17749
|
-
typeof requestAnimationFrame === "function" &&
|
|
17750
|
-
document.visibilityState === "visible"
|
|
17751
|
-
) {
|
|
17752
|
-
requestAnimationFrame(() => finish());
|
|
17753
|
-
}
|
|
17754
|
-
setTimeout(finish, 32);
|
|
17755
|
-
});
|
|
17756
|
-
|
|
17757
|
-
const rect = el.getBoundingClientRect();
|
|
17758
|
-
if (rect.width <= 0 || rect.height <= 0) {
|
|
17759
|
-
return { error: "Element is not visible. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it (scroll or scroll_to_element) then call read_page to refresh visible elements before clicking again." };
|
|
17760
|
-
}
|
|
17761
|
-
|
|
17762
|
-
const points = samplePoints(rect);
|
|
17763
|
-
const hit = points.find((point) => matchesTarget(document.elementFromPoint(point.x, point.y), el));
|
|
17764
|
-
const chosen = hit || points[0];
|
|
17765
|
-
const top = document.elementFromPoint(chosen.x, chosen.y);
|
|
17766
|
-
|
|
17767
|
-
return {
|
|
17768
|
-
x: Math.round(chosen.x),
|
|
17769
|
-
y: Math.round(chosen.y),
|
|
17770
|
-
obstructed: !matchesTarget(top, el),
|
|
17771
|
-
hiddenWindow: document.visibilityState !== "visible",
|
|
17772
|
-
};
|
|
17773
|
-
})()
|
|
17774
|
-
`);
|
|
17775
|
-
if (!target || typeof target !== "object") {
|
|
17776
|
-
return "Error: Could not resolve click target";
|
|
17777
|
-
}
|
|
17778
|
-
if ("error" in target && typeof target.error === "string") {
|
|
17779
|
-
return `Error: ${target.error}`;
|
|
17780
|
-
}
|
|
17781
|
-
const x = typeof target.x === "number" ? target.x : null;
|
|
17782
|
-
const y = typeof target.y === "number" ? target.y : null;
|
|
17783
|
-
const hiddenWindow = target.hiddenWindow === true;
|
|
17784
|
-
if (x == null || y == null) {
|
|
17785
|
-
return "Error: Could not resolve click coordinates";
|
|
17786
|
-
}
|
|
17787
|
-
if (hiddenWindow) {
|
|
17788
|
-
const activationResult = await activateElement(wc, selector);
|
|
17789
|
-
if (activationResult.startsWith("Error:")) {
|
|
17790
|
-
return activationResult;
|
|
17791
|
-
}
|
|
17792
|
-
await sleep(80);
|
|
17793
|
-
return "Clicked via DOM activation";
|
|
17794
|
-
}
|
|
17795
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
17796
|
-
await sleep(16);
|
|
17797
|
-
wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
|
|
17798
|
-
await sleep(24);
|
|
17799
|
-
wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
|
|
17800
|
-
await sleep(80);
|
|
17801
|
-
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
17802
|
-
}
|
|
17803
|
-
async function activateElement(wc, selector) {
|
|
17804
|
-
const activated = await wc.executeJavaScript(`
|
|
17805
|
-
(function() {
|
|
17806
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17807
|
-
if (!el) return { error: "Element not found" };
|
|
17808
|
-
if (el instanceof HTMLElement) {
|
|
17809
|
-
el.focus({ preventScroll: true });
|
|
17810
|
-
}
|
|
17811
|
-
if (typeof el.click === "function") {
|
|
17812
|
-
el.click();
|
|
17813
|
-
return { ok: true };
|
|
17814
|
-
}
|
|
17815
|
-
return { error: "Element is not clickable" };
|
|
17816
|
-
})()
|
|
17817
|
-
`);
|
|
17818
|
-
if (!activated || typeof activated !== "object") {
|
|
17819
|
-
return "Error: Could not activate element";
|
|
17820
|
-
}
|
|
17821
|
-
if ("error" in activated && typeof activated.error === "string") {
|
|
17822
|
-
return `Error: ${activated.error}`;
|
|
17823
|
-
}
|
|
17824
|
-
return "Activated element via DOM click";
|
|
17825
|
-
}
|
|
17826
|
-
async function describeElementForClick(wc, selector) {
|
|
17827
|
-
const result = await wc.executeJavaScript(`
|
|
17828
|
-
(function() {
|
|
17829
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17830
|
-
if (!el) return { error: "Element not found" };
|
|
17831
|
-
const anchor = el instanceof HTMLAnchorElement ? el : el.closest("a[href]");
|
|
17832
|
-
const text = (el.textContent || el.tagName || "Element").trim().slice(0, 100);
|
|
17833
|
-
return {
|
|
17834
|
-
text: text || "Element",
|
|
17835
|
-
href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
|
|
17836
|
-
};
|
|
17837
|
-
})()
|
|
17838
|
-
`);
|
|
17839
|
-
if (!result || typeof result !== "object") {
|
|
17840
|
-
return { error: "Element not found" };
|
|
17841
|
-
}
|
|
17842
|
-
if ("error" in result && typeof result.error === "string") {
|
|
17843
|
-
return { error: result.error };
|
|
17844
|
-
}
|
|
17845
|
-
return {
|
|
17846
|
-
text: "text" in result && typeof result.text === "string" ? result.text : "Element",
|
|
17847
|
-
href: "href" in result && typeof result.href === "string" ? result.href : void 0
|
|
17848
|
-
};
|
|
17849
|
-
}
|
|
17850
|
-
async function clickResolvedSelector(wc, selector) {
|
|
17851
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
17852
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
17853
|
-
const beforeUrl2 = wc.getURL();
|
|
17854
|
-
const idxLabel = await wc.executeJavaScript(
|
|
17855
|
-
`window.__vessel?.getElementText?.(${idx}) || ""`
|
|
17856
|
-
);
|
|
17857
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
|
|
17858
|
-
return `Blocked: "${idxLabel}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17859
|
-
}
|
|
17860
|
-
const result = await wc.executeJavaScript(
|
|
17861
|
-
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
|
|
17862
|
-
);
|
|
17863
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17864
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17865
|
-
recordCartClick(beforeUrl2, idxLabel);
|
|
17866
|
-
}
|
|
17867
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17868
|
-
const afterUrl2 = wc.getURL();
|
|
17869
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17870
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17871
|
-
if (!overlayHint2 && typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17872
|
-
await sleep(1200);
|
|
17873
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17874
|
-
}
|
|
17875
|
-
if (!overlayHint2) {
|
|
17876
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17877
|
-
if (hrefMatch) {
|
|
17878
|
-
try {
|
|
17879
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17880
|
-
await waitForLoad(wc, 8e3);
|
|
17881
|
-
const hrefUrl = wc.getURL();
|
|
17882
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17883
|
-
} catch {
|
|
17884
|
-
}
|
|
17885
|
-
}
|
|
17886
|
-
return result;
|
|
17887
|
-
}
|
|
17888
|
-
const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
|
|
17889
|
-
const actionsSuffix = dialogActions ? `
|
|
17890
|
-
${dialogActions}
|
|
17891
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17892
|
-
return `${result}
|
|
17893
|
-
${overlayHint2}${actionsSuffix}`;
|
|
17894
|
-
}
|
|
17895
|
-
if (selector.includes(" >>> ")) {
|
|
17896
|
-
const beforeUrl2 = wc.getURL();
|
|
17897
|
-
const shadowLabel = await wc.executeJavaScript(`
|
|
17898
|
-
(function() {
|
|
17899
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17900
|
-
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
17901
|
-
})()
|
|
17902
|
-
`);
|
|
17903
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
|
|
17904
|
-
return `Blocked: "${shadowLabel}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17905
|
-
}
|
|
17906
|
-
const result = await wc.executeJavaScript(`
|
|
17907
|
-
(function() {
|
|
17908
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17909
|
-
if (!el || !document.contains(el)) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
17910
|
-
if (el instanceof HTMLElement) { el.focus(); el.click(); }
|
|
17911
|
-
var anchor = el instanceof HTMLAnchorElement ? el : el.closest('a[href]');
|
|
17912
|
-
var href = anchor instanceof HTMLAnchorElement ? anchor.href : null;
|
|
17913
|
-
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase()) + (href ? "\\nhref: " + href : "");
|
|
17914
|
-
})()
|
|
17915
|
-
`);
|
|
17916
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17917
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17918
|
-
recordCartClick(beforeUrl2, shadowLabel);
|
|
17919
|
-
}
|
|
17920
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17921
|
-
const afterUrl2 = wc.getURL();
|
|
17922
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17923
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17924
|
-
if (!overlayHint2 && typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17925
|
-
await sleep(1200);
|
|
17926
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17927
|
-
}
|
|
17928
|
-
if (!overlayHint2) {
|
|
17929
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17930
|
-
if (hrefMatch) {
|
|
17931
|
-
try {
|
|
17932
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17933
|
-
await waitForLoad(wc, 8e3);
|
|
17934
|
-
const hrefUrl = wc.getURL();
|
|
17935
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17936
|
-
} catch {
|
|
17937
|
-
}
|
|
17938
|
-
}
|
|
17939
|
-
return result;
|
|
17940
|
-
}
|
|
17941
|
-
const dialogActions2 = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
|
|
17942
|
-
const actionsSuffix2 = dialogActions2 ? `
|
|
17943
|
-
${dialogActions2}
|
|
17944
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17945
|
-
return `${result}
|
|
17946
|
-
${overlayHint2}${actionsSuffix2}`;
|
|
17947
|
-
}
|
|
17948
|
-
const beforeUrl = wc.getURL();
|
|
17949
|
-
const elInfo = await describeElementForClick(wc, selector);
|
|
17950
|
-
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
17951
|
-
const cartMatch = isAddToCartText(elInfo.text);
|
|
17952
|
-
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
17953
|
-
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17954
|
-
}
|
|
17955
|
-
if (!cartMatch) {
|
|
17956
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17957
|
-
if (dialogActions) {
|
|
17958
|
-
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
17959
|
-
${dialogActions}
|
|
17960
|
-
Click one of these dialog actions instead.`;
|
|
17961
|
-
}
|
|
17962
|
-
}
|
|
17963
|
-
if (elInfo.href) {
|
|
17964
|
-
const validation = await validateLinkDestination(elInfo.href);
|
|
17965
|
-
if (validation.status === "dead") {
|
|
17966
|
-
return formatDeadLinkMessage(elInfo.text, validation);
|
|
17967
|
-
}
|
|
17968
|
-
}
|
|
17969
|
-
if (cartMatch) {
|
|
17970
|
-
recordCartClick(beforeUrl, elInfo.text);
|
|
17971
|
-
}
|
|
17972
|
-
const clickText = `Clicked: ${elInfo.text}`;
|
|
17973
|
-
const clickResult = await clickElement(wc, selector);
|
|
17974
|
-
if (clickResult.startsWith("Error:")) return clickResult;
|
|
17975
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
17976
|
-
const afterUrl = wc.getURL();
|
|
17977
|
-
if (afterUrl !== beforeUrl) {
|
|
17978
|
-
return `${clickText} -> ${afterUrl}`;
|
|
17979
|
-
}
|
|
17980
|
-
const overlayHint = await detectPostClickOverlay(wc);
|
|
17981
|
-
if (overlayHint) {
|
|
17982
|
-
const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
|
|
17983
|
-
const actionsSuffix = dialogActions ? `
|
|
17984
|
-
${dialogActions}
|
|
17985
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17986
|
-
return `${clickText} (${clickResult})
|
|
17987
|
-
${overlayHint}${actionsSuffix}`;
|
|
17988
|
-
}
|
|
17989
|
-
if (cartMatch) {
|
|
17990
|
-
await sleep(1200);
|
|
17991
|
-
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
17992
|
-
if (delayedOverlayHint) {
|
|
17993
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17994
|
-
const actionsSuffix = dialogActions ? `
|
|
17995
|
-
${dialogActions}
|
|
17996
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17997
|
-
return `${clickText} (${clickResult})
|
|
17998
|
-
${delayedOverlayHint}${actionsSuffix}`;
|
|
17999
|
-
}
|
|
18000
|
-
return `${clickText} (${clickResult})`;
|
|
18001
|
-
}
|
|
18002
|
-
const activationResult = await activateElement(wc, selector);
|
|
18003
|
-
if (!activationResult.startsWith("Error:")) {
|
|
18004
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18005
|
-
const fallbackUrl = wc.getURL();
|
|
18006
|
-
if (fallbackUrl !== beforeUrl) {
|
|
18007
|
-
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
18008
|
-
}
|
|
18009
|
-
}
|
|
18010
|
-
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
18011
|
-
if (postActivationOverlayHint) {
|
|
18012
|
-
return `${clickText} (${clickResult})
|
|
18013
|
-
${postActivationOverlayHint}`;
|
|
18014
|
-
}
|
|
18015
|
-
return `${clickText} (${clickResult})`;
|
|
18016
|
-
}
|
|
18017
|
-
async function getCartDialogActions(wc) {
|
|
18018
|
-
const result = await wc.executeJavaScript(`
|
|
18019
|
-
(function() {
|
|
18020
|
-
function isVisible(el) {
|
|
18021
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
18022
|
-
const style = getComputedStyle(el);
|
|
18023
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
18024
|
-
const rect = el.getBoundingClientRect();
|
|
18025
|
-
return rect.width >= 20 && rect.height >= 10;
|
|
18026
|
-
}
|
|
18027
|
-
|
|
18028
|
-
function findDialogRoot() {
|
|
18029
|
-
const selectors = [
|
|
18030
|
-
'[data-test="basket-flyout"]',
|
|
18031
|
-
'[role="dialog"]',
|
|
18032
|
-
'dialog[open]',
|
|
18033
|
-
'[role="alertdialog"]',
|
|
18034
|
-
'[aria-modal="true"]',
|
|
18035
|
-
];
|
|
18036
|
-
for (const selector of selectors) {
|
|
18037
|
-
const nodes = document.querySelectorAll(selector);
|
|
18038
|
-
for (const node of nodes) {
|
|
18039
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
18040
|
-
const text = (node.textContent || "").slice(0, 800).toLowerCase();
|
|
18041
|
-
const cartSignals = [
|
|
18042
|
-
"added to cart", "added to bag", "added to basket",
|
|
18043
|
-
"item added", "your basket", "your cart", "your bag",
|
|
18044
|
-
"view basket", "view cart", "continue shopping",
|
|
18045
|
-
];
|
|
18046
|
-
if (cartSignals.some((signal) => text.includes(signal))) {
|
|
18047
|
-
return node;
|
|
18048
|
-
}
|
|
18049
|
-
}
|
|
18050
|
-
}
|
|
18051
|
-
return null;
|
|
18052
|
-
}
|
|
18053
|
-
|
|
18054
|
-
const dialog = findDialogRoot();
|
|
18055
|
-
if (!dialog) return { found: false, actions: [] };
|
|
18056
|
-
|
|
18057
|
-
const actions = [];
|
|
18058
|
-
dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
|
|
18059
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18060
|
-
const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
|
|
18061
|
-
if (!label || label.length < 2) return;
|
|
18062
|
-
const href = el.getAttribute("href") || "";
|
|
18063
|
-
const selector = el.id ? "#" + el.id
|
|
18064
|
-
: el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
|
|
18065
|
-
: el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
|
|
18066
|
-
: null;
|
|
18067
|
-
if (selector) {
|
|
18068
|
-
actions.push({ label: label, href: href, selector: selector });
|
|
18069
|
-
}
|
|
18070
|
-
});
|
|
18071
|
-
|
|
18072
|
-
return {
|
|
18073
|
-
found: true,
|
|
18074
|
-
actions: actions.map((action) =>
|
|
18075
|
-
'- "' + action.label + '"' +
|
|
18076
|
-
(action.href ? ' -> ' + action.href : "") +
|
|
18077
|
-
(action.selector ? ' (selector: ' + action.selector + ')' : "")
|
|
18078
|
-
),
|
|
18079
|
-
};
|
|
18080
|
-
})()
|
|
18081
|
-
`);
|
|
18082
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18083
|
-
return null;
|
|
18084
|
-
}
|
|
18085
|
-
if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
|
|
18086
|
-
return null;
|
|
18087
|
-
}
|
|
18088
|
-
return `Available dialog actions:
|
|
18089
|
-
${result.actions.join("\n")}`;
|
|
18090
|
-
}
|
|
18091
|
-
async function detectPostClickOverlay(wc) {
|
|
18092
|
-
const result = await wc.executeJavaScript(`
|
|
18093
|
-
(function() {
|
|
18094
|
-
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
18095
|
-
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
18096
|
-
var vpArea = Math.max(1, vw * vh);
|
|
18097
|
-
|
|
18098
|
-
function isVisible(el) {
|
|
18099
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
18100
|
-
var style = getComputedStyle(el);
|
|
18101
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
18102
|
-
return el.getBoundingClientRect().width > 0;
|
|
18103
|
-
}
|
|
18104
|
-
|
|
18105
|
-
function hasFixedAncestor(el) {
|
|
18106
|
-
var current = el.parentElement;
|
|
18107
|
-
while (current && current !== document.body) {
|
|
18108
|
-
var position = getComputedStyle(current).position;
|
|
18109
|
-
if (position === "fixed" || position === "sticky") return true;
|
|
18110
|
-
current = current.parentElement;
|
|
18111
|
-
}
|
|
18112
|
-
return false;
|
|
18113
|
-
}
|
|
18114
|
-
|
|
18115
|
-
function effectiveZ(el) {
|
|
18116
|
-
var current = el;
|
|
18117
|
-
while (current && current !== document.body) {
|
|
18118
|
-
var z = parseInt(getComputedStyle(current).zIndex, 10);
|
|
18119
|
-
if (z > 0) return z;
|
|
18120
|
-
current = current.parentElement;
|
|
18121
|
-
}
|
|
18122
|
-
return 0;
|
|
18123
|
-
}
|
|
18124
|
-
|
|
18125
|
-
function touchesViewportEdge(rect) {
|
|
18126
|
-
return rect.left <= 24 || rect.top <= 24 ||
|
|
18127
|
-
rect.right >= vw - 24 || rect.bottom >= vh - 24;
|
|
18128
|
-
}
|
|
18129
|
-
|
|
18130
|
-
var cartPhrases = [
|
|
18131
|
-
"added to cart", "added to bag", "added to basket",
|
|
18132
|
-
"added to your cart", "added to your bag", "added to your basket",
|
|
18133
|
-
];
|
|
18134
|
-
var cartActions = [
|
|
18135
|
-
"view cart", "go to cart", "view basket", "go to basket",
|
|
18136
|
-
"continue shopping", "keep shopping", "checkout",
|
|
18137
|
-
];
|
|
18138
|
-
|
|
18139
|
-
var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
|
|
18140
|
-
var candidates = document.querySelectorAll(selectors);
|
|
18141
|
-
var hit = null;
|
|
18142
|
-
for (var i = 0; i < candidates.length; i++) {
|
|
18143
|
-
if (isVisible(candidates[i])) {
|
|
18144
|
-
hit = candidates[i];
|
|
18145
|
-
break;
|
|
18146
|
-
}
|
|
18147
|
-
}
|
|
18148
|
-
|
|
18149
|
-
if (!hit) {
|
|
18150
|
-
var elements = document.querySelectorAll("*");
|
|
18151
|
-
for (var j = 0; j < elements.length; j++) {
|
|
18152
|
-
var el = elements[j];
|
|
18153
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
|
|
18154
|
-
var style = getComputedStyle(el);
|
|
18155
|
-
var position = style.position;
|
|
18156
|
-
var isFixed = position === "fixed" || position === "sticky";
|
|
18157
|
-
var isAbsolute = position === "absolute";
|
|
18158
|
-
if (!isFixed && !isAbsolute) continue;
|
|
18159
|
-
if (isAbsolute && !hasFixedAncestor(el)) continue;
|
|
18160
|
-
if (effectiveZ(el) < 5) continue;
|
|
18161
|
-
var rect = el.getBoundingClientRect();
|
|
18162
|
-
var areaRatio = (rect.width * rect.height) / vpArea;
|
|
18163
|
-
if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
|
|
18164
|
-
hit = el;
|
|
18165
|
-
break;
|
|
18166
|
-
}
|
|
18167
|
-
}
|
|
18168
|
-
}
|
|
18169
|
-
|
|
18170
|
-
if (!hit) return { found: false, label: "", cartLike: false };
|
|
18171
|
-
var text = (hit.textContent || "").slice(0, 800).toLowerCase();
|
|
18172
|
-
var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
|
|
18173
|
-
return text.indexOf(signal) !== -1;
|
|
18174
|
-
});
|
|
18175
|
-
var heading = hit.querySelector("h1,h2,h3,h4");
|
|
18176
|
-
var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
|
|
18177
|
-
return { found: true, label: label, cartLike: cartLike };
|
|
18178
|
-
})()
|
|
18179
|
-
`);
|
|
18180
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18181
|
-
return null;
|
|
18182
|
-
}
|
|
18183
|
-
const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
|
|
18184
|
-
if ("cartLike" in result && result.cartLike) {
|
|
18185
|
-
return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
|
|
18186
|
-
}
|
|
18187
|
-
return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
|
|
18188
|
-
}
|
|
18189
|
-
async function dismissPopup(wc) {
|
|
18190
|
-
const before = await extractContent(wc);
|
|
18191
|
-
const initialBlocking = before.overlays.filter(
|
|
18192
|
-
(overlay) => overlay.blocksInteraction
|
|
18193
|
-
).length;
|
|
18194
|
-
if (initialBlocking > 0) {
|
|
18195
|
-
const overlayText = before.overlays.map(
|
|
18196
|
-
(o) => [o.label, o.text].filter(Boolean).join(" ")
|
|
18197
|
-
).join(" ").toLowerCase();
|
|
18198
|
-
const cartSignals = [
|
|
18199
|
-
"added to cart",
|
|
18200
|
-
"added to bag",
|
|
18201
|
-
"added to basket",
|
|
18202
|
-
"item added",
|
|
18203
|
-
"items in your basket",
|
|
18204
|
-
"items in your cart",
|
|
18205
|
-
"items in your bag",
|
|
18206
|
-
"your basket",
|
|
18207
|
-
"your cart",
|
|
18208
|
-
"your bag",
|
|
18209
|
-
"view basket",
|
|
18210
|
-
"view cart",
|
|
18211
|
-
"continue shopping"
|
|
18212
|
-
];
|
|
18213
|
-
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
18214
|
-
const continueResult = await wc.executeJavaScript(`
|
|
18215
|
-
(function() {
|
|
18216
|
-
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
18217
|
-
if (!dialog) return "Error: dialog not found";
|
|
18218
|
-
var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
|
|
18219
|
-
var continueBtn = null;
|
|
18220
|
-
var viewCartBtn = null;
|
|
18221
|
-
for (var i = 0; i < buttons.length; i++) {
|
|
18222
|
-
var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
|
|
18223
|
-
if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
|
|
18224
|
-
if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
|
|
18225
|
-
}
|
|
18226
|
-
var target = continueBtn || viewCartBtn;
|
|
18227
|
-
if (!target) return "Error: no dialog action found";
|
|
18228
|
-
var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
|
|
18229
|
-
if (target.tagName === 'A' && target.href) {
|
|
18230
|
-
window.location.href = target.href;
|
|
18231
|
-
return "Clicked: " + actionLabel + " -> " + target.href;
|
|
18232
|
-
}
|
|
18233
|
-
target.click();
|
|
18234
|
-
return "Clicked: " + actionLabel;
|
|
18235
|
-
})()
|
|
18236
|
-
`);
|
|
18237
|
-
if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
18238
|
-
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
18239
|
-
}
|
|
18240
|
-
return "Cannot dismiss: this is a cart confirmation dialog. Item is in your cart. Use read_page to see dialog actions (e.g. View Basket, Continue Shopping) and click one of them instead.";
|
|
18241
|
-
}
|
|
18242
|
-
}
|
|
18243
|
-
const initialDormant = before.dormantOverlays.length;
|
|
18244
|
-
const candidates = await wc.executeJavaScript(`
|
|
18245
|
-
(function() {
|
|
18246
|
-
function text(value) {
|
|
18247
|
-
const trimmed = value == null ? "" : String(value).trim();
|
|
18248
|
-
return trimmed || "";
|
|
18249
|
-
}
|
|
18250
|
-
|
|
18251
|
-
${selectorHelpersJS(["data-testid", "data-test", "aria-label", "name", "title"])}
|
|
18252
|
-
|
|
18253
|
-
function isVisible(el) {
|
|
18254
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
18255
|
-
const style = window.getComputedStyle(el);
|
|
18256
|
-
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
18257
|
-
return false;
|
|
18258
|
-
}
|
|
18259
|
-
if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
|
|
18260
|
-
return false;
|
|
18261
|
-
}
|
|
18262
|
-
const rect = el.getBoundingClientRect();
|
|
18263
|
-
return rect.width > 0 && rect.height > 0;
|
|
18264
|
-
}
|
|
18265
|
-
|
|
18266
|
-
function overlayRoots() {
|
|
18267
|
-
const nodes = [];
|
|
18268
|
-
document.querySelectorAll("dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']").forEach((el) => {
|
|
18269
|
-
if (isVisible(el)) nodes.push(el);
|
|
18270
|
-
});
|
|
18271
|
-
// Detect known consent manager containers
|
|
18272
|
-
document.querySelectorAll("#onetrust-consent-sdk, #onetrust-banner-sdk, [id*='onetrust'], [class*='onetrust'], #CybotCookiebotDialog, #truste-consent-track, [id*='cookie-banner'], [id*='consent-banner'], [class*='cookie-consent'], [class*='consent-banner'], [id*='gdpr'], [class*='gdpr']").forEach((el) => {
|
|
18273
|
-
if (el instanceof HTMLElement && isVisible(el)) nodes.push(el);
|
|
18274
|
-
});
|
|
18275
|
-
document.querySelectorAll("body *").forEach((el) => {
|
|
18276
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18277
|
-
const style = window.getComputedStyle(el);
|
|
18278
|
-
const rect = el.getBoundingClientRect();
|
|
18279
|
-
const zIndex = Number.parseInt(style.zIndex, 10);
|
|
18280
|
-
const coversCenter =
|
|
18281
|
-
rect.left <= (window.innerWidth || 0) / 2 &&
|
|
18282
|
-
rect.right >= (window.innerWidth || 0) / 2 &&
|
|
18283
|
-
rect.top <= (window.innerHeight || 0) / 2 &&
|
|
18284
|
-
rect.bottom >= (window.innerHeight || 0) / 2;
|
|
18285
|
-
if (
|
|
18286
|
-
(style.position === "fixed" || style.position === "sticky") &&
|
|
18287
|
-
Number.isFinite(zIndex) &&
|
|
18288
|
-
zIndex >= 10 &&
|
|
18289
|
-
coversCenter
|
|
18290
|
-
) {
|
|
18291
|
-
nodes.push(el);
|
|
18292
|
-
}
|
|
18293
|
-
});
|
|
18294
|
-
return Array.from(new Set(nodes));
|
|
18295
|
-
}
|
|
18296
|
-
|
|
18297
|
-
function scoreCandidate(el, rooted) {
|
|
18298
|
-
const label = text(
|
|
18299
|
-
el.getAttribute("aria-label") ||
|
|
18300
|
-
el.getAttribute("title") ||
|
|
18301
|
-
el.textContent ||
|
|
18302
|
-
el.getAttribute("value"),
|
|
18303
|
-
).toLowerCase();
|
|
18304
|
-
const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18305
|
-
const idText = text(el.id).toLowerCase();
|
|
18306
|
-
const combined = classText + " " + idText;
|
|
18307
|
-
let score = rooted ? 30 : 0;
|
|
18308
|
-
if (/^x$|^×$/.test(label)) score += 120;
|
|
18309
|
-
if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
|
|
18310
|
-
if (/close|dismiss|modal-close|overlay-close/.test(combined)) score += 90;
|
|
18311
|
-
if (/onetrust-close|onetrust-reject|cookie.*close|consent.*close|cookie.*reject|consent.*reject/.test(combined)) score += 110;
|
|
18312
|
-
if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
|
|
18313
|
-
if (el.getAttribute("aria-label")) score += 20;
|
|
18314
|
-
if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
|
|
18315
|
-
const rect = el.getBoundingClientRect();
|
|
18316
|
-
if (rect.top < 120) score += 10;
|
|
18317
|
-
if (rect.right > (window.innerWidth || 0) - 120) score += 15;
|
|
18318
|
-
return score;
|
|
18319
|
-
}
|
|
18320
|
-
|
|
18321
|
-
const selector = "button, [role='button'], a[href], input[type='button'], input[type='submit'], [aria-label], [title]";
|
|
18322
|
-
const results = [];
|
|
18323
|
-
const roots = overlayRoots();
|
|
18324
|
-
|
|
18325
|
-
function collect(container, rooted) {
|
|
18326
|
-
container.querySelectorAll(selector).forEach((el) => {
|
|
18327
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18328
|
-
const candidateSelector = selectorFor(el);
|
|
18329
|
-
if (!candidateSelector) return;
|
|
18330
|
-
var label = text(
|
|
18331
|
-
el.getAttribute("aria-label") ||
|
|
18332
|
-
el.getAttribute("title") ||
|
|
18333
|
-
el.textContent ||
|
|
18334
|
-
el.getAttribute("value"),
|
|
18335
|
-
);
|
|
18336
|
-
if (!label) {
|
|
18337
|
-
var idLower = (el.id || "").toLowerCase();
|
|
18338
|
-
var classLower = (typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18339
|
-
var combined = idLower + " " + classLower;
|
|
18340
|
-
if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(combined)) {
|
|
18341
|
-
label = idLower.includes("accept") ? "Accept cookies"
|
|
18342
|
-
: idLower.includes("reject") ? "Reject cookies"
|
|
18343
|
-
: idLower.includes("close") || classLower.includes("close") ? "Close"
|
|
18344
|
-
: "Consent button";
|
|
18345
|
-
} else {
|
|
18346
|
-
return;
|
|
18347
|
-
}
|
|
18348
|
-
}
|
|
18349
|
-
results.push({
|
|
18350
|
-
selector: candidateSelector,
|
|
18351
|
-
label: label.slice(0, 120),
|
|
18352
|
-
score: scoreCandidate(el, rooted),
|
|
18353
|
-
});
|
|
18354
|
-
});
|
|
18355
|
-
}
|
|
18356
|
-
|
|
18357
|
-
roots.forEach((root) => collect(root, true));
|
|
18358
|
-
if (results.length === 0) {
|
|
18359
|
-
collect(document, false);
|
|
18360
|
-
}
|
|
18361
|
-
|
|
18362
|
-
const seen = new Set();
|
|
18363
|
-
return results
|
|
18364
|
-
.filter((candidate) => {
|
|
18365
|
-
if (seen.has(candidate.selector)) return false;
|
|
18366
|
-
seen.add(candidate.selector);
|
|
18367
|
-
return candidate.score > 0;
|
|
18368
|
-
})
|
|
18369
|
-
.sort((a, b) => b.score - a.score)
|
|
18370
|
-
.slice(0, 8);
|
|
18371
|
-
})()
|
|
18372
|
-
`);
|
|
18373
|
-
if (Array.isArray(candidates)) {
|
|
18374
|
-
for (const candidate of candidates) {
|
|
18375
|
-
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
18376
|
-
continue;
|
|
18377
|
-
}
|
|
18378
|
-
const result = await clickElement(wc, candidate.selector);
|
|
18379
|
-
if (result.startsWith("Error:")) continue;
|
|
18380
|
-
await sleep(250);
|
|
18381
|
-
const after = await extractContent(wc);
|
|
18382
|
-
const blocking = after.overlays.filter(
|
|
18383
|
-
(overlay) => overlay.blocksInteraction
|
|
18384
|
-
).length;
|
|
18385
|
-
if (blocking < initialBlocking || initialBlocking > 0 && blocking === 0) {
|
|
18386
|
-
const label = typeof candidate.label === "string" && candidate.label ? candidate.label : "popup control";
|
|
18387
|
-
return `Dismissed popup using "${label}"`;
|
|
18388
|
-
}
|
|
18389
|
-
}
|
|
18390
|
-
}
|
|
18391
|
-
wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
|
|
18392
|
-
await sleep(16);
|
|
18393
|
-
wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
|
|
18394
|
-
await sleep(200);
|
|
18395
|
-
const afterEscape = await extractContent(wc);
|
|
18396
|
-
const escapeBlocking = afterEscape.overlays.filter(
|
|
18397
|
-
(overlay) => overlay.blocksInteraction
|
|
18398
|
-
).length;
|
|
18399
|
-
if (escapeBlocking < initialBlocking || initialBlocking > 0 && escapeBlocking === 0) {
|
|
18400
|
-
return "Dismissed popup with Escape";
|
|
18401
|
-
}
|
|
18402
|
-
return initialBlocking > 0 ? "Could not dismiss the blocking popup automatically" : initialDormant > 0 ? `No active blocking popup detected. Found ${initialDormant} dormant consent/modal surface(s) in the DOM, likely geo-gated or inactive in this session.` : "No blocking popup detected";
|
|
18403
|
-
}
|
|
18404
|
-
function isDangerousAction(name) {
|
|
18405
|
-
return [
|
|
18406
|
-
"navigate",
|
|
18407
|
-
"click",
|
|
18408
|
-
"type",
|
|
18409
|
-
"select_option",
|
|
18410
|
-
"submit_form",
|
|
18411
|
-
"press_key",
|
|
18412
|
-
"create_tab",
|
|
18413
|
-
"switch_tab",
|
|
18414
|
-
"close_tab",
|
|
18415
|
-
"restore_checkpoint",
|
|
18416
|
-
"login",
|
|
18417
|
-
"fill_form",
|
|
18418
|
-
"search",
|
|
18419
|
-
"paginate"
|
|
18420
|
-
].includes(name);
|
|
18421
|
-
}
|
|
18422
|
-
function getTabByMatch(tabManager, match) {
|
|
18423
|
-
const lowered = match.toLowerCase();
|
|
18424
|
-
return tabManager.getAllStates().find(
|
|
18425
|
-
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
18426
|
-
) || null;
|
|
18427
|
-
}
|
|
18428
|
-
async function getPostActionState(tabManager, name) {
|
|
18429
|
-
const tab = tabManager.getActiveTab();
|
|
18430
|
-
if (!tab) return "";
|
|
18431
|
-
const wc = tab.view.webContents;
|
|
18432
|
-
const navActions = [
|
|
18433
|
-
"navigate",
|
|
18434
|
-
"go_back",
|
|
18435
|
-
"go_forward",
|
|
18436
|
-
"click",
|
|
18437
|
-
"submit_form",
|
|
18438
|
-
"reload",
|
|
18439
|
-
"press_key"
|
|
18440
|
-
];
|
|
18441
|
-
const interactActions = [
|
|
18442
|
-
"type",
|
|
18443
|
-
"type_text",
|
|
18444
|
-
"select_option",
|
|
18445
|
-
"hover",
|
|
18446
|
-
"focus"
|
|
18447
|
-
];
|
|
18448
|
-
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
18449
|
-
if (navActions.includes(name)) {
|
|
18450
|
-
let warning = "";
|
|
18451
|
-
try {
|
|
18452
|
-
const page = await extractContent(wc);
|
|
18453
|
-
const issue = getRecoverableAccessIssue(page);
|
|
18454
|
-
if (issue) {
|
|
18455
|
-
const blockedUrl = wc.getURL();
|
|
18456
|
-
const canRecover = [
|
|
18457
|
-
"navigate",
|
|
18458
|
-
"open_bookmark",
|
|
18459
|
-
"click",
|
|
18460
|
-
"submit_form",
|
|
18461
|
-
"reload",
|
|
18462
|
-
"press_key"
|
|
18463
|
-
].includes(name) && tab.canGoBack();
|
|
18464
|
-
if (canRecover && tab.goBack()) {
|
|
18465
|
-
await waitForLoad(wc);
|
|
18466
|
-
warning = `
|
|
18467
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
18468
|
-
} else {
|
|
18469
|
-
warning = `
|
|
18470
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
18471
|
-
}
|
|
18472
|
-
}
|
|
18473
|
-
} catch {
|
|
18474
|
-
}
|
|
18475
|
-
return `${warning}
|
|
18476
|
-
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
18477
|
-
}
|
|
18478
|
-
if (interactActions.includes(name)) {
|
|
18479
|
-
return `
|
|
18480
|
-
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
18481
|
-
}
|
|
18482
|
-
if (tabActions.includes(name)) {
|
|
18483
|
-
const activeId = tabManager.getActiveTabId();
|
|
18484
|
-
const active = getActiveTabSummary(tabManager);
|
|
18485
|
-
const count = tabManager.getAllStates().length;
|
|
18486
|
-
return `
|
|
18487
|
-
[state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
|
|
18488
|
-
}
|
|
18489
|
-
return "";
|
|
18490
|
-
}
|
|
18491
|
-
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
18492
|
-
try {
|
|
18493
|
-
const result = await runtime2.runControlledAction({
|
|
18494
|
-
source: "mcp",
|
|
18495
|
-
name,
|
|
18496
|
-
args,
|
|
18497
|
-
tabId: tabManager.getActiveTabId(),
|
|
18498
|
-
dangerous: isDangerousAction(name),
|
|
18499
|
-
executor
|
|
18500
|
-
});
|
|
18501
|
-
const stateInfo = await getPostActionState(tabManager, name);
|
|
18502
|
-
const flowCtx = runtime2.getFlowContext();
|
|
18503
|
-
return asTextResponse(result + stateInfo + flowCtx);
|
|
18504
|
-
} catch (error) {
|
|
18505
|
-
return asTextResponse(
|
|
18506
|
-
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
18507
|
-
);
|
|
18508
|
-
}
|
|
18509
|
-
}
|
|
18510
|
-
async function setElementValue(wc, selector, value) {
|
|
18511
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
18512
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
18513
|
-
return wc.executeJavaScript(
|
|
18514
|
-
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
18515
|
-
);
|
|
18516
|
-
}
|
|
18517
|
-
if (selector.includes(" >>> ")) {
|
|
18518
|
-
return wc.executeJavaScript(`
|
|
18519
|
-
(function() {
|
|
18520
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
18521
|
-
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
18522
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
|
|
18523
|
-
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18524
|
-
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
18525
|
-
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
18526
|
-
el.focus();
|
|
18527
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
18528
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
18529
|
-
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
18530
|
-
})()
|
|
18531
|
-
`);
|
|
18532
|
-
}
|
|
18533
|
-
return wc.executeJavaScript(`
|
|
18534
|
-
(function() {
|
|
18535
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18536
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18537
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18538
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18539
|
-
}
|
|
18540
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18541
|
-
return 'Error[disabled]: Input is disabled';
|
|
18542
|
-
}
|
|
18543
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18544
|
-
? HTMLTextAreaElement.prototype
|
|
18545
|
-
: HTMLInputElement.prototype;
|
|
18546
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18547
|
-
if (descriptor && descriptor.set) {
|
|
18548
|
-
descriptor.set.call(el, ${JSON.stringify(value)});
|
|
18549
|
-
} else {
|
|
18550
|
-
el.value = ${JSON.stringify(value)};
|
|
18551
|
-
}
|
|
18552
|
-
el.focus();
|
|
18553
|
-
el.dispatchEvent(new InputEvent('input', {
|
|
18554
|
-
bubbles: true,
|
|
18555
|
-
cancelable: true,
|
|
18556
|
-
data: ${JSON.stringify(value)},
|
|
18557
|
-
inputType: 'insertText',
|
|
18558
|
-
}));
|
|
18559
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18560
|
-
return 'Typed into: ' +
|
|
18561
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18562
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18563
|
-
})()
|
|
18564
|
-
`);
|
|
18565
|
-
}
|
|
18566
|
-
async function typeKeystroke(wc, selector, value) {
|
|
18567
|
-
return wc.executeJavaScript(`
|
|
18568
|
-
(async function() {
|
|
18569
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18570
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18571
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18572
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18573
|
-
}
|
|
18574
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18575
|
-
return 'Error[disabled]: Input is disabled';
|
|
18576
|
-
}
|
|
18577
|
-
el.focus();
|
|
18578
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18579
|
-
? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18580
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18581
|
-
if (descriptor && descriptor.set) {
|
|
18582
|
-
descriptor.set.call(el, '');
|
|
18583
|
-
} else {
|
|
18584
|
-
el.value = '';
|
|
18585
|
-
}
|
|
18586
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: '', inputType: 'deleteContentBackward' }));
|
|
18587
|
-
const chars = ${JSON.stringify(value)}.split('');
|
|
18588
|
-
for (const ch of chars) {
|
|
18589
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true, cancelable: true }));
|
|
18590
|
-
el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true, cancelable: true }));
|
|
18591
|
-
if (descriptor && descriptor.set) {
|
|
18592
|
-
descriptor.set.call(el, el.value + ch);
|
|
18593
|
-
} else {
|
|
18594
|
-
el.value += ch;
|
|
18595
|
-
}
|
|
18596
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: ch, inputType: 'insertText' }));
|
|
18597
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true, cancelable: true }));
|
|
18598
|
-
}
|
|
18599
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18600
|
-
return 'Typed into: ' +
|
|
18601
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18602
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18603
|
-
})()
|
|
18604
|
-
`);
|
|
18605
|
-
}
|
|
18606
|
-
async function hoverElement(wc, selector) {
|
|
18607
|
-
const pos = await wc.executeJavaScript(`
|
|
18608
|
-
(function() {
|
|
18609
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18610
|
-
if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
|
|
18611
|
-
if (el instanceof HTMLElement) {
|
|
18612
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
|
|
18613
|
-
}
|
|
18614
|
-
const rect = el.getBoundingClientRect();
|
|
18615
|
-
if (rect.width <= 0 || rect.height <= 0) return { error: 'Error[hidden]: Element has no visible area. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it then call read_page to refresh visible elements.' };
|
|
18616
|
-
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
|
|
18617
|
-
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
|
|
18618
|
-
const label = (el.textContent || el.tagName || 'Element').trim().slice(0, 80);
|
|
18619
|
-
return {
|
|
18620
|
-
x: Math.round(rect.left + rect.width / 2),
|
|
18621
|
-
y: Math.round(rect.top + rect.height / 2),
|
|
18622
|
-
label: label,
|
|
18623
|
-
};
|
|
18624
|
-
})()
|
|
18625
|
-
`);
|
|
18626
|
-
if (!pos || typeof pos !== "object") return "Error: Could not hover element";
|
|
18627
|
-
if ("error" in pos && typeof pos.error === "string") return pos.error;
|
|
18628
|
-
const x = typeof pos.x === "number" ? pos.x : null;
|
|
18629
|
-
const y = typeof pos.y === "number" ? pos.y : null;
|
|
18630
|
-
if (x == null || y == null)
|
|
18631
|
-
return "Error: Could not resolve hover coordinates";
|
|
18632
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
18633
|
-
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
18634
|
-
return `Hovered: ${label}`;
|
|
18635
|
-
}
|
|
18636
|
-
async function focusElement(wc, selector) {
|
|
18637
|
-
return wc.executeJavaScript(`
|
|
18638
|
-
(function() {
|
|
18639
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18640
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18641
|
-
if (!(el instanceof HTMLElement)) return 'Error[not-interactive]: Element is not focusable';
|
|
18642
|
-
if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
|
|
18643
|
-
return 'Error[disabled]: Element is disabled';
|
|
18644
|
-
}
|
|
18645
|
-
el.focus({ preventScroll: false });
|
|
18646
|
-
return 'Focused: ' + (el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
18647
|
-
})()
|
|
18648
|
-
`);
|
|
18649
|
-
}
|
|
18650
|
-
async function selectOption(wc, index, selector, label, value) {
|
|
18651
|
-
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18652
|
-
if (!resolvedSelector)
|
|
18653
|
-
return "Error: No select element index or selector provided";
|
|
18654
|
-
return wc.executeJavaScript(`
|
|
18655
|
-
(function() {
|
|
18656
|
-
const el = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18657
|
-
if (!(el instanceof HTMLSelectElement)) {
|
|
18658
|
-
return 'Element is not a select dropdown';
|
|
18659
|
-
}
|
|
18660
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18661
|
-
return 'Select is disabled';
|
|
18662
|
-
}
|
|
18663
|
-
const requestedLabel = ${JSON.stringify(label || "")}.trim().toLowerCase();
|
|
18664
|
-
const requestedValue = ${JSON.stringify(value || "")}.trim();
|
|
18665
|
-
const option = Array.from(el.options).find((item) => {
|
|
18666
|
-
const optionLabel = (item.textContent || '').trim().toLowerCase();
|
|
18667
|
-
return (requestedLabel && optionLabel === requestedLabel) ||
|
|
18668
|
-
(requestedValue && item.value === requestedValue);
|
|
18669
|
-
});
|
|
18670
|
-
if (!option) return 'Option not found';
|
|
18671
|
-
el.value = option.value;
|
|
18672
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
18673
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18674
|
-
return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
|
|
18675
|
-
})()
|
|
18676
|
-
`);
|
|
18677
|
-
}
|
|
18678
|
-
async function submitForm(wc, index, selector) {
|
|
18679
|
-
const beforeUrl = wc.getURL();
|
|
18680
|
-
let resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18681
|
-
if (!resolvedSelector) {
|
|
18682
|
-
resolvedSelector = await wc.executeJavaScript(`
|
|
18683
|
-
(function() {
|
|
18684
|
-
var forms = document.querySelectorAll('form');
|
|
18685
|
-
for (var i = 0; i < forms.length; i++) {
|
|
18686
|
-
var f = forms[i];
|
|
18687
|
-
var rect = f.getBoundingClientRect();
|
|
18688
|
-
if (rect.width > 0 && rect.height > 0) return 'form';
|
|
18689
|
-
}
|
|
18690
|
-
return forms.length > 0 ? 'form' : null;
|
|
18691
|
-
})()
|
|
18692
|
-
`);
|
|
18693
|
-
if (!resolvedSelector) return "Error: No form found on the page";
|
|
18694
|
-
}
|
|
18695
|
-
const formInfo = await wc.executeJavaScript(`
|
|
18696
|
-
(function() {
|
|
18697
|
-
const target = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18698
|
-
if (!target) return { error: 'Target not found' };
|
|
18699
|
-
// Find the form: nested, or linked via form="id" attribute
|
|
18700
|
-
var form = target instanceof HTMLFormElement ? target : target.closest('form');
|
|
18701
|
-
if (!form) {
|
|
18702
|
-
const formId = target.getAttribute('form');
|
|
18703
|
-
if (formId) {
|
|
18704
|
-
const linked = document.getElementById(formId);
|
|
18705
|
-
if (linked instanceof HTMLFormElement) form = linked;
|
|
18706
|
-
}
|
|
18707
|
-
}
|
|
18708
|
-
if (!form) return { error: 'No parent form found' };
|
|
18709
|
-
function isSubmitControl(el) {
|
|
18710
|
-
return (
|
|
18711
|
-
(el instanceof HTMLButtonElement &&
|
|
18712
|
-
((el.getAttribute('type') || '').trim().toLowerCase() === '' ||
|
|
18713
|
-
el.type === 'submit')) ||
|
|
18714
|
-
(el instanceof HTMLInputElement &&
|
|
18715
|
-
(el.type === 'submit' || el.type === 'image'))
|
|
18716
|
-
);
|
|
18717
|
-
}
|
|
18718
|
-
const submitter = isSubmitControl(target)
|
|
18719
|
-
? target
|
|
18720
|
-
: Array.from(document.querySelectorAll('button, input[type="submit"], input[type="image"]')).find(
|
|
18721
|
-
(candidate) => isSubmitControl(candidate) && candidate.form === form,
|
|
18722
|
-
);
|
|
18723
|
-
if (
|
|
18724
|
-
submitter instanceof HTMLElement &&
|
|
18725
|
-
(submitter.hasAttribute('disabled') ||
|
|
18726
|
-
submitter.getAttribute('aria-disabled') === 'true')
|
|
18727
|
-
) {
|
|
18728
|
-
return { error: 'Submit control is disabled' };
|
|
18729
|
-
}
|
|
18730
|
-
// Collect form data and determine method
|
|
18731
|
-
const submitterActionAttr =
|
|
18732
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18733
|
-
submitter instanceof HTMLInputElement
|
|
18734
|
-
? submitter.getAttribute('formaction')?.trim()
|
|
18735
|
-
: '') || '';
|
|
18736
|
-
const action = submitterActionAttr
|
|
18737
|
-
? new URL(submitterActionAttr, document.baseURI).toString()
|
|
18738
|
-
: form.action || window.location.href;
|
|
18739
|
-
const submitterMethodAttr =
|
|
18740
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18741
|
-
submitter instanceof HTMLInputElement
|
|
18742
|
-
? submitter.getAttribute('formmethod')?.trim()
|
|
18743
|
-
: '') || '';
|
|
18744
|
-
const method = (
|
|
18745
|
-
submitterMethodAttr ||
|
|
18746
|
-
form.getAttribute('method') ||
|
|
18747
|
-
form.method ||
|
|
18748
|
-
'GET'
|
|
18749
|
-
).toUpperCase();
|
|
18750
|
-
let fd;
|
|
18751
|
-
try {
|
|
18752
|
-
fd = submitter instanceof HTMLElement
|
|
18753
|
-
? new FormData(form, submitter)
|
|
18754
|
-
: new FormData(form);
|
|
18755
|
-
} catch {
|
|
18756
|
-
fd = new FormData(form);
|
|
18757
|
-
}
|
|
18758
|
-
const params = new URLSearchParams();
|
|
18759
|
-
for (const [k, v] of fd.entries()) {
|
|
18760
|
-
if (typeof v === 'string') params.append(k, v);
|
|
18761
|
-
}
|
|
18762
|
-
// Use requestSubmit to fire JS submit handlers for all methods
|
|
18763
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18764
|
-
try {
|
|
18765
|
-
if (
|
|
18766
|
-
submitter instanceof HTMLButtonElement ||
|
|
18767
|
-
submitter instanceof HTMLInputElement
|
|
18768
|
-
) {
|
|
18769
|
-
form.requestSubmit(submitter);
|
|
18770
|
-
} else {
|
|
18771
|
-
form.requestSubmit();
|
|
18772
|
-
}
|
|
18773
|
-
} catch {
|
|
18774
|
-
form.requestSubmit();
|
|
18775
|
-
}
|
|
18776
|
-
return { submitted: true, method };
|
|
18777
|
-
}
|
|
18778
|
-
if (submitter instanceof HTMLElement && typeof submitter.click === 'function') {
|
|
18779
|
-
submitter.click();
|
|
18780
|
-
return { submitted: true, method };
|
|
18781
|
-
}
|
|
18782
|
-
// Last resort: form.submit() bypasses JS handlers but at least submits
|
|
18783
|
-
if (method === 'GET') {
|
|
18784
|
-
return { action, method, params: params.toString(), found: true };
|
|
18785
|
-
}
|
|
18786
|
-
form.submit();
|
|
18787
|
-
return { submitted: true, method };
|
|
18788
|
-
})()
|
|
18789
|
-
`);
|
|
18790
|
-
if (formInfo.error) return formInfo.error;
|
|
18791
|
-
if (formInfo.found && formInfo.method === "GET") {
|
|
18792
|
-
const url = new URL(formInfo.action);
|
|
18793
|
-
if (formInfo.params) {
|
|
18794
|
-
url.search = formInfo.params;
|
|
18795
|
-
}
|
|
18796
|
-
await loadPermittedUrl(wc, url.toString());
|
|
18797
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18798
|
-
const afterUrl = wc.getURL();
|
|
18799
|
-
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
18800
|
-
}
|
|
18801
|
-
if (formInfo.submitted) {
|
|
18802
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18803
|
-
const afterUrl = wc.getURL();
|
|
18804
|
-
return afterUrl !== beforeUrl ? `Submitted form via ${formInfo.method} -> ${afterUrl}` : `Submitted form via ${formInfo.method}`;
|
|
17791
|
+
if (tabActions.includes(name)) {
|
|
17792
|
+
const activeId = tabManager.getActiveTabId();
|
|
17793
|
+
const active = getActiveTabSummary(tabManager);
|
|
17794
|
+
const count = tabManager.getAllStates().length;
|
|
17795
|
+
return `
|
|
17796
|
+
[state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
|
|
18805
17797
|
}
|
|
18806
|
-
return "
|
|
17798
|
+
return "";
|
|
18807
17799
|
}
|
|
18808
|
-
async function
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
if (key === 'Enter') {
|
|
18825
|
-
if (tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button'))) {
|
|
18826
|
-
target.click();
|
|
18827
|
-
} else if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
18828
|
-
const form = target.closest('form');
|
|
18829
|
-
if (form) {
|
|
18830
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18831
|
-
form.requestSubmit();
|
|
18832
|
-
} else {
|
|
18833
|
-
const submitBtn = form.querySelector('[type="submit"]');
|
|
18834
|
-
if (submitBtn) submitBtn.click();
|
|
18835
|
-
else form.submit();
|
|
18836
|
-
}
|
|
18837
|
-
}
|
|
18838
|
-
}
|
|
18839
|
-
}
|
|
18840
|
-
target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
|
|
18841
|
-
return 'Pressed key: ' + key;
|
|
18842
|
-
})()
|
|
18843
|
-
`);
|
|
17800
|
+
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
17801
|
+
try {
|
|
17802
|
+
const result = await runtime2.runControlledAction({
|
|
17803
|
+
source: "mcp",
|
|
17804
|
+
name,
|
|
17805
|
+
args,
|
|
17806
|
+
tabId: tabManager.getActiveTabId(),
|
|
17807
|
+
dangerous: isDangerousMcpAction(name),
|
|
17808
|
+
executor
|
|
17809
|
+
});
|
|
17810
|
+
const stateInfo = await getPostActionState(tabManager, name);
|
|
17811
|
+
const flowCtx = runtime2.getFlowContext();
|
|
17812
|
+
return asTextResponse(result + stateInfo + flowCtx);
|
|
17813
|
+
} catch (error) {
|
|
17814
|
+
return asErrorTextResponse(getErrorMessage(error));
|
|
17815
|
+
}
|
|
18844
17816
|
}
|
|
18845
|
-
async function
|
|
17817
|
+
async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
18846
17818
|
const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
|
|
18847
17819
|
const expectedText = (text || "").trim();
|
|
18848
17820
|
const expectedSelector = (selector || "").trim();
|
|
18849
|
-
|
|
17821
|
+
const startedAt = Date.now();
|
|
17822
|
+
const result = await waitForConditionDirect(
|
|
17823
|
+
wc,
|
|
17824
|
+
expectedText,
|
|
17825
|
+
expectedSelector,
|
|
17826
|
+
effectiveTimeout
|
|
17827
|
+
);
|
|
17828
|
+
const elapsedMs = Date.now() - startedAt;
|
|
17829
|
+
if (result === "Error: wait_for requires text or selector") {
|
|
18850
17830
|
return JSON.stringify({
|
|
18851
17831
|
matched: false,
|
|
18852
17832
|
error: "wait_for requires text or selector"
|
|
18853
17833
|
});
|
|
18854
17834
|
}
|
|
18855
|
-
if (
|
|
18856
|
-
|
|
17835
|
+
if (result.startsWith("Error: Invalid selector ")) {
|
|
17836
|
+
return JSON.stringify({
|
|
17837
|
+
matched: false,
|
|
17838
|
+
error: result.slice("Error: ".length)
|
|
17839
|
+
});
|
|
18857
17840
|
}
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
17841
|
+
if (result.startsWith("Error: Page is still busy; wait_for timed out")) {
|
|
17842
|
+
return JSON.stringify({
|
|
17843
|
+
matched: false,
|
|
17844
|
+
error: result.slice("Error: ".length),
|
|
17845
|
+
elapsed_ms: elapsedMs,
|
|
17846
|
+
timeout_ms: effectiveTimeout
|
|
17847
|
+
});
|
|
17848
|
+
}
|
|
17849
|
+
if (expectedSelector && result === `Matched selector ${expectedSelector}`) {
|
|
17850
|
+
return JSON.stringify({
|
|
17851
|
+
matched: true,
|
|
17852
|
+
type: "selector",
|
|
17853
|
+
value: expectedSelector,
|
|
17854
|
+
elapsed_ms: elapsedMs
|
|
17855
|
+
});
|
|
17856
|
+
}
|
|
17857
|
+
const matchedTextPrefix = 'Matched text "';
|
|
17858
|
+
if (result.startsWith(matchedTextPrefix) && result.endsWith('"')) {
|
|
17859
|
+
return JSON.stringify({
|
|
17860
|
+
matched: true,
|
|
17861
|
+
type: "text",
|
|
17862
|
+
value: result.slice(matchedTextPrefix.length, -1),
|
|
17863
|
+
elapsed_ms: elapsedMs
|
|
17864
|
+
});
|
|
17865
|
+
}
|
|
17866
|
+
const timeoutPayload = {
|
|
17867
|
+
matched: false,
|
|
17868
|
+
type: expectedSelector ? "selector" : "text",
|
|
17869
|
+
value: expectedSelector || expectedText.slice(0, 80),
|
|
17870
|
+
elapsed_ms: elapsedMs,
|
|
17871
|
+
timeout_ms: effectiveTimeout
|
|
17872
|
+
};
|
|
17873
|
+
if (expectedSelector) {
|
|
17874
|
+
const diagnostic = await wc.executeJavaScript(`
|
|
18861
17875
|
(function() {
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
} catch (e) {
|
|
18868
|
-
return 'invalid_selector:' + e.message;
|
|
18869
|
-
}
|
|
17876
|
+
try {
|
|
17877
|
+
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
17878
|
+
return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
|
|
17879
|
+
} catch (e) {
|
|
17880
|
+
return 'selector error: ' + e.message;
|
|
18870
17881
|
}
|
|
18871
|
-
if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
|
|
18872
|
-
return '';
|
|
18873
17882
|
})()
|
|
18874
|
-
`)
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
value: expectedSelector,
|
|
18881
|
-
elapsed_ms: elapsedMs2
|
|
18882
|
-
});
|
|
18883
|
-
}
|
|
18884
|
-
if (result === "text") {
|
|
18885
|
-
return JSON.stringify({
|
|
18886
|
-
matched: true,
|
|
18887
|
-
type: "text",
|
|
18888
|
-
value: expectedText.slice(0, 80),
|
|
18889
|
-
elapsed_ms: elapsedMs2
|
|
18890
|
-
});
|
|
18891
|
-
}
|
|
18892
|
-
if (typeof result === "string" && result.startsWith("invalid_selector:")) {
|
|
18893
|
-
return JSON.stringify({
|
|
18894
|
-
matched: false,
|
|
18895
|
-
error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
|
|
18896
|
-
});
|
|
17883
|
+
`).catch((err) => {
|
|
17884
|
+
logger$7.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
17885
|
+
return null;
|
|
17886
|
+
});
|
|
17887
|
+
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
17888
|
+
timeoutPayload.diagnostic = diagnostic;
|
|
18897
17889
|
}
|
|
18898
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
18899
17890
|
}
|
|
18900
|
-
|
|
18901
|
-
const diagnostic = expectedSelector ? await wc.executeJavaScript(`
|
|
18902
|
-
(function() {
|
|
18903
|
-
try {
|
|
18904
|
-
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
18905
|
-
return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
|
|
18906
|
-
} catch (e) { return 'selector error: ' + e.message; }
|
|
18907
|
-
})()
|
|
18908
|
-
`) : null;
|
|
18909
|
-
return JSON.stringify({
|
|
18910
|
-
matched: false,
|
|
18911
|
-
type: expectedSelector ? "selector" : "text",
|
|
18912
|
-
value: expectedSelector ? expectedSelector : expectedText.slice(0, 80),
|
|
18913
|
-
elapsed_ms: elapsedMs,
|
|
18914
|
-
timeout_ms: effectiveTimeout,
|
|
18915
|
-
...diagnostic ? { diagnostic } : {}
|
|
18916
|
-
});
|
|
17891
|
+
return JSON.stringify(timeoutPayload);
|
|
18917
17892
|
}
|
|
18918
17893
|
function registerTools(server, tabManager, runtime2) {
|
|
18919
17894
|
server.registerPrompt(
|
|
@@ -18992,7 +17967,8 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
18992
17967
|
pageTitle = wc.getTitle();
|
|
18993
17968
|
const page = await extractContent(wc);
|
|
18994
17969
|
pageType = detectPageType(page);
|
|
18995
|
-
} catch {
|
|
17970
|
+
} catch (err) {
|
|
17971
|
+
logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
18996
17972
|
}
|
|
18997
17973
|
}
|
|
18998
17974
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -19044,7 +18020,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19044
18020
|
},
|
|
19045
18021
|
async () => {
|
|
19046
18022
|
const activeTab = getActiveTabSummary(tabManager);
|
|
19047
|
-
if (!activeTab) return
|
|
18023
|
+
if (!activeTab) return asNoActiveTabResponse();
|
|
19048
18024
|
return asTextResponse(JSON.stringify(activeTab, null, 2));
|
|
19049
18025
|
}
|
|
19050
18026
|
);
|
|
@@ -19153,7 +18129,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19153
18129
|
},
|
|
19154
18130
|
async ({ mode }) => {
|
|
19155
18131
|
const tab = tabManager.getActiveTab();
|
|
19156
|
-
if (!tab) return
|
|
18132
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19157
18133
|
try {
|
|
19158
18134
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19159
18135
|
const effectiveMode = mode || "full";
|
|
@@ -19185,7 +18161,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19185
18161
|
},
|
|
19186
18162
|
async ({ mode }) => {
|
|
19187
18163
|
const tab = tabManager.getActiveTab();
|
|
19188
|
-
if (!tab) return
|
|
18164
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19189
18165
|
try {
|
|
19190
18166
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19191
18167
|
const effectiveMode = mode || "full";
|
|
@@ -19234,7 +18210,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19234
18210
|
},
|
|
19235
18211
|
async ({ url, postBody }) => {
|
|
19236
18212
|
const tab = tabManager.getActiveTab();
|
|
19237
|
-
if (!tab) return
|
|
18213
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19238
18214
|
const preCheck = await validateLinkDestination(url);
|
|
19239
18215
|
if (preCheck.status === "dead") {
|
|
19240
18216
|
return asTextResponse(
|
|
@@ -19276,7 +18252,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19276
18252
|
async ({ enabled, tabId, match, reload }) => {
|
|
19277
18253
|
const activeTab = tabManager.getActiveTab();
|
|
19278
18254
|
if (!activeTab && !tabId && !match) {
|
|
19279
|
-
return
|
|
18255
|
+
return asNoActiveTabResponse();
|
|
19280
18256
|
}
|
|
19281
18257
|
return withAction(
|
|
19282
18258
|
runtime2,
|
|
@@ -19319,7 +18295,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19319
18295
|
},
|
|
19320
18296
|
async ({ type }) => {
|
|
19321
18297
|
const tab = tabManager.getActiveTab();
|
|
19322
|
-
if (!tab) return
|
|
18298
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19323
18299
|
try {
|
|
19324
18300
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19325
18301
|
const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
|
|
@@ -19367,7 +18343,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19367
18343
|
},
|
|
19368
18344
|
async () => {
|
|
19369
18345
|
const tab = tabManager.getActiveTab();
|
|
19370
|
-
if (!tab) return
|
|
18346
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19371
18347
|
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
19372
18348
|
if (!tab.canGoBack()) {
|
|
19373
18349
|
return "No previous page in history";
|
|
@@ -19388,7 +18364,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19388
18364
|
},
|
|
19389
18365
|
async () => {
|
|
19390
18366
|
const tab = tabManager.getActiveTab();
|
|
19391
|
-
if (!tab) return
|
|
18367
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19392
18368
|
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
19393
18369
|
if (!tab.canGoForward()) {
|
|
19394
18370
|
return "No forward page in history";
|
|
@@ -19409,7 +18385,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19409
18385
|
},
|
|
19410
18386
|
async () => {
|
|
19411
18387
|
const tab = tabManager.getActiveTab();
|
|
19412
|
-
if (!tab) return
|
|
18388
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19413
18389
|
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
19414
18390
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
19415
18391
|
await waitForLoad(tab.view.webContents);
|
|
@@ -19429,7 +18405,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19429
18405
|
},
|
|
19430
18406
|
async ({ index, selector }) => {
|
|
19431
18407
|
const tab = tabManager.getActiveTab();
|
|
19432
|
-
if (!tab) return
|
|
18408
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19433
18409
|
return withAction(
|
|
19434
18410
|
runtime2,
|
|
19435
18411
|
tabManager,
|
|
@@ -19458,7 +18434,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19458
18434
|
},
|
|
19459
18435
|
async ({ index, selector }) => {
|
|
19460
18436
|
const tab = tabManager.getActiveTab();
|
|
19461
|
-
if (!tab) return
|
|
18437
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19462
18438
|
return withAction(
|
|
19463
18439
|
runtime2,
|
|
19464
18440
|
tabManager,
|
|
@@ -19487,7 +18463,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19487
18463
|
},
|
|
19488
18464
|
async ({ index, selector }) => {
|
|
19489
18465
|
const tab = tabManager.getActiveTab();
|
|
19490
|
-
if (!tab) return
|
|
18466
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19491
18467
|
return withAction(
|
|
19492
18468
|
runtime2,
|
|
19493
18469
|
tabManager,
|
|
@@ -19516,11 +18492,11 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19516
18492
|
},
|
|
19517
18493
|
async ({ index, selector }) => {
|
|
19518
18494
|
const tab = tabManager.getActiveTab();
|
|
19519
|
-
if (!tab) return
|
|
18495
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19520
18496
|
const wc = tab.view.webContents;
|
|
19521
18497
|
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19522
18498
|
if (!resolvedSelector) {
|
|
19523
|
-
return
|
|
18499
|
+
return asErrorTextResponse("No index or selector provided");
|
|
19524
18500
|
}
|
|
19525
18501
|
const result = await wc.executeJavaScript(`
|
|
19526
18502
|
(function() {
|
|
@@ -19570,7 +18546,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19570
18546
|
);
|
|
19571
18547
|
}
|
|
19572
18548
|
if ("error" in result && typeof result.error === "string") {
|
|
19573
|
-
return
|
|
18549
|
+
return asErrorTextResponse(result.error);
|
|
19574
18550
|
}
|
|
19575
18551
|
const parts = [`<${result.tag}>`];
|
|
19576
18552
|
if ("role" in result && typeof result.role === "string" && result.role.trim()) {
|
|
@@ -19601,7 +18577,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19601
18577
|
},
|
|
19602
18578
|
async ({ index, selector, text, mode }) => {
|
|
19603
18579
|
const tab = tabManager.getActiveTab();
|
|
19604
|
-
if (!tab) return
|
|
18580
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19605
18581
|
return withAction(
|
|
19606
18582
|
runtime2,
|
|
19607
18583
|
tabManager,
|
|
@@ -19640,7 +18616,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19640
18616
|
},
|
|
19641
18617
|
async ({ index, selector, text, mode }) => {
|
|
19642
18618
|
const tab = tabManager.getActiveTab();
|
|
19643
|
-
if (!tab) return
|
|
18619
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19644
18620
|
return withAction(
|
|
19645
18621
|
runtime2,
|
|
19646
18622
|
tabManager,
|
|
@@ -19677,13 +18653,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19677
18653
|
},
|
|
19678
18654
|
async ({ index, selector, label, value }) => {
|
|
19679
18655
|
const tab = tabManager.getActiveTab();
|
|
19680
|
-
if (!tab) return
|
|
18656
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19681
18657
|
return withAction(
|
|
19682
18658
|
runtime2,
|
|
19683
18659
|
tabManager,
|
|
19684
18660
|
"select_option",
|
|
19685
18661
|
{ index, selector, label, value },
|
|
19686
|
-
async () =>
|
|
18662
|
+
async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
|
|
19687
18663
|
);
|
|
19688
18664
|
}
|
|
19689
18665
|
);
|
|
@@ -19699,23 +18675,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19699
18675
|
},
|
|
19700
18676
|
async ({ index, selector }) => {
|
|
19701
18677
|
const tab = tabManager.getActiveTab();
|
|
19702
|
-
if (!tab) return
|
|
18678
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19703
18679
|
return withAction(
|
|
19704
18680
|
runtime2,
|
|
19705
18681
|
tabManager,
|
|
19706
18682
|
"submit_form",
|
|
19707
18683
|
{ index, selector },
|
|
19708
|
-
async () =>
|
|
19709
|
-
const wc = tab.view.webContents;
|
|
19710
|
-
const beforeUrl = wc.getURL();
|
|
19711
|
-
const result = await submitForm(wc, index, selector);
|
|
19712
|
-
if (result.startsWith("Error") || result.startsWith("Target") || result.startsWith("No parent") || result.startsWith("Submit control")) {
|
|
19713
|
-
return result;
|
|
19714
|
-
}
|
|
19715
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
19716
|
-
const afterUrl = wc.getURL();
|
|
19717
|
-
return afterUrl !== beforeUrl ? `${result} -> ${afterUrl}` : result;
|
|
19718
|
-
}
|
|
18684
|
+
async () => submitFormDirect(tab.view.webContents, index, selector)
|
|
19719
18685
|
);
|
|
19720
18686
|
}
|
|
19721
18687
|
);
|
|
@@ -19732,7 +18698,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19732
18698
|
},
|
|
19733
18699
|
async ({ key, index, selector }) => {
|
|
19734
18700
|
const tab = tabManager.getActiveTab();
|
|
19735
|
-
if (!tab) return
|
|
18701
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19736
18702
|
return withAction(
|
|
19737
18703
|
runtime2,
|
|
19738
18704
|
tabManager,
|
|
@@ -19741,7 +18707,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19741
18707
|
async () => {
|
|
19742
18708
|
const wc = tab.view.webContents;
|
|
19743
18709
|
const beforeUrl = wc.getURL();
|
|
19744
|
-
const result = await
|
|
18710
|
+
const result = await pressKeyDirect(wc, key, index, selector);
|
|
19745
18711
|
if (key === "Enter") {
|
|
19746
18712
|
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
19747
18713
|
const afterUrl = wc.getURL();
|
|
@@ -19768,7 +18734,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19768
18734
|
},
|
|
19769
18735
|
async ({ direction, amount }) => {
|
|
19770
18736
|
const tab = tabManager.getActiveTab();
|
|
19771
|
-
if (!tab) return
|
|
18737
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19772
18738
|
return withAction(
|
|
19773
18739
|
runtime2,
|
|
19774
18740
|
tabManager,
|
|
@@ -19791,7 +18757,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19791
18757
|
},
|
|
19792
18758
|
async () => {
|
|
19793
18759
|
const tab = tabManager.getActiveTab();
|
|
19794
|
-
if (!tab) return
|
|
18760
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19795
18761
|
return withAction(
|
|
19796
18762
|
runtime2,
|
|
19797
18763
|
tabManager,
|
|
@@ -19814,7 +18780,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19814
18780
|
},
|
|
19815
18781
|
async ({ strategy }) => {
|
|
19816
18782
|
const tab = tabManager.getActiveTab();
|
|
19817
|
-
if (!tab) return
|
|
18783
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19818
18784
|
return withAction(
|
|
19819
18785
|
runtime2,
|
|
19820
18786
|
tabManager,
|
|
@@ -19840,13 +18806,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19840
18806
|
},
|
|
19841
18807
|
async ({ text, selector, timeoutMs }) => {
|
|
19842
18808
|
const tab = tabManager.getActiveTab();
|
|
19843
|
-
if (!tab) return
|
|
18809
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19844
18810
|
return withAction(
|
|
19845
18811
|
runtime2,
|
|
19846
18812
|
tabManager,
|
|
19847
18813
|
"wait_for",
|
|
19848
18814
|
{ text, selector, timeoutMs },
|
|
19849
|
-
async () =>
|
|
18815
|
+
async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
|
|
19850
18816
|
);
|
|
19851
18817
|
}
|
|
19852
18818
|
);
|
|
@@ -20027,7 +18993,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
20027
18993
|
},
|
|
20028
18994
|
async () => {
|
|
20029
18995
|
const tab = tabManager.getActiveTab();
|
|
20030
|
-
if (!tab) return
|
|
18996
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20031
18997
|
try {
|
|
20032
18998
|
const bounds = tab.view.getBounds();
|
|
20033
18999
|
if (bounds.width <= 0 || bounds.height <= 0) {
|
|
@@ -20096,7 +19062,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20096
19062
|
},
|
|
20097
19063
|
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
20098
19064
|
const tab = tabManager.getActiveTab();
|
|
20099
|
-
if (!tab) return
|
|
19065
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20100
19066
|
const normalizedText = normalizeLooseString(text);
|
|
20101
19067
|
return withAction(
|
|
20102
19068
|
runtime2,
|
|
@@ -20146,7 +19112,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20146
19112
|
},
|
|
20147
19113
|
async () => {
|
|
20148
19114
|
const tab = tabManager.getActiveTab();
|
|
20149
|
-
if (!tab) return
|
|
19115
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20150
19116
|
return withAction(
|
|
20151
19117
|
runtime2,
|
|
20152
19118
|
tabManager,
|
|
@@ -20285,8 +19251,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20285
19251
|
h.label,
|
|
20286
19252
|
void 0,
|
|
20287
19253
|
h.color
|
|
20288
|
-
).catch(
|
|
20289
|
-
|
|
19254
|
+
).catch(
|
|
19255
|
+
(err) => logger$7.warn("Failed to restore highlight after removal:", err)
|
|
19256
|
+
);
|
|
20290
19257
|
}
|
|
20291
19258
|
}
|
|
20292
19259
|
return asTextResponse(`Removed highlight ${id}`);
|
|
@@ -20426,7 +19393,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20426
19393
|
note,
|
|
20427
19394
|
{
|
|
20428
19395
|
onDuplicate: on_duplicate ?? "ask",
|
|
20429
|
-
extra:
|
|
19396
|
+
extra: getBookmarkMetadataFromArgs({
|
|
20430
19397
|
intent,
|
|
20431
19398
|
expected_content,
|
|
20432
19399
|
key_fields,
|
|
@@ -20570,7 +19537,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20570
19537
|
folderId: target.folderId,
|
|
20571
19538
|
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
|
|
20572
19539
|
note,
|
|
20573
|
-
...
|
|
19540
|
+
...getBookmarkMetadataFromArgs(args)
|
|
20574
19541
|
});
|
|
20575
19542
|
if (!updated) {
|
|
20576
19543
|
return `Bookmark ${existing.id} not found`;
|
|
@@ -20588,7 +19555,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20588
19555
|
note,
|
|
20589
19556
|
{
|
|
20590
19557
|
onDuplicate: "update",
|
|
20591
|
-
extra:
|
|
19558
|
+
extra: getBookmarkMetadataFromArgs(args)
|
|
20592
19559
|
}
|
|
20593
19560
|
);
|
|
20594
19561
|
const bookmark = result.bookmark;
|
|
@@ -20971,7 +19938,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20971
19938
|
},
|
|
20972
19939
|
async ({ title, folder, summary, note, tags }) => {
|
|
20973
19940
|
const tab = tabManager.getActiveTab();
|
|
20974
|
-
if (!tab) return
|
|
19941
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20975
19942
|
return withAction(
|
|
20976
19943
|
runtime2,
|
|
20977
19944
|
tabManager,
|
|
@@ -21115,7 +20082,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21115
20082
|
let page;
|
|
21116
20083
|
try {
|
|
21117
20084
|
page = await extractContent(wc);
|
|
21118
|
-
} catch {
|
|
20085
|
+
} catch (err) {
|
|
20086
|
+
logger$7.warn("Failed to extract page while generating suggestions:", err);
|
|
21119
20087
|
return asTextResponse(
|
|
21120
20088
|
"Could not read page. Try navigate to a working URL."
|
|
21121
20089
|
);
|
|
@@ -21222,7 +20190,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21222
20190
|
},
|
|
21223
20191
|
async ({ fields, submit }) => {
|
|
21224
20192
|
const tab = tabManager.getActiveTab();
|
|
21225
|
-
if (!tab) return
|
|
20193
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21226
20194
|
return withAction(
|
|
21227
20195
|
runtime2,
|
|
21228
20196
|
tabManager,
|
|
@@ -21236,7 +20204,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21236
20204
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
21237
20205
|
if (firstSel) {
|
|
21238
20206
|
const beforeUrl = wc.getURL();
|
|
21239
|
-
const submitResult = await
|
|
20207
|
+
const submitResult = await submitFormDirect(wc, void 0, firstSel);
|
|
21240
20208
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21241
20209
|
const afterUrl = wc.getURL();
|
|
21242
20210
|
results.push(
|
|
@@ -21279,7 +20247,7 @@ ${results.join("\n")}`;
|
|
|
21279
20247
|
submit_selector
|
|
21280
20248
|
}) => {
|
|
21281
20249
|
const tab = tabManager.getActiveTab();
|
|
21282
|
-
if (!tab) return
|
|
20250
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21283
20251
|
return withAction(
|
|
21284
20252
|
runtime2,
|
|
21285
20253
|
tabManager,
|
|
@@ -21353,7 +20321,7 @@ ${steps.join("\n")}`;
|
|
|
21353
20321
|
},
|
|
21354
20322
|
async ({ query, selector }) => {
|
|
21355
20323
|
const tab = tabManager.getActiveTab();
|
|
21356
|
-
if (!tab) return
|
|
20324
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21357
20325
|
const qLower = query.toLowerCase().trim();
|
|
21358
20326
|
const buttonLabels = [
|
|
21359
20327
|
"add to cart",
|
|
@@ -21438,7 +20406,7 @@ ${steps.join("\n")}`;
|
|
|
21438
20406
|
},
|
|
21439
20407
|
async ({ direction, selector }) => {
|
|
21440
20408
|
const tab = tabManager.getActiveTab();
|
|
21441
|
-
if (!tab) return
|
|
20409
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21442
20410
|
return withAction(
|
|
21443
20411
|
runtime2,
|
|
21444
20412
|
tabManager,
|
|
@@ -21489,7 +20457,7 @@ ${steps.join("\n")}`;
|
|
|
21489
20457
|
},
|
|
21490
20458
|
async () => {
|
|
21491
20459
|
const tab = tabManager.getActiveTab();
|
|
21492
|
-
if (!tab) return
|
|
20460
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21493
20461
|
return withAction(
|
|
21494
20462
|
runtime2,
|
|
21495
20463
|
tabManager,
|
|
@@ -21547,7 +20515,7 @@ ${steps.join("\n")}`;
|
|
|
21547
20515
|
},
|
|
21548
20516
|
async ({ index, selector: rawSelector }) => {
|
|
21549
20517
|
const tab = tabManager.getActiveTab();
|
|
21550
|
-
if (!tab) return
|
|
20518
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21551
20519
|
return withAction(
|
|
21552
20520
|
runtime2,
|
|
21553
20521
|
tabManager,
|
|
@@ -21602,7 +20570,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21602
20570
|
},
|
|
21603
20571
|
async ({ index, selector: rawSelector, position }) => {
|
|
21604
20572
|
const tab = tabManager.getActiveTab();
|
|
21605
|
-
if (!tab) return
|
|
20573
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21606
20574
|
return withAction(
|
|
21607
20575
|
runtime2,
|
|
21608
20576
|
tabManager,
|
|
@@ -21660,7 +20628,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21660
20628
|
},
|
|
21661
20629
|
async ({ timeoutMs }) => {
|
|
21662
20630
|
const tab = tabManager.getActiveTab();
|
|
21663
|
-
if (!tab) return
|
|
20631
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21664
20632
|
return withAction(
|
|
21665
20633
|
runtime2,
|
|
21666
20634
|
tabManager,
|
|
@@ -21720,11 +20688,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21720
20688
|
let targetDomain = domain;
|
|
21721
20689
|
if (!targetDomain) {
|
|
21722
20690
|
const tab = tabManager.getActiveTab();
|
|
21723
|
-
if (!tab) return
|
|
20691
|
+
if (!tab) return asErrorTextResponse("No active tab and no domain specified");
|
|
21724
20692
|
try {
|
|
21725
20693
|
targetDomain = new URL(tab.state.url).hostname;
|
|
21726
|
-
} catch {
|
|
21727
|
-
|
|
20694
|
+
} catch (err) {
|
|
20695
|
+
logger$7.warn("Failed to parse active tab URL for vault_status:", err);
|
|
20696
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21728
20697
|
}
|
|
21729
20698
|
}
|
|
21730
20699
|
const matches = findEntriesForDomain(
|
|
@@ -21783,13 +20752,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21783
20752
|
submit_index
|
|
21784
20753
|
}) => {
|
|
21785
20754
|
const tab = tabManager.getActiveTab();
|
|
21786
|
-
if (!tab) return
|
|
20755
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21787
20756
|
const wc = tab.view.webContents;
|
|
21788
20757
|
let hostname;
|
|
21789
20758
|
try {
|
|
21790
20759
|
hostname = new URL(tab.state.url).hostname;
|
|
21791
|
-
} catch {
|
|
21792
|
-
|
|
20760
|
+
} catch (err) {
|
|
20761
|
+
logger$7.warn("Failed to parse active tab URL for vault_login:", err);
|
|
20762
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21793
20763
|
}
|
|
21794
20764
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21795
20765
|
if (matches.length === 0) {
|
|
@@ -21825,7 +20795,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21825
20795
|
}
|
|
21826
20796
|
const creds = getCredential(match.id);
|
|
21827
20797
|
if (!creds) {
|
|
21828
|
-
return
|
|
20798
|
+
return asErrorTextResponse("Credential not found in vault");
|
|
21829
20799
|
}
|
|
21830
20800
|
const results = [];
|
|
21831
20801
|
if (username_index != null) {
|
|
@@ -21876,13 +20846,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21876
20846
|
},
|
|
21877
20847
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
21878
20848
|
const tab = tabManager.getActiveTab();
|
|
21879
|
-
if (!tab) return
|
|
20849
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21880
20850
|
const wc = tab.view.webContents;
|
|
21881
20851
|
let hostname;
|
|
21882
20852
|
try {
|
|
21883
20853
|
hostname = new URL(tab.state.url).hostname;
|
|
21884
|
-
} catch {
|
|
21885
|
-
|
|
20854
|
+
} catch (err) {
|
|
20855
|
+
logger$7.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
20856
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21886
20857
|
}
|
|
21887
20858
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21888
20859
|
const match = credential_label ? matches.find(
|
|
@@ -22053,7 +21024,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22053
21024
|
await mcpServer.connect(transport);
|
|
22054
21025
|
await transport.handleRequest(req, res);
|
|
22055
21026
|
} catch (error) {
|
|
22056
|
-
|
|
21027
|
+
logger$7.error("Error handling request:", error);
|
|
22057
21028
|
if (!res.headersSent) {
|
|
22058
21029
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22059
21030
|
res.end(
|
|
@@ -22072,7 +21043,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22072
21043
|
};
|
|
22073
21044
|
server.once("error", (error) => {
|
|
22074
21045
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
22075
|
-
|
|
21046
|
+
logger$7.error("Server error:", error);
|
|
22076
21047
|
clearMcpAuthFile();
|
|
22077
21048
|
setMcpHealth({
|
|
22078
21049
|
configuredPort: port,
|
|
@@ -22084,14 +21055,12 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22084
21055
|
if (httpServer === server) {
|
|
22085
21056
|
httpServer = null;
|
|
22086
21057
|
}
|
|
22087
|
-
finish({
|
|
22088
|
-
ok: false,
|
|
21058
|
+
finish(errorResult(message, {
|
|
22089
21059
|
configuredPort: port,
|
|
22090
21060
|
activePort: null,
|
|
22091
21061
|
endpoint: null,
|
|
22092
|
-
authToken: null
|
|
22093
|
-
|
|
22094
|
-
});
|
|
21062
|
+
authToken: null
|
|
21063
|
+
}));
|
|
22095
21064
|
});
|
|
22096
21065
|
server.listen(port, "127.0.0.1", () => {
|
|
22097
21066
|
httpServer = server;
|
|
@@ -22106,7 +21075,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22106
21075
|
message: `MCP server listening on ${endpoint}.`
|
|
22107
21076
|
});
|
|
22108
21077
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22109
|
-
|
|
21078
|
+
logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
22110
21079
|
}
|
|
22111
21080
|
if (mcpAuthToken) {
|
|
22112
21081
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -22145,7 +21114,7 @@ function stopMcpServer() {
|
|
|
22145
21114
|
message: "MCP server is stopped."
|
|
22146
21115
|
});
|
|
22147
21116
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22148
|
-
|
|
21117
|
+
logger$7.info("Server stopped");
|
|
22149
21118
|
}
|
|
22150
21119
|
resolve();
|
|
22151
21120
|
});
|
|
@@ -22166,6 +21135,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
22166
21135
|
function isSafeAutomationKitId(id) {
|
|
22167
21136
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
22168
21137
|
}
|
|
21138
|
+
const logger$6 = createLogger("KitRegistry");
|
|
22169
21139
|
function getUserKitsDir() {
|
|
22170
21140
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
22171
21141
|
}
|
|
@@ -22203,10 +21173,10 @@ function getInstalledKits() {
|
|
|
22203
21173
|
if (isValidKit(parsed)) {
|
|
22204
21174
|
kits.push(parsed);
|
|
22205
21175
|
} else {
|
|
22206
|
-
|
|
21176
|
+
logger$6.warn(`Skipping invalid kit file: ${file}`);
|
|
22207
21177
|
}
|
|
22208
|
-
} catch {
|
|
22209
|
-
|
|
21178
|
+
} catch (err) {
|
|
21179
|
+
logger$6.warn(`Failed to read kit file: ${file}`, err);
|
|
22210
21180
|
}
|
|
22211
21181
|
}
|
|
22212
21182
|
return kits;
|
|
@@ -22218,69 +21188,67 @@ async function installKitFromFile() {
|
|
|
22218
21188
|
properties: ["openFile"]
|
|
22219
21189
|
});
|
|
22220
21190
|
if (canceled || filePaths.length === 0) {
|
|
22221
|
-
return
|
|
21191
|
+
return errorResult("canceled");
|
|
22222
21192
|
}
|
|
22223
21193
|
let raw;
|
|
22224
21194
|
try {
|
|
22225
21195
|
raw = fs$1.readFileSync(filePaths[0], "utf-8");
|
|
22226
21196
|
} catch {
|
|
22227
|
-
return
|
|
21197
|
+
return errorResult("Could not read the selected file.");
|
|
22228
21198
|
}
|
|
22229
21199
|
let parsed;
|
|
22230
21200
|
try {
|
|
22231
21201
|
parsed = JSON.parse(raw);
|
|
22232
21202
|
} catch {
|
|
22233
|
-
return
|
|
21203
|
+
return errorResult("File is not valid JSON.");
|
|
22234
21204
|
}
|
|
22235
21205
|
if (!isValidKit(parsed)) {
|
|
22236
|
-
return
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
};
|
|
21206
|
+
return errorResult(
|
|
21207
|
+
"File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
21208
|
+
);
|
|
22240
21209
|
}
|
|
22241
21210
|
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
22242
|
-
return
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
};
|
|
21211
|
+
return errorResult(
|
|
21212
|
+
`Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
21213
|
+
);
|
|
22246
21214
|
}
|
|
22247
21215
|
ensureKitsDir();
|
|
22248
21216
|
const dest = getKitFilePath(parsed.id);
|
|
22249
21217
|
if (!dest) {
|
|
22250
|
-
return
|
|
21218
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22251
21219
|
}
|
|
22252
21220
|
try {
|
|
22253
21221
|
fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
22254
21222
|
} catch {
|
|
22255
|
-
return
|
|
21223
|
+
return errorResult("Failed to save the kit file.");
|
|
22256
21224
|
}
|
|
22257
|
-
return {
|
|
21225
|
+
return okResult({ kit: parsed });
|
|
22258
21226
|
}
|
|
22259
21227
|
function uninstallKit(id, scheduledKitIds) {
|
|
22260
21228
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
22261
|
-
return
|
|
21229
|
+
return errorResult("Built-in kits cannot be removed.");
|
|
22262
21230
|
}
|
|
22263
21231
|
if (scheduledKitIds?.has(id)) {
|
|
22264
|
-
return
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
};
|
|
21232
|
+
return errorResult(
|
|
21233
|
+
"This kit has active scheduled jobs. Delete or reassign them first."
|
|
21234
|
+
);
|
|
22268
21235
|
}
|
|
22269
21236
|
ensureKitsDir();
|
|
22270
21237
|
const target = getKitFilePath(id);
|
|
22271
21238
|
if (!target) {
|
|
22272
|
-
return
|
|
21239
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22273
21240
|
}
|
|
22274
21241
|
if (!fs$1.existsSync(target)) {
|
|
22275
|
-
return
|
|
21242
|
+
return errorResult("Kit not found.");
|
|
22276
21243
|
}
|
|
22277
21244
|
try {
|
|
22278
21245
|
fs$1.unlinkSync(target);
|
|
22279
|
-
return
|
|
21246
|
+
return okResult();
|
|
22280
21247
|
} catch {
|
|
22281
|
-
return
|
|
21248
|
+
return errorResult("Failed to remove the kit file.");
|
|
22282
21249
|
}
|
|
22283
21250
|
}
|
|
21251
|
+
const logger$5 = createLogger("Scheduler");
|
|
22284
21252
|
let jobs = [];
|
|
22285
21253
|
let removeIdleListener = null;
|
|
22286
21254
|
let broadcastFn = null;
|
|
@@ -22305,7 +21273,7 @@ function saveJobs() {
|
|
|
22305
21273
|
try {
|
|
22306
21274
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
22307
21275
|
} catch (err) {
|
|
22308
|
-
|
|
21276
|
+
logger$5.warn("Failed to save jobs:", err);
|
|
22309
21277
|
}
|
|
22310
21278
|
}
|
|
22311
21279
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -22427,7 +21395,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22427
21395
|
};
|
|
22428
21396
|
startActivity();
|
|
22429
21397
|
if (!settings2.chatProvider) {
|
|
22430
|
-
|
|
21398
|
+
logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
22431
21399
|
appendActivity(
|
|
22432
21400
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
22433
21401
|
);
|
|
@@ -22435,7 +21403,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22435
21403
|
return;
|
|
22436
21404
|
}
|
|
22437
21405
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
22438
|
-
|
|
21406
|
+
logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
22439
21407
|
}
|
|
22440
21408
|
try {
|
|
22441
21409
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -22488,7 +21456,7 @@ function tick(windowState, runtime2) {
|
|
|
22488
21456
|
saveJobs();
|
|
22489
21457
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
22490
21458
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
22491
|
-
|
|
21459
|
+
logger$5.warn("Unexpected error firing job:", err);
|
|
22492
21460
|
}).finally(fireNext);
|
|
22493
21461
|
};
|
|
22494
21462
|
fireNext();
|
|
@@ -23046,6 +22014,7 @@ function registerWindowControlHandlers(mainWindow) {
|
|
|
23046
22014
|
});
|
|
23047
22015
|
}
|
|
23048
22016
|
let activeChatProvider = null;
|
|
22017
|
+
const logger$4 = createLogger("IPC");
|
|
23049
22018
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
23050
22019
|
function registerIpcHandlers(windowState, runtime2) {
|
|
23051
22020
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
@@ -23122,7 +22091,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23122
22091
|
let parsed;
|
|
23123
22092
|
try {
|
|
23124
22093
|
parsed = new URL(rawUrl);
|
|
23125
|
-
} catch {
|
|
22094
|
+
} catch (err) {
|
|
22095
|
+
logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
|
|
23126
22096
|
return;
|
|
23127
22097
|
}
|
|
23128
22098
|
if (parsed.origin !== premiumApiOrigin) return;
|
|
@@ -23179,7 +22149,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23179
22149
|
if (wc.isDestroyed()) return 0;
|
|
23180
22150
|
try {
|
|
23181
22151
|
return await getHighlightCount(wc) ?? 0;
|
|
23182
|
-
} catch {
|
|
22152
|
+
} catch (err) {
|
|
22153
|
+
logger$4.warn("Failed to get active highlight count:", err);
|
|
23183
22154
|
return 0;
|
|
23184
22155
|
}
|
|
23185
22156
|
};
|
|
@@ -23287,13 +22258,13 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23287
22258
|
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
|
|
23288
22259
|
try {
|
|
23289
22260
|
if (!config || typeof config !== "object" || !("id" in config)) {
|
|
23290
|
-
return
|
|
22261
|
+
return errorResult("Invalid provider configuration", { models: [] });
|
|
23291
22262
|
}
|
|
23292
22263
|
return await fetchProviderModels(
|
|
23293
22264
|
config
|
|
23294
22265
|
);
|
|
23295
22266
|
} catch (err) {
|
|
23296
|
-
return {
|
|
22267
|
+
return errorResult(getErrorMessage(err), { models: [] });
|
|
23297
22268
|
}
|
|
23298
22269
|
});
|
|
23299
22270
|
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
|
|
@@ -23488,12 +22459,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23488
22459
|
const wc = activeTab.view.webContents;
|
|
23489
22460
|
const result = await captureSelectionHighlight(wc);
|
|
23490
22461
|
if (result.success && result.text) {
|
|
23491
|
-
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
23492
|
-
|
|
22462
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
22463
|
+
(err) => logger$4.warn("Failed to highlight captured selection:", err)
|
|
22464
|
+
);
|
|
23493
22465
|
await emitHighlightCount();
|
|
23494
22466
|
}
|
|
23495
22467
|
return result;
|
|
23496
|
-
} catch {
|
|
22468
|
+
} catch (err) {
|
|
22469
|
+
logger$4.warn("Failed to capture highlight from active tab:", err);
|
|
23497
22470
|
return { success: false, message: "Could not capture selection" };
|
|
23498
22471
|
}
|
|
23499
22472
|
});
|
|
@@ -23517,7 +22490,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23517
22490
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
23518
22491
|
}
|
|
23519
22492
|
});
|
|
23520
|
-
} catch {
|
|
22493
|
+
} catch (err) {
|
|
22494
|
+
logger$4.warn("Failed to persist auto-highlight selection:", err);
|
|
23521
22495
|
}
|
|
23522
22496
|
});
|
|
23523
22497
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
@@ -23530,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23530
22504
|
if (wc.isDestroyed()) return false;
|
|
23531
22505
|
try {
|
|
23532
22506
|
return scrollToHighlight(wc, index);
|
|
23533
|
-
} catch {
|
|
22507
|
+
} catch (err) {
|
|
22508
|
+
logger$4.warn("Failed to scroll to highlight:", err);
|
|
23534
22509
|
return false;
|
|
23535
22510
|
}
|
|
23536
22511
|
});
|
|
@@ -23545,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23545
22520
|
await emitHighlightCount();
|
|
23546
22521
|
}
|
|
23547
22522
|
return removed;
|
|
23548
|
-
} catch {
|
|
22523
|
+
} catch (err) {
|
|
22524
|
+
logger$4.warn("Failed to remove highlight at index:", err);
|
|
23549
22525
|
return false;
|
|
23550
22526
|
}
|
|
23551
22527
|
});
|
|
@@ -23560,7 +22536,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23560
22536
|
await emitHighlightCount();
|
|
23561
22537
|
}
|
|
23562
22538
|
return cleared;
|
|
23563
|
-
} catch {
|
|
22539
|
+
} catch (err) {
|
|
22540
|
+
logger$4.warn("Failed to clear highlight elements:", err);
|
|
23564
22541
|
return false;
|
|
23565
22542
|
}
|
|
23566
22543
|
});
|
|
@@ -23644,7 +22621,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23644
22621
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
|
|
23645
22622
|
assertString(email, "email");
|
|
23646
22623
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23647
|
-
return
|
|
22624
|
+
return errorResult("Invalid email format");
|
|
23648
22625
|
}
|
|
23649
22626
|
trackPremiumFunnel("activation_attempted");
|
|
23650
22627
|
const result = await requestActivationCode(email);
|
|
@@ -23660,11 +22637,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23660
22637
|
assertString(code, "code");
|
|
23661
22638
|
assertString(challengeToken, "challengeToken");
|
|
23662
22639
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23663
|
-
return {
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
error: "Invalid email format"
|
|
23667
|
-
};
|
|
22640
|
+
return errorResult("Invalid email format", {
|
|
22641
|
+
state: getPremiumState()
|
|
22642
|
+
});
|
|
23668
22643
|
}
|
|
23669
22644
|
trackPremiumFunnel("activation_attempted");
|
|
23670
22645
|
const result = await verifyActivationCode(email, code, challengeToken);
|
|
@@ -24099,6 +23074,7 @@ ${lines.join("\n")}
|
|
|
24099
23074
|
}
|
|
24100
23075
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
24101
23076
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
23077
|
+
const logger$3 = createLogger("Runtime");
|
|
24102
23078
|
function clone(value) {
|
|
24103
23079
|
return JSON.parse(JSON.stringify(value));
|
|
24104
23080
|
}
|
|
@@ -24488,9 +23464,7 @@ ${progress}
|
|
|
24488
23464
|
JSON.stringify(persisted, null, 2),
|
|
24489
23465
|
"utf-8"
|
|
24490
23466
|
)
|
|
24491
|
-
).catch(
|
|
24492
|
-
(err) => console.error("[Vessel] Failed to persist runtime state:", err)
|
|
24493
|
-
);
|
|
23467
|
+
).catch((err) => logger$3.error("Failed to persist runtime state:", err));
|
|
24494
23468
|
}
|
|
24495
23469
|
schedulePersist() {
|
|
24496
23470
|
this.persistDirty = true;
|
|
@@ -24755,6 +23729,7 @@ function installDownloadHandler(chromeView) {
|
|
|
24755
23729
|
});
|
|
24756
23730
|
});
|
|
24757
23731
|
}
|
|
23732
|
+
const logger$2 = createLogger("Shortcuts");
|
|
24758
23733
|
function registerHighlightShortcut(mainWindow, tabManager) {
|
|
24759
23734
|
const register = () => {
|
|
24760
23735
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
@@ -24764,7 +23739,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
|
|
|
24764
23739
|
tabManager.captureHighlightFromActiveTab();
|
|
24765
23740
|
});
|
|
24766
23741
|
if (!success) {
|
|
24767
|
-
|
|
23742
|
+
logger$2.warn("Failed to register Ctrl+H shortcut");
|
|
24768
23743
|
}
|
|
24769
23744
|
};
|
|
24770
23745
|
register();
|
|
@@ -24833,6 +23808,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
24833
23808
|
});
|
|
24834
23809
|
}
|
|
24835
23810
|
}
|
|
23811
|
+
const logger$1 = createLogger("Splash");
|
|
24836
23812
|
function findIconBase64() {
|
|
24837
23813
|
const candidates = [
|
|
24838
23814
|
path$1.join(process.resourcesPath, "vessel-icon.png"),
|
|
@@ -24995,7 +23971,7 @@ function createSplashWindow() {
|
|
|
24995
23971
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|
|
24996
23972
|
void splash.loadFile(tmpPath);
|
|
24997
23973
|
} catch (err) {
|
|
24998
|
-
|
|
23974
|
+
logger$1.warn("Failed to write temp HTML, using fallback:", err);
|
|
24999
23975
|
void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
|
|
25000
23976
|
}
|
|
25001
23977
|
return splash;
|
|
@@ -25005,6 +23981,7 @@ function closeSplash(splash, delayMs = 0) {
|
|
|
25005
23981
|
if (!splash.isDestroyed()) splash.close();
|
|
25006
23982
|
}, delayMs);
|
|
25007
23983
|
}
|
|
23984
|
+
const logger = createLogger("Bootstrap");
|
|
25008
23985
|
let runtime = null;
|
|
25009
23986
|
function checkWritableUserData(userDataPath) {
|
|
25010
23987
|
const issues = [];
|
|
@@ -25124,7 +24101,7 @@ async function bootstrap() {
|
|
|
25124
24101
|
};
|
|
25125
24102
|
let didInitializeChromeRenderer = false;
|
|
25126
24103
|
const splashTimeout = setTimeout(() => {
|
|
25127
|
-
|
|
24104
|
+
logger.warn("Renderer did not finish loading before splash timeout");
|
|
25128
24105
|
revealMainWindow();
|
|
25129
24106
|
}, 8e3);
|
|
25130
24107
|
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
@@ -25175,8 +24152,8 @@ async function bootstrap() {
|
|
|
25175
24152
|
"did-fail-load",
|
|
25176
24153
|
(_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
|
25177
24154
|
if (!isMainFrame) return;
|
|
25178
|
-
|
|
25179
|
-
"
|
|
24155
|
+
logger.error(
|
|
24156
|
+
"Chrome renderer failed to load:",
|
|
25180
24157
|
errorCode,
|
|
25181
24158
|
errorDescription,
|
|
25182
24159
|
validatedURL
|
|
@@ -25187,21 +24164,21 @@ async function bootstrap() {
|
|
|
25187
24164
|
);
|
|
25188
24165
|
loadRenderers(chromeView, sidebarView, devtoolsPanelView);
|
|
25189
24166
|
startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
|
|
25190
|
-
|
|
24167
|
+
logger.error("MCP server failed to start:", err);
|
|
25191
24168
|
});
|
|
25192
24169
|
}
|
|
25193
24170
|
process.on("uncaughtException", (error) => {
|
|
25194
|
-
|
|
24171
|
+
logger.error("Uncaught exception:", error.message, error.stack);
|
|
25195
24172
|
electron.app.quit();
|
|
25196
24173
|
});
|
|
25197
24174
|
process.on("unhandledRejection", (reason) => {
|
|
25198
|
-
|
|
25199
|
-
"
|
|
24175
|
+
logger.error(
|
|
24176
|
+
"Unhandled rejection:",
|
|
25200
24177
|
reason instanceof Error ? reason.message : reason
|
|
25201
24178
|
);
|
|
25202
24179
|
});
|
|
25203
24180
|
electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
25204
|
-
|
|
24181
|
+
logger.error("Failed to bootstrap application:", error);
|
|
25205
24182
|
electron.app.quit();
|
|
25206
24183
|
});
|
|
25207
24184
|
electron.app.on("window-all-closed", () => {
|