@quanta-intellect/vessel-browser 0.1.61 → 0.1.65
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;
|
|
@@ -3938,6 +3981,7 @@ let flushTimer = null;
|
|
|
3938
3981
|
let sessionStartedAt = null;
|
|
3939
3982
|
function isEnabled() {
|
|
3940
3983
|
if (POSTHOG_API_KEY === "YOUR_POSTHOG_KEY_HERE") return false;
|
|
3984
|
+
if (process.env.VESSEL_DEV === "1") return false;
|
|
3941
3985
|
return loadSettings().telemetryEnabled !== false;
|
|
3942
3986
|
}
|
|
3943
3987
|
function trackEvent(event, properties = {}) {
|
|
@@ -4199,6 +4243,37 @@ function mapActionButtons(interactiveElements) {
|
|
|
4199
4243
|
}
|
|
4200
4244
|
return buttons;
|
|
4201
4245
|
}
|
|
4246
|
+
function asStructuredObject(value) {
|
|
4247
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4248
|
+
}
|
|
4249
|
+
function stringifyStructuredScalar(value) {
|
|
4250
|
+
if (typeof value === "string") {
|
|
4251
|
+
const trimmed = value.trim();
|
|
4252
|
+
return trimmed || void 0;
|
|
4253
|
+
}
|
|
4254
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
4255
|
+
return String(value);
|
|
4256
|
+
}
|
|
4257
|
+
return void 0;
|
|
4258
|
+
}
|
|
4259
|
+
function firstStructuredString(...values) {
|
|
4260
|
+
for (const value of values) {
|
|
4261
|
+
const normalized = stringifyStructuredScalar(value);
|
|
4262
|
+
if (normalized) return normalized;
|
|
4263
|
+
}
|
|
4264
|
+
return void 0;
|
|
4265
|
+
}
|
|
4266
|
+
function getOfferPrice(offers) {
|
|
4267
|
+
if (Array.isArray(offers)) {
|
|
4268
|
+
for (const offer2 of offers) {
|
|
4269
|
+
const price = getOfferPrice(offer2);
|
|
4270
|
+
if (price) return price;
|
|
4271
|
+
}
|
|
4272
|
+
return void 0;
|
|
4273
|
+
}
|
|
4274
|
+
const offer = asStructuredObject(offers);
|
|
4275
|
+
return firstStructuredString(offer?.price);
|
|
4276
|
+
}
|
|
4202
4277
|
function extractPrimaryEntity(pageType, structuredData, metaTags) {
|
|
4203
4278
|
if (pageType === "product") {
|
|
4204
4279
|
const product = structuredData?.find(
|
|
@@ -4206,14 +4281,27 @@ function extractPrimaryEntity(pageType, structuredData, metaTags) {
|
|
|
4206
4281
|
);
|
|
4207
4282
|
if (product) {
|
|
4208
4283
|
const attrs = product.attributes ?? {};
|
|
4284
|
+
const aggregateRating = asStructuredObject(attrs.aggregateRating);
|
|
4209
4285
|
return {
|
|
4210
4286
|
type: "Product",
|
|
4211
|
-
nameField:
|
|
4212
|
-
priceField:
|
|
4213
|
-
imageField:
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4287
|
+
nameField: firstStructuredString(attrs.name),
|
|
4288
|
+
priceField: firstStructuredString(attrs.price) ?? getOfferPrice(attrs.offers),
|
|
4289
|
+
imageField: firstStructuredString(
|
|
4290
|
+
attrs.image,
|
|
4291
|
+
Array.isArray(attrs.image) ? attrs.image[0] : void 0
|
|
4292
|
+
),
|
|
4293
|
+
descriptionField: firstStructuredString(attrs.description),
|
|
4294
|
+
reviewsField: firstStructuredString(
|
|
4295
|
+
attrs.reviews,
|
|
4296
|
+
attrs.reviewCount,
|
|
4297
|
+
aggregateRating?.reviewCount,
|
|
4298
|
+
aggregateRating?.ratingCount
|
|
4299
|
+
),
|
|
4300
|
+
ratingField: firstStructuredString(
|
|
4301
|
+
attrs.rating,
|
|
4302
|
+
attrs.ratingValue,
|
|
4303
|
+
aggregateRating?.ratingValue
|
|
4304
|
+
),
|
|
4217
4305
|
addToCartField: void 0
|
|
4218
4306
|
};
|
|
4219
4307
|
}
|
|
@@ -4331,6 +4419,7 @@ function inferPageSchema(page) {
|
|
|
4331
4419
|
confidence
|
|
4332
4420
|
};
|
|
4333
4421
|
}
|
|
4422
|
+
const logger$e = createLogger("Extractor");
|
|
4334
4423
|
const EMPTY_PAGE_CONTENT = {
|
|
4335
4424
|
title: "",
|
|
4336
4425
|
content: "",
|
|
@@ -5077,7 +5166,8 @@ async function executeScript(webContents, script) {
|
|
|
5077
5166
|
timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
|
|
5078
5167
|
})
|
|
5079
5168
|
]);
|
|
5080
|
-
} catch {
|
|
5169
|
+
} catch (err) {
|
|
5170
|
+
logger$e.warn("Failed to execute page script:", err);
|
|
5081
5171
|
return null;
|
|
5082
5172
|
} finally {
|
|
5083
5173
|
if (timer) {
|
|
@@ -5185,7 +5275,8 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5185
5275
|
);
|
|
5186
5276
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
5187
5277
|
}
|
|
5188
|
-
} catch {
|
|
5278
|
+
} catch (err) {
|
|
5279
|
+
logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
5189
5280
|
}
|
|
5190
5281
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
5191
5282
|
}
|
|
@@ -5974,6 +6065,9 @@ function isClickReadLoop(names) {
|
|
|
5974
6065
|
}
|
|
5975
6066
|
return clickReadPairs >= 2;
|
|
5976
6067
|
}
|
|
6068
|
+
function isRecord(value) {
|
|
6069
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6070
|
+
}
|
|
5977
6071
|
class AnthropicProvider {
|
|
5978
6072
|
agentToolProfile = "default";
|
|
5979
6073
|
client;
|
|
@@ -6070,10 +6164,14 @@ class AnthropicProvider {
|
|
|
6070
6164
|
}
|
|
6071
6165
|
} else if (event.type === "content_block_stop" && currentToolUse) {
|
|
6072
6166
|
try {
|
|
6167
|
+
const input = JSON.parse(currentToolUse.inputJson || "{}");
|
|
6168
|
+
if (!isRecord(input)) {
|
|
6169
|
+
throw new Error("Tool input must be a JSON object");
|
|
6170
|
+
}
|
|
6073
6171
|
toolUseBlocks.push({
|
|
6074
6172
|
id: currentToolUse.id,
|
|
6075
6173
|
name: currentToolUse.name,
|
|
6076
|
-
input
|
|
6174
|
+
input
|
|
6077
6175
|
});
|
|
6078
6176
|
} catch {
|
|
6079
6177
|
toolUseBlocks.push({
|
|
@@ -6120,7 +6218,7 @@ class AnthropicProvider {
|
|
|
6120
6218
|
});
|
|
6121
6219
|
continue;
|
|
6122
6220
|
}
|
|
6123
|
-
const argSummary = tb.input.url
|
|
6221
|
+
const argSummary = [tb.input.url, tb.input.text, tb.input.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
6124
6222
|
onChunk(`
|
|
6125
6223
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
6126
6224
|
`);
|
|
@@ -6347,6 +6445,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
6347
6445
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
6348
6446
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
6349
6447
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
6448
|
+
const logger$d = createLogger("OpenAIProvider");
|
|
6350
6449
|
function shouldDebugAgentLoop() {
|
|
6351
6450
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
6352
6451
|
return value === "1" || value === "true";
|
|
@@ -6753,9 +6852,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
6753
6852
|
function logAgentLoopDebug(payload) {
|
|
6754
6853
|
if (!shouldDebugAgentLoop()) return;
|
|
6755
6854
|
try {
|
|
6756
|
-
|
|
6855
|
+
logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
6757
6856
|
} catch (err) {
|
|
6758
|
-
|
|
6857
|
+
logger$d.warn("Failed to serialize debug payload:", err);
|
|
6759
6858
|
}
|
|
6760
6859
|
}
|
|
6761
6860
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -6775,7 +6874,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
|
6775
6874
|
const argsJson = match[2] ?? "{}";
|
|
6776
6875
|
let parsedArgs = {};
|
|
6777
6876
|
try {
|
|
6778
|
-
|
|
6877
|
+
const raw = JSON.parse(argsJson);
|
|
6878
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
6879
|
+
parsedArgs = raw;
|
|
6880
|
+
}
|
|
6779
6881
|
} catch {
|
|
6780
6882
|
continue;
|
|
6781
6883
|
}
|
|
@@ -7172,7 +7274,7 @@ class OpenAICompatProvider {
|
|
|
7172
7274
|
}
|
|
7173
7275
|
continue;
|
|
7174
7276
|
}
|
|
7175
|
-
const argSummary = args.url
|
|
7277
|
+
const argSummary = [args.url, args.query, args.text, args.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
7176
7278
|
onChunk(`
|
|
7177
7279
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
7178
7280
|
`);
|
|
@@ -7330,7 +7432,7 @@ async function fetchProviderModels(config) {
|
|
|
7330
7432
|
if (normalized.id === "anthropic") {
|
|
7331
7433
|
const client2 = new Anthropic({ apiKey: normalized.apiKey });
|
|
7332
7434
|
const page2 = await client2.models.list();
|
|
7333
|
-
return {
|
|
7435
|
+
return okResult({ models: page2.data.map((model) => model.id) });
|
|
7334
7436
|
}
|
|
7335
7437
|
const meta = PROVIDERS[normalized.id];
|
|
7336
7438
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
@@ -7342,9 +7444,10 @@ async function fetchProviderModels(config) {
|
|
|
7342
7444
|
const models = page.data.map((model) => model.id);
|
|
7343
7445
|
const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
|
|
7344
7446
|
return {
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7447
|
+
...okResult({
|
|
7448
|
+
models,
|
|
7449
|
+
...warning ? { warning } : {}
|
|
7450
|
+
})
|
|
7348
7451
|
};
|
|
7349
7452
|
}
|
|
7350
7453
|
function createProvider(config) {
|
|
@@ -7359,6 +7462,7 @@ function createProvider(config) {
|
|
|
7359
7462
|
return new OpenAICompatProvider(normalized);
|
|
7360
7463
|
}
|
|
7361
7464
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
7465
|
+
const logger$c = createLogger("DevTrace");
|
|
7362
7466
|
let cachedFactory;
|
|
7363
7467
|
function createNoopTraceSession() {
|
|
7364
7468
|
return {
|
|
@@ -7391,7 +7495,7 @@ function loadLocalFactory() {
|
|
|
7391
7495
|
return cachedFactory;
|
|
7392
7496
|
}
|
|
7393
7497
|
} catch (err) {
|
|
7394
|
-
|
|
7498
|
+
logger$c.warn("Failed to load local trace logger:", err);
|
|
7395
7499
|
}
|
|
7396
7500
|
}
|
|
7397
7501
|
return cachedFactory;
|
|
@@ -11126,6 +11230,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
11126
11230
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
11127
11231
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
11128
11232
|
}
|
|
11233
|
+
const logger$b = createLogger("Screenshot");
|
|
11129
11234
|
async function captureScreenshot(wc) {
|
|
11130
11235
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
11131
11236
|
await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
|
|
@@ -11135,13 +11240,21 @@ async function captureScreenshot(wc) {
|
|
|
11135
11240
|
const size = image.getSize();
|
|
11136
11241
|
const base64 = image.toPNG().toString("base64");
|
|
11137
11242
|
if (base64) {
|
|
11138
|
-
return {
|
|
11243
|
+
return okResult({
|
|
11244
|
+
base64,
|
|
11245
|
+
width: size.width,
|
|
11246
|
+
height: size.height
|
|
11247
|
+
});
|
|
11139
11248
|
}
|
|
11140
11249
|
}
|
|
11141
|
-
} catch {
|
|
11250
|
+
} catch (err) {
|
|
11251
|
+
logger$b.debug(
|
|
11252
|
+
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
11253
|
+
getErrorMessage(err)
|
|
11254
|
+
);
|
|
11142
11255
|
}
|
|
11143
11256
|
}
|
|
11144
|
-
return
|
|
11257
|
+
return errorResult("Page image was empty after 3 attempts");
|
|
11145
11258
|
}
|
|
11146
11259
|
const SESSION_VERSION = 1;
|
|
11147
11260
|
function getSessionsDir() {
|
|
@@ -11749,6 +11862,33 @@ function formatCompactToolResult(name, result) {
|
|
|
11749
11862
|
return limitText(result, 18, 1400);
|
|
11750
11863
|
}
|
|
11751
11864
|
}
|
|
11865
|
+
function normalizeOptionalString(value) {
|
|
11866
|
+
if (typeof value !== "string") return void 0;
|
|
11867
|
+
const trimmed = value.trim();
|
|
11868
|
+
return trimmed || void 0;
|
|
11869
|
+
}
|
|
11870
|
+
function normalizeKeyFields(value) {
|
|
11871
|
+
if (!Array.isArray(value)) return void 0;
|
|
11872
|
+
const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
|
|
11873
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
11874
|
+
}
|
|
11875
|
+
function normalizeAgentHints(value) {
|
|
11876
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11877
|
+
return void 0;
|
|
11878
|
+
}
|
|
11879
|
+
const normalized = Object.fromEntries(
|
|
11880
|
+
Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
|
|
11881
|
+
);
|
|
11882
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
11883
|
+
}
|
|
11884
|
+
function normalizeBookmarkMetadata(input) {
|
|
11885
|
+
return {
|
|
11886
|
+
intent: normalizeOptionalString(input.intent),
|
|
11887
|
+
expectedContent: normalizeOptionalString(input.expectedContent),
|
|
11888
|
+
keyFields: normalizeKeyFields(input.keyFields),
|
|
11889
|
+
agentHints: normalizeAgentHints(input.agentHints)
|
|
11890
|
+
};
|
|
11891
|
+
}
|
|
11752
11892
|
const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
|
|
11753
11893
|
const HUGGING_FACE_MODEL_TASKS = [
|
|
11754
11894
|
{
|
|
@@ -12009,9 +12149,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
12009
12149
|
appliedFilters
|
|
12010
12150
|
};
|
|
12011
12151
|
}
|
|
12152
|
+
const logger$a = createLogger("PageActions");
|
|
12153
|
+
function getBookmarkMetadataFromArgs(args) {
|
|
12154
|
+
return normalizeBookmarkMetadata({
|
|
12155
|
+
intent: args.intent ?? args.intent,
|
|
12156
|
+
expectedContent: args.expectedContent ?? args.expected_content,
|
|
12157
|
+
keyFields: args.keyFields ?? args.key_fields,
|
|
12158
|
+
agentHints: args.agentHints ?? args.agent_hints
|
|
12159
|
+
});
|
|
12160
|
+
}
|
|
12012
12161
|
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
12013
12162
|
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
12014
|
-
async function loadPermittedUrl
|
|
12163
|
+
async function loadPermittedUrl(wc, url) {
|
|
12015
12164
|
assertPermittedNavigationURL(url);
|
|
12016
12165
|
await wc.loadURL(url);
|
|
12017
12166
|
}
|
|
@@ -12184,7 +12333,9 @@ async function executePageScript(wc, script, options) {
|
|
|
12184
12333
|
return PAGE_SCRIPT_TIMEOUT;
|
|
12185
12334
|
}
|
|
12186
12335
|
return result;
|
|
12187
|
-
} catch {
|
|
12336
|
+
} catch (err) {
|
|
12337
|
+
const label = options?.label ? ` (${options.label})` : "";
|
|
12338
|
+
logger$a.warn(`Failed to execute page script${label}:`, err);
|
|
12188
12339
|
return null;
|
|
12189
12340
|
} finally {
|
|
12190
12341
|
if (timer) {
|
|
@@ -12284,7 +12435,8 @@ async function getPostSearchSummary(wc) {
|
|
|
12284
12435
|
Search results snapshot:
|
|
12285
12436
|
${truncated}`;
|
|
12286
12437
|
}
|
|
12287
|
-
} catch {
|
|
12438
|
+
} catch (err) {
|
|
12439
|
+
logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
12288
12440
|
}
|
|
12289
12441
|
const fallback = await getPostNavSummary(wc);
|
|
12290
12442
|
return fallback ? `${fallback}
|
|
@@ -12306,11 +12458,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
|
|
|
12306
12458
|
Page snapshot after navigation:
|
|
12307
12459
|
${truncated}`;
|
|
12308
12460
|
}
|
|
12309
|
-
} catch {
|
|
12461
|
+
} catch (err) {
|
|
12462
|
+
logger$a.warn("Failed to build post-click navigation summary:", err);
|
|
12310
12463
|
}
|
|
12311
12464
|
return "";
|
|
12312
12465
|
}
|
|
12313
|
-
async function scrollPage
|
|
12466
|
+
async function scrollPage(wc, deltaY) {
|
|
12314
12467
|
const getScrollY = async () => {
|
|
12315
12468
|
const scrollY = await executePageScript(
|
|
12316
12469
|
wc,
|
|
@@ -12354,7 +12507,7 @@ async function scrollPage$1(wc, deltaY) {
|
|
|
12354
12507
|
movedY: Math.round(afterY - beforeY)
|
|
12355
12508
|
};
|
|
12356
12509
|
}
|
|
12357
|
-
async function clickElement
|
|
12510
|
+
async function clickElement(wc, selector) {
|
|
12358
12511
|
const target = await executePageScript(
|
|
12359
12512
|
wc,
|
|
12360
12513
|
`
|
|
@@ -12443,7 +12596,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12443
12596
|
return "Error: Could not resolve click coordinates";
|
|
12444
12597
|
}
|
|
12445
12598
|
if (hiddenWindow) {
|
|
12446
|
-
const activationResult = await activateElement
|
|
12599
|
+
const activationResult = await activateElement(wc, selector);
|
|
12447
12600
|
if (activationResult.startsWith("Error:")) {
|
|
12448
12601
|
return activationResult;
|
|
12449
12602
|
}
|
|
@@ -12458,7 +12611,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12458
12611
|
await sleep(80);
|
|
12459
12612
|
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
12460
12613
|
}
|
|
12461
|
-
async function activateElement
|
|
12614
|
+
async function activateElement(wc, selector) {
|
|
12462
12615
|
const activated = await executePageScript(
|
|
12463
12616
|
wc,
|
|
12464
12617
|
`
|
|
@@ -12490,7 +12643,7 @@ async function activateElement$1(wc, selector) {
|
|
|
12490
12643
|
}
|
|
12491
12644
|
return "Activated element via DOM click";
|
|
12492
12645
|
}
|
|
12493
|
-
async function describeElementForClick
|
|
12646
|
+
async function describeElementForClick(wc, selector) {
|
|
12494
12647
|
const result = await executePageScript(
|
|
12495
12648
|
wc,
|
|
12496
12649
|
`
|
|
@@ -12799,7 +12952,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12799
12952
|
return;
|
|
12800
12953
|
}
|
|
12801
12954
|
}
|
|
12802
|
-
} catch {
|
|
12955
|
+
} catch (err) {
|
|
12956
|
+
logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
12803
12957
|
}
|
|
12804
12958
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
12805
12959
|
try {
|
|
@@ -12807,14 +12961,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12807
12961
|
await wc.loadURL(snapshot.url);
|
|
12808
12962
|
await waitForLoad(wc, 3e3);
|
|
12809
12963
|
return;
|
|
12810
|
-
} catch {
|
|
12964
|
+
} catch (err) {
|
|
12965
|
+
logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
12811
12966
|
}
|
|
12812
12967
|
}
|
|
12813
12968
|
if (snapshot.url) {
|
|
12814
12969
|
try {
|
|
12815
12970
|
await wc.reload();
|
|
12816
12971
|
await waitForLoad(wc, 3e3);
|
|
12817
|
-
} catch {
|
|
12972
|
+
} catch (err) {
|
|
12973
|
+
logger$a.warn("Failed to restore locale via page reload:", err);
|
|
12818
12974
|
}
|
|
12819
12975
|
}
|
|
12820
12976
|
}
|
|
@@ -12945,14 +13101,14 @@ Go back to search results to select the next product.`;
|
|
|
12945
13101
|
if (!overlayHint) {
|
|
12946
13102
|
return cartSummary;
|
|
12947
13103
|
}
|
|
12948
|
-
const dialogActions = await getCartDialogActions
|
|
13104
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
12949
13105
|
const actionsSuffix = dialogActions ? `
|
|
12950
13106
|
${dialogActions}
|
|
12951
13107
|
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
12952
13108
|
return `
|
|
12953
13109
|
${overlayHint}${actionsSuffix}${cartSummary}`;
|
|
12954
13110
|
}
|
|
12955
|
-
async function clickResolvedSelector
|
|
13111
|
+
async function clickResolvedSelector(wc, selector) {
|
|
12956
13112
|
if (selector.startsWith("__vessel_idx:")) {
|
|
12957
13113
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
12958
13114
|
const beforeUrl2 = wc.getURL();
|
|
@@ -12985,10 +13141,10 @@ Go back and select a different product.`;
|
|
|
12985
13141
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
12986
13142
|
const afterUrl2 = wc.getURL();
|
|
12987
13143
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
12988
|
-
let idxOverlay = await detectPostClickOverlay
|
|
13144
|
+
let idxOverlay = await detectPostClickOverlay(wc);
|
|
12989
13145
|
if (!idxOverlay && idxCartMatch) {
|
|
12990
13146
|
await sleep(1200);
|
|
12991
|
-
idxOverlay = await detectPostClickOverlay
|
|
13147
|
+
idxOverlay = await detectPostClickOverlay(wc);
|
|
12992
13148
|
}
|
|
12993
13149
|
if (idxCartMatch) {
|
|
12994
13150
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
|
|
@@ -12997,7 +13153,7 @@ Go back and select a different product.`;
|
|
|
12997
13153
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
12998
13154
|
if (hrefMatch) {
|
|
12999
13155
|
try {
|
|
13000
|
-
await loadPermittedUrl
|
|
13156
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13001
13157
|
await waitForLoad(wc, 8e3);
|
|
13002
13158
|
const hrefUrl = wc.getURL();
|
|
13003
13159
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13052,10 +13208,10 @@ Go back and select a different product.`;
|
|
|
13052
13208
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
13053
13209
|
const afterUrl2 = wc.getURL();
|
|
13054
13210
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
13055
|
-
let shadowOverlay = await detectPostClickOverlay
|
|
13211
|
+
let shadowOverlay = await detectPostClickOverlay(wc);
|
|
13056
13212
|
if (!shadowOverlay && shadowCartMatch) {
|
|
13057
13213
|
await sleep(1200);
|
|
13058
|
-
shadowOverlay = await detectPostClickOverlay
|
|
13214
|
+
shadowOverlay = await detectPostClickOverlay(wc);
|
|
13059
13215
|
}
|
|
13060
13216
|
if (shadowCartMatch) {
|
|
13061
13217
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
|
|
@@ -13064,7 +13220,7 @@ Go back and select a different product.`;
|
|
|
13064
13220
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
13065
13221
|
if (hrefMatch) {
|
|
13066
13222
|
try {
|
|
13067
|
-
await loadPermittedUrl
|
|
13223
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13068
13224
|
await waitForLoad(wc, 8e3);
|
|
13069
13225
|
const hrefUrl = wc.getURL();
|
|
13070
13226
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13077,14 +13233,14 @@ ${shadowOverlay}` : `${result}
|
|
|
13077
13233
|
Note: Page did not change after click.`;
|
|
13078
13234
|
}
|
|
13079
13235
|
const beforeUrl = wc.getURL();
|
|
13080
|
-
const elInfo = await describeElementForClick
|
|
13236
|
+
const elInfo = await describeElementForClick(wc, selector);
|
|
13081
13237
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
13082
13238
|
const cartMatch = isAddToCartText(elInfo.text);
|
|
13083
13239
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
13084
13240
|
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).`;
|
|
13085
13241
|
}
|
|
13086
13242
|
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
13087
|
-
const dialogActions = await getCartDialogActions
|
|
13243
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13088
13244
|
if (dialogActions) {
|
|
13089
13245
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
13090
13246
|
${dialogActions}
|
|
@@ -13107,14 +13263,14 @@ Go back and select a different product.`;
|
|
|
13107
13263
|
}
|
|
13108
13264
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
13109
13265
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
13110
|
-
const clickResult = await clickElement
|
|
13266
|
+
const clickResult = await clickElement(wc, selector);
|
|
13111
13267
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
13112
13268
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13113
13269
|
const afterUrl = wc.getURL();
|
|
13114
13270
|
if (afterUrl !== beforeUrl) {
|
|
13115
13271
|
return `${clickText} -> ${afterUrl}`;
|
|
13116
13272
|
}
|
|
13117
|
-
const overlayHint = await detectPostClickOverlay
|
|
13273
|
+
const overlayHint = await detectPostClickOverlay(wc);
|
|
13118
13274
|
if (overlayHint) {
|
|
13119
13275
|
if (cartMatch) {
|
|
13120
13276
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
@@ -13128,7 +13284,7 @@ ${overlayHint}`;
|
|
|
13128
13284
|
}
|
|
13129
13285
|
if (cartMatch) {
|
|
13130
13286
|
await sleep(1200);
|
|
13131
|
-
const delayedOverlayHint = await detectPostClickOverlay
|
|
13287
|
+
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
13132
13288
|
if (delayedOverlayHint) {
|
|
13133
13289
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
13134
13290
|
wc,
|
|
@@ -13141,7 +13297,7 @@ ${overlayHint}`;
|
|
|
13141
13297
|
beforeUrl
|
|
13142
13298
|
)}`;
|
|
13143
13299
|
}
|
|
13144
|
-
const activationResult = await activateElement
|
|
13300
|
+
const activationResult = await activateElement(wc, selector);
|
|
13145
13301
|
if (!activationResult.startsWith("Error:")) {
|
|
13146
13302
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13147
13303
|
const fallbackUrl = wc.getURL();
|
|
@@ -13149,7 +13305,7 @@ ${overlayHint}`;
|
|
|
13149
13305
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
13150
13306
|
}
|
|
13151
13307
|
}
|
|
13152
|
-
const postActivationOverlayHint = await detectPostClickOverlay
|
|
13308
|
+
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
13153
13309
|
if (postActivationOverlayHint) {
|
|
13154
13310
|
return `${clickText} (${clickResult})
|
|
13155
13311
|
${postActivationOverlayHint}`;
|
|
@@ -13159,13 +13315,14 @@ ${postActivationOverlayHint}`;
|
|
|
13159
13315
|
const validation = await validateLinkDestination(elInfo.href);
|
|
13160
13316
|
if (validation.status !== "dead") {
|
|
13161
13317
|
try {
|
|
13162
|
-
await loadPermittedUrl
|
|
13318
|
+
await loadPermittedUrl(wc, elInfo.href);
|
|
13163
13319
|
await waitForLoad(wc, 8e3);
|
|
13164
13320
|
const hrefFallbackUrl = wc.getURL();
|
|
13165
13321
|
if (hrefFallbackUrl !== beforeUrl) {
|
|
13166
13322
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
13167
13323
|
}
|
|
13168
|
-
} catch {
|
|
13324
|
+
} catch (err) {
|
|
13325
|
+
logger$a.warn("Failed href fallback after click, returning generic click result:", err);
|
|
13169
13326
|
}
|
|
13170
13327
|
}
|
|
13171
13328
|
}
|
|
@@ -13209,11 +13366,12 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
13209
13366
|
await sleep(500);
|
|
13210
13367
|
return result;
|
|
13211
13368
|
}
|
|
13212
|
-
} catch {
|
|
13369
|
+
} catch (err) {
|
|
13370
|
+
logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
13213
13371
|
}
|
|
13214
13372
|
return null;
|
|
13215
13373
|
}
|
|
13216
|
-
async function getCartDialogActions
|
|
13374
|
+
async function getCartDialogActions(wc) {
|
|
13217
13375
|
const result = await executePageScript(
|
|
13218
13376
|
wc,
|
|
13219
13377
|
`
|
|
@@ -13258,7 +13416,7 @@ async function getCartDialogActions$1(wc) {
|
|
|
13258
13416
|
return `Available dialog actions:
|
|
13259
13417
|
${result.actions.join("\n")}`;
|
|
13260
13418
|
}
|
|
13261
|
-
async function detectPostClickOverlay
|
|
13419
|
+
async function detectPostClickOverlay(wc) {
|
|
13262
13420
|
const result = await executePageScript(
|
|
13263
13421
|
wc,
|
|
13264
13422
|
`
|
|
@@ -13364,7 +13522,7 @@ async function detectPostClickOverlay$1(wc) {
|
|
|
13364
13522
|
const desc = result.label ? ` ("${result.label}")` : "";
|
|
13365
13523
|
return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
|
|
13366
13524
|
}
|
|
13367
|
-
async function dismissPopup
|
|
13525
|
+
async function dismissPopup(wc) {
|
|
13368
13526
|
const before = await extractContent(wc);
|
|
13369
13527
|
const initialBlocking = before.overlays.filter(
|
|
13370
13528
|
(overlay) => overlay.blocksInteraction
|
|
@@ -13417,7 +13575,7 @@ async function dismissPopup$1(wc) {
|
|
|
13417
13575
|
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
13418
13576
|
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
13419
13577
|
}
|
|
13420
|
-
const dialogActions = await getCartDialogActions
|
|
13578
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13421
13579
|
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."}`;
|
|
13422
13580
|
}
|
|
13423
13581
|
}
|
|
@@ -13577,7 +13735,7 @@ async function dismissPopup$1(wc) {
|
|
|
13577
13735
|
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
13578
13736
|
continue;
|
|
13579
13737
|
}
|
|
13580
|
-
const result = await clickElement
|
|
13738
|
+
const result = await clickElement(wc, candidate.selector);
|
|
13581
13739
|
if (result.startsWith("Error:")) continue;
|
|
13582
13740
|
await sleep(250);
|
|
13583
13741
|
const postClickLocale = await getLocaleSnapshot(wc);
|
|
@@ -13619,7 +13777,7 @@ function describeOverlayState(page) {
|
|
|
13619
13777
|
}
|
|
13620
13778
|
async function clickOverlayCandidate(wc, action) {
|
|
13621
13779
|
if (!action?.selector) return null;
|
|
13622
|
-
const result = await clickResolvedSelector
|
|
13780
|
+
const result = await clickResolvedSelector(wc, action.selector);
|
|
13623
13781
|
return `${action.label || action.selector}: ${result}`;
|
|
13624
13782
|
}
|
|
13625
13783
|
async function tryDismissConsentIframe(wc) {
|
|
@@ -13830,7 +13988,7 @@ Submitted modal: ${submitResult}`;
|
|
|
13830
13988
|
}
|
|
13831
13989
|
}
|
|
13832
13990
|
if (!actionMessage) {
|
|
13833
|
-
actionMessage = `Fallback popup handling: ${await dismissPopup
|
|
13991
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
|
|
13834
13992
|
}
|
|
13835
13993
|
steps.push(actionMessage);
|
|
13836
13994
|
if (overlay.kind === "cookie_consent") {
|
|
@@ -14005,7 +14163,7 @@ async function fillFormFields(wc, fields) {
|
|
|
14005
14163
|
});
|
|
14006
14164
|
continue;
|
|
14007
14165
|
}
|
|
14008
|
-
const result = await setElementValue
|
|
14166
|
+
const result = await setElementValue(
|
|
14009
14167
|
wc,
|
|
14010
14168
|
selector,
|
|
14011
14169
|
String(field.value || "")
|
|
@@ -14014,14 +14172,14 @@ async function fillFormFields(wc, fields) {
|
|
|
14014
14172
|
}
|
|
14015
14173
|
return results;
|
|
14016
14174
|
}
|
|
14017
|
-
function getTabByMatch
|
|
14175
|
+
function getTabByMatch(tabManager, match) {
|
|
14018
14176
|
if (!match) return null;
|
|
14019
14177
|
const lowered = match.toLowerCase();
|
|
14020
14178
|
return tabManager.getAllStates().find(
|
|
14021
14179
|
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
14022
14180
|
) || null;
|
|
14023
14181
|
}
|
|
14024
|
-
function isDangerousAction
|
|
14182
|
+
function isDangerousAction(name) {
|
|
14025
14183
|
return [
|
|
14026
14184
|
"navigate",
|
|
14027
14185
|
"open_bookmark",
|
|
@@ -14032,6 +14190,7 @@ function isDangerousAction$1(name) {
|
|
|
14032
14190
|
"press_key",
|
|
14033
14191
|
"create_tab",
|
|
14034
14192
|
"switch_tab",
|
|
14193
|
+
"close_tab",
|
|
14035
14194
|
"restore_checkpoint",
|
|
14036
14195
|
"load_session",
|
|
14037
14196
|
"login",
|
|
@@ -14040,7 +14199,7 @@ function isDangerousAction$1(name) {
|
|
|
14040
14199
|
"paginate"
|
|
14041
14200
|
].includes(name);
|
|
14042
14201
|
}
|
|
14043
|
-
async function setElementValue
|
|
14202
|
+
async function setElementValue(wc, selector, value) {
|
|
14044
14203
|
if (selector.startsWith("__vessel_idx:")) {
|
|
14045
14204
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
14046
14205
|
const result2 = await executePageScript(
|
|
@@ -14144,7 +14303,7 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
14144
14303
|
);
|
|
14145
14304
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14146
14305
|
}
|
|
14147
|
-
async function typeKeystroke
|
|
14306
|
+
async function typeKeystroke(wc, selector, value) {
|
|
14148
14307
|
const result = await executePageScript(
|
|
14149
14308
|
wc,
|
|
14150
14309
|
`
|
|
@@ -14192,7 +14351,7 @@ async function typeKeystroke$1(wc, selector, value) {
|
|
|
14192
14351
|
);
|
|
14193
14352
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14194
14353
|
}
|
|
14195
|
-
async function hoverElement
|
|
14354
|
+
async function hoverElement(wc, selector) {
|
|
14196
14355
|
const pos = await wc.executeJavaScript(`
|
|
14197
14356
|
(function() {
|
|
14198
14357
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14222,7 +14381,7 @@ async function hoverElement$1(wc, selector) {
|
|
|
14222
14381
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
14223
14382
|
return `Hovered: ${label}`;
|
|
14224
14383
|
}
|
|
14225
|
-
async function focusElement
|
|
14384
|
+
async function focusElement(wc, selector) {
|
|
14226
14385
|
return wc.executeJavaScript(`
|
|
14227
14386
|
(function() {
|
|
14228
14387
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14236,7 +14395,7 @@ async function focusElement$1(wc, selector) {
|
|
|
14236
14395
|
})()
|
|
14237
14396
|
`);
|
|
14238
14397
|
}
|
|
14239
|
-
async function waitForCondition
|
|
14398
|
+
async function waitForCondition(wc, args) {
|
|
14240
14399
|
const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
|
|
14241
14400
|
const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
|
|
14242
14401
|
const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
@@ -14295,8 +14454,8 @@ function findCheckpoint(checkpoints, args) {
|
|
|
14295
14454
|
}
|
|
14296
14455
|
return null;
|
|
14297
14456
|
}
|
|
14298
|
-
function resolveBookmarkFolderTarget
|
|
14299
|
-
const folderId = typeof args.folderId === "string" ? args.folderId.trim() : "";
|
|
14457
|
+
function resolveBookmarkFolderTarget(args) {
|
|
14458
|
+
const folderId = typeof args.folderId === "string" ? args.folderId.trim() : typeof args.folder_id === "string" ? args.folder_id.trim() : "";
|
|
14300
14459
|
if (folderId) {
|
|
14301
14460
|
if (folderId === UNSORTED_ID) {
|
|
14302
14461
|
return {
|
|
@@ -14310,7 +14469,7 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14310
14469
|
}
|
|
14311
14470
|
return { folderId: folder2.id, folderName: folder2.name };
|
|
14312
14471
|
}
|
|
14313
|
-
const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
14472
|
+
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 : "";
|
|
14314
14473
|
if (!folderName || folderName.toLowerCase() === "unsorted") {
|
|
14315
14474
|
return {
|
|
14316
14475
|
folderId: UNSORTED_ID,
|
|
@@ -14321,10 +14480,11 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14321
14480
|
if (existing) {
|
|
14322
14481
|
return { folderId: existing.id, folderName: existing.name };
|
|
14323
14482
|
}
|
|
14324
|
-
|
|
14483
|
+
const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
|
|
14484
|
+
if (createIfMissing === false) {
|
|
14325
14485
|
return { folderName, error: `Folder "${folderName}" not found` };
|
|
14326
14486
|
}
|
|
14327
|
-
const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : void 0;
|
|
14487
|
+
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;
|
|
14328
14488
|
const { folder } = ensureFolder(folderName, folderSummary);
|
|
14329
14489
|
return {
|
|
14330
14490
|
folderId: folder.id,
|
|
@@ -14332,27 +14492,27 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14332
14492
|
createdFolder: folder.name
|
|
14333
14493
|
};
|
|
14334
14494
|
}
|
|
14335
|
-
function formatFolderStatus
|
|
14495
|
+
function formatFolderStatus(limit = 6) {
|
|
14336
14496
|
const folders = listFolderOverviews();
|
|
14337
14497
|
const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
|
|
14338
14498
|
return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
|
|
14339
14499
|
}
|
|
14340
|
-
function describeFolder
|
|
14500
|
+
function describeFolder(folderId) {
|
|
14341
14501
|
if (!folderId || folderId === UNSORTED_ID) {
|
|
14342
14502
|
return "Unsorted";
|
|
14343
14503
|
}
|
|
14344
14504
|
return getFolder(folderId)?.name ?? folderId;
|
|
14345
14505
|
}
|
|
14346
|
-
function composeDuplicateBookmarkResponse
|
|
14506
|
+
function composeDuplicateBookmarkResponse(args) {
|
|
14347
14507
|
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.`;
|
|
14348
14508
|
}
|
|
14349
|
-
function composeFolderAwareResponse
|
|
14509
|
+
function composeFolderAwareResponse(message, createdFolder) {
|
|
14350
14510
|
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
14351
14511
|
` : "";
|
|
14352
14512
|
return `${prefix}${message}
|
|
14353
|
-
${formatFolderStatus
|
|
14513
|
+
${formatFolderStatus()}`;
|
|
14354
14514
|
}
|
|
14355
|
-
async function selectOption
|
|
14515
|
+
async function selectOption(wc, args) {
|
|
14356
14516
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
14357
14517
|
if (!selector) return "Error: No select element index or selector provided";
|
|
14358
14518
|
const result = await executePageScript(
|
|
@@ -14388,7 +14548,7 @@ async function selectOption$1(wc, args) {
|
|
|
14388
14548
|
);
|
|
14389
14549
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
|
|
14390
14550
|
}
|
|
14391
|
-
async function submitForm
|
|
14551
|
+
async function submitForm(wc, args) {
|
|
14392
14552
|
const beforeUrl = wc.getURL();
|
|
14393
14553
|
let selector = await resolveSelector(wc, args.index, args.selector);
|
|
14394
14554
|
if (!selector) {
|
|
@@ -14529,7 +14689,7 @@ async function submitForm$1(wc, args) {
|
|
|
14529
14689
|
if (formInfo.params) {
|
|
14530
14690
|
url.search = formInfo.params;
|
|
14531
14691
|
}
|
|
14532
|
-
await loadPermittedUrl
|
|
14692
|
+
await loadPermittedUrl(wc, url.toString());
|
|
14533
14693
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
14534
14694
|
const afterUrl = wc.getURL();
|
|
14535
14695
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
@@ -14571,8 +14731,20 @@ async function submitForm$1(wc, args) {
|
|
|
14571
14731
|
}
|
|
14572
14732
|
return "Submitted form";
|
|
14573
14733
|
}
|
|
14734
|
+
async function pressKeyDirect(wc, key, index, selector) {
|
|
14735
|
+
return pressKey(wc, { key, index, selector });
|
|
14736
|
+
}
|
|
14737
|
+
async function submitFormDirect(wc, index, selector) {
|
|
14738
|
+
return submitForm(wc, { index, selector });
|
|
14739
|
+
}
|
|
14740
|
+
async function selectOptionDirect(wc, index, selector, label, value) {
|
|
14741
|
+
return selectOption(wc, { index, selector, label, value });
|
|
14742
|
+
}
|
|
14743
|
+
async function waitForConditionDirect(wc, text, selector, timeoutMs) {
|
|
14744
|
+
return waitForCondition(wc, { text, selector, timeoutMs });
|
|
14745
|
+
}
|
|
14574
14746
|
async function clickElementBySelector(wc, selector) {
|
|
14575
|
-
return clickResolvedSelector
|
|
14747
|
+
return clickResolvedSelector(wc, selector);
|
|
14576
14748
|
}
|
|
14577
14749
|
function normalizeSearchQuery(query) {
|
|
14578
14750
|
return query.replace(/\s+/g, " ").trim();
|
|
@@ -14856,7 +15028,7 @@ async function searchPage(wc, args) {
|
|
|
14856
15028
|
const shortcut = buildSearchShortcut(wc.getURL(), query);
|
|
14857
15029
|
if (shortcut) {
|
|
14858
15030
|
const beforeUrl2 = wc.getURL();
|
|
14859
|
-
await loadPermittedUrl
|
|
15031
|
+
await loadPermittedUrl(wc, shortcut.url);
|
|
14860
15032
|
await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
|
|
14861
15033
|
const afterUrl2 = wc.getURL();
|
|
14862
15034
|
const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
|
|
@@ -14874,13 +15046,13 @@ async function searchPage(wc, args) {
|
|
|
14874
15046
|
if (!searchInfo?.selector) {
|
|
14875
15047
|
return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
|
|
14876
15048
|
}
|
|
14877
|
-
const fillResult = await setElementValue
|
|
15049
|
+
const fillResult = await setElementValue(wc, searchInfo.selector, query);
|
|
14878
15050
|
if (fillResult.startsWith("Error:")) {
|
|
14879
15051
|
return fillResult;
|
|
14880
15052
|
}
|
|
14881
15053
|
await sleep(100);
|
|
14882
15054
|
const beforeUrl = wc.getURL();
|
|
14883
|
-
const keyResult = await pressKey
|
|
15055
|
+
const keyResult = await pressKey(wc, {
|
|
14884
15056
|
key: "Enter",
|
|
14885
15057
|
selector: searchInfo.selector
|
|
14886
15058
|
});
|
|
@@ -14904,7 +15076,7 @@ async function searchPage(wc, args) {
|
|
|
14904
15076
|
}
|
|
14905
15077
|
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
|
|
14906
15078
|
}
|
|
14907
|
-
async function pressKey
|
|
15079
|
+
async function pressKey(wc, args) {
|
|
14908
15080
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
14909
15081
|
if (!key) return "Error: No key provided";
|
|
14910
15082
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
@@ -15116,9 +15288,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
15116
15288
|
"metrics",
|
|
15117
15289
|
"wait_for_navigation"
|
|
15118
15290
|
]);
|
|
15119
|
-
async function executeAction(name,
|
|
15291
|
+
async function executeAction(name, args, ctx) {
|
|
15120
15292
|
name = normalizeToolAlias(name);
|
|
15121
|
-
const args = rawArgs;
|
|
15122
15293
|
if (!KNOWN_TOOLS.has(name)) {
|
|
15123
15294
|
for (const known of KNOWN_TOOLS) {
|
|
15124
15295
|
if (name.startsWith(known) && name.length > known.length) {
|
|
@@ -15167,7 +15338,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15167
15338
|
name,
|
|
15168
15339
|
args,
|
|
15169
15340
|
tabId,
|
|
15170
|
-
dangerous: isDangerousAction
|
|
15341
|
+
dangerous: isDangerousAction(name),
|
|
15171
15342
|
executor: async () => {
|
|
15172
15343
|
switch (name) {
|
|
15173
15344
|
case "screenshot": {
|
|
@@ -15215,7 +15386,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15215
15386
|
case "switch_tab": {
|
|
15216
15387
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15217
15388
|
if (!targetId) {
|
|
15218
|
-
targetId = getTabByMatch
|
|
15389
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15219
15390
|
}
|
|
15220
15391
|
if (!targetId) return "Error: No matching tab found";
|
|
15221
15392
|
ctx.tabManager.switchTab(targetId);
|
|
@@ -15309,7 +15480,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15309
15480
|
if (!selector) {
|
|
15310
15481
|
return "Error: No element index, selector, or visible text provided";
|
|
15311
15482
|
}
|
|
15312
|
-
return clickResolvedSelector
|
|
15483
|
+
return clickResolvedSelector(wc, selector);
|
|
15313
15484
|
}
|
|
15314
15485
|
case "inspect_element": {
|
|
15315
15486
|
if (!wc) return "Error: No active tab";
|
|
@@ -15341,18 +15512,18 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15341
15512
|
if (!selector) return "Error: No element index or selector provided";
|
|
15342
15513
|
const mode = typeof args.mode === "string" ? args.mode : "default";
|
|
15343
15514
|
if (mode === "keystroke") {
|
|
15344
|
-
return typeKeystroke
|
|
15515
|
+
return typeKeystroke(wc, selector, String(args.text || ""));
|
|
15345
15516
|
}
|
|
15346
|
-
return setElementValue
|
|
15517
|
+
return setElementValue(wc, selector, String(args.text || ""));
|
|
15347
15518
|
}
|
|
15348
15519
|
case "select_option": {
|
|
15349
15520
|
if (!wc) return "Error: No active tab";
|
|
15350
|
-
return selectOption
|
|
15521
|
+
return selectOption(wc, args);
|
|
15351
15522
|
}
|
|
15352
15523
|
case "submit_form": {
|
|
15353
15524
|
if (!wc) return "Error: No active tab";
|
|
15354
15525
|
const beforeUrl = wc.getURL();
|
|
15355
|
-
const result2 = await submitForm
|
|
15526
|
+
const result2 = await submitForm(wc, args);
|
|
15356
15527
|
if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
|
|
15357
15528
|
return result2;
|
|
15358
15529
|
}
|
|
@@ -15363,7 +15534,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15363
15534
|
case "press_key": {
|
|
15364
15535
|
if (!wc) return "Error: No active tab";
|
|
15365
15536
|
const beforeUrl = wc.getURL();
|
|
15366
|
-
const result2 = await pressKey
|
|
15537
|
+
const result2 = await pressKey(wc, args);
|
|
15367
15538
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
15368
15539
|
if (key === "Enter") {
|
|
15369
15540
|
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
@@ -15380,20 +15551,20 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15380
15551
|
if (!wc) return "Error: No active tab";
|
|
15381
15552
|
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
15382
15553
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
15383
|
-
const result2 = await scrollPage
|
|
15554
|
+
const result2 = await scrollPage(wc, dir);
|
|
15384
15555
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
15385
15556
|
}
|
|
15386
15557
|
case "hover": {
|
|
15387
15558
|
if (!wc) return "Error: No active tab";
|
|
15388
15559
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15389
15560
|
if (!selector) return "Error: No element index or selector provided";
|
|
15390
|
-
return hoverElement
|
|
15561
|
+
return hoverElement(wc, selector);
|
|
15391
15562
|
}
|
|
15392
15563
|
case "focus": {
|
|
15393
15564
|
if (!wc) return "Error: No active tab";
|
|
15394
15565
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15395
15566
|
if (!selector) return "Error: No element index or selector provided";
|
|
15396
|
-
return focusElement
|
|
15567
|
+
return focusElement(wc, selector);
|
|
15397
15568
|
}
|
|
15398
15569
|
case "set_ad_blocking": {
|
|
15399
15570
|
const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
|
|
@@ -15402,7 +15573,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15402
15573
|
}
|
|
15403
15574
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15404
15575
|
if (!targetId) {
|
|
15405
|
-
targetId = getTabByMatch
|
|
15576
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15406
15577
|
}
|
|
15407
15578
|
if (!targetId) {
|
|
15408
15579
|
targetId = ctx.tabManager.getActiveTabId() || "";
|
|
@@ -15421,7 +15592,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15421
15592
|
}
|
|
15422
15593
|
case "dismiss_popup": {
|
|
15423
15594
|
if (!wc) return "Error: No active tab";
|
|
15424
|
-
return dismissPopup
|
|
15595
|
+
return dismissPopup(wc);
|
|
15425
15596
|
}
|
|
15426
15597
|
case "clear_overlays": {
|
|
15427
15598
|
if (!wc) return "Error: No active tab";
|
|
@@ -15444,7 +15615,8 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15444
15615
|
}, 6e3)
|
|
15445
15616
|
)
|
|
15446
15617
|
]);
|
|
15447
|
-
} catch {
|
|
15618
|
+
} catch (err) {
|
|
15619
|
+
logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
15448
15620
|
content = null;
|
|
15449
15621
|
}
|
|
15450
15622
|
if (!content || content.content.length === 0) {
|
|
@@ -15460,11 +15632,13 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15460
15632
|
extractContent(wc),
|
|
15461
15633
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
15462
15634
|
]);
|
|
15463
|
-
} catch {
|
|
15635
|
+
} catch (err) {
|
|
15636
|
+
logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
15464
15637
|
content = null;
|
|
15465
15638
|
}
|
|
15466
15639
|
}
|
|
15467
|
-
} catch {
|
|
15640
|
+
} catch (err) {
|
|
15641
|
+
logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
15468
15642
|
}
|
|
15469
15643
|
}
|
|
15470
15644
|
if (content && content.content.length > 0) {
|
|
@@ -15504,7 +15678,7 @@ ${truncated}`;
|
|
|
15504
15678
|
}
|
|
15505
15679
|
case "wait_for": {
|
|
15506
15680
|
if (!wc) return "Error: No active tab";
|
|
15507
|
-
return waitForCondition
|
|
15681
|
+
return waitForCondition(wc, args);
|
|
15508
15682
|
}
|
|
15509
15683
|
case "wait_for_navigation": {
|
|
15510
15684
|
if (!wc) return "Error: No active tab";
|
|
@@ -15647,12 +15821,12 @@ ${truncated}`;
|
|
|
15647
15821
|
(folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
|
|
15648
15822
|
);
|
|
15649
15823
|
if (existing) {
|
|
15650
|
-
return composeFolderAwareResponse
|
|
15824
|
+
return composeFolderAwareResponse(
|
|
15651
15825
|
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
15652
15826
|
);
|
|
15653
15827
|
}
|
|
15654
15828
|
const folder = createFolderWithSummary(name2, summary);
|
|
15655
|
-
return composeFolderAwareResponse
|
|
15829
|
+
return composeFolderAwareResponse(
|
|
15656
15830
|
`Created folder "${folder.name}" (id=${folder.id})`
|
|
15657
15831
|
);
|
|
15658
15832
|
}
|
|
@@ -15664,7 +15838,7 @@ ${truncated}`;
|
|
|
15664
15838
|
resolvedSelector
|
|
15665
15839
|
});
|
|
15666
15840
|
if ("error" in source) return `Error: ${source.error}`;
|
|
15667
|
-
const target = resolveBookmarkFolderTarget
|
|
15841
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15668
15842
|
if (target.error) return target.error;
|
|
15669
15843
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
15670
15844
|
const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
|
|
@@ -15675,21 +15849,14 @@ ${truncated}`;
|
|
|
15675
15849
|
note,
|
|
15676
15850
|
{
|
|
15677
15851
|
onDuplicate,
|
|
15678
|
-
extra:
|
|
15679
|
-
intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
|
|
15680
|
-
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15681
|
-
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15682
|
-
(f) => typeof f === "string"
|
|
15683
|
-
) : void 0,
|
|
15684
|
-
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15685
|
-
}
|
|
15852
|
+
extra: getBookmarkMetadataFromArgs(args)
|
|
15686
15853
|
}
|
|
15687
15854
|
);
|
|
15688
15855
|
if (result2.status === "conflict" && result2.existing) {
|
|
15689
|
-
return composeFolderAwareResponse
|
|
15690
|
-
composeDuplicateBookmarkResponse
|
|
15856
|
+
return composeFolderAwareResponse(
|
|
15857
|
+
composeDuplicateBookmarkResponse({
|
|
15691
15858
|
url: source.url,
|
|
15692
|
-
folderName: describeFolder
|
|
15859
|
+
folderName: describeFolder(target.folderId),
|
|
15693
15860
|
bookmarkId: result2.existing.id
|
|
15694
15861
|
}),
|
|
15695
15862
|
target.createdFolder
|
|
@@ -15698,13 +15865,13 @@ ${truncated}`;
|
|
|
15698
15865
|
const bookmark = result2.bookmark;
|
|
15699
15866
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15700
15867
|
const verb = result2.status === "updated" ? "Updated" : "Saved";
|
|
15701
|
-
return composeFolderAwareResponse
|
|
15702
|
-
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder
|
|
15868
|
+
return composeFolderAwareResponse(
|
|
15869
|
+
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15703
15870
|
target.createdFolder
|
|
15704
15871
|
);
|
|
15705
15872
|
}
|
|
15706
15873
|
case "organize_bookmark": {
|
|
15707
|
-
const target = resolveBookmarkFolderTarget
|
|
15874
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15708
15875
|
if (target.error) return target.error;
|
|
15709
15876
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15710
15877
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15723,18 +15890,13 @@ ${truncated}`;
|
|
|
15723
15890
|
folderId: target.folderId,
|
|
15724
15891
|
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
|
|
15725
15892
|
note,
|
|
15726
|
-
|
|
15727
|
-
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15728
|
-
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15729
|
-
(f) => typeof f === "string"
|
|
15730
|
-
) : void 0,
|
|
15731
|
-
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15893
|
+
...getBookmarkMetadataFromArgs(args)
|
|
15732
15894
|
});
|
|
15733
15895
|
if (!updated) {
|
|
15734
15896
|
return `Bookmark ${existing.id} not found`;
|
|
15735
15897
|
}
|
|
15736
|
-
return composeFolderAwareResponse
|
|
15737
|
-
`Organized existing bookmark "${updated.title}" into "${describeFolder
|
|
15898
|
+
return composeFolderAwareResponse(
|
|
15899
|
+
`Organized existing bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15738
15900
|
target.createdFolder
|
|
15739
15901
|
);
|
|
15740
15902
|
}
|
|
@@ -15746,25 +15908,18 @@ ${truncated}`;
|
|
|
15746
15908
|
note,
|
|
15747
15909
|
{
|
|
15748
15910
|
onDuplicate: "update",
|
|
15749
|
-
extra:
|
|
15750
|
-
intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
|
|
15751
|
-
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15752
|
-
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15753
|
-
(f) => typeof f === "string"
|
|
15754
|
-
) : void 0,
|
|
15755
|
-
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15756
|
-
}
|
|
15911
|
+
extra: getBookmarkMetadataFromArgs(args)
|
|
15757
15912
|
}
|
|
15758
15913
|
);
|
|
15759
15914
|
const bookmark = result2.bookmark;
|
|
15760
15915
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15761
|
-
return composeFolderAwareResponse
|
|
15762
|
-
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder
|
|
15916
|
+
return composeFolderAwareResponse(
|
|
15917
|
+
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15763
15918
|
target.createdFolder
|
|
15764
15919
|
);
|
|
15765
15920
|
}
|
|
15766
15921
|
case "archive_bookmark": {
|
|
15767
|
-
const target = resolveBookmarkFolderTarget
|
|
15922
|
+
const target = resolveBookmarkFolderTarget({ archive: true });
|
|
15768
15923
|
if (target.error) return target.error;
|
|
15769
15924
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15770
15925
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15787,8 +15942,8 @@ ${truncated}`;
|
|
|
15787
15942
|
if (!updated) {
|
|
15788
15943
|
return `Bookmark ${existing.id} not found`;
|
|
15789
15944
|
}
|
|
15790
|
-
return composeFolderAwareResponse
|
|
15791
|
-
`Archived bookmark "${updated.title}" into "${describeFolder
|
|
15945
|
+
return composeFolderAwareResponse(
|
|
15946
|
+
`Archived bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15792
15947
|
target.createdFolder
|
|
15793
15948
|
);
|
|
15794
15949
|
}
|
|
@@ -15801,8 +15956,8 @@ ${truncated}`;
|
|
|
15801
15956
|
target.folderId,
|
|
15802
15957
|
note
|
|
15803
15958
|
);
|
|
15804
|
-
return composeFolderAwareResponse
|
|
15805
|
-
`Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder
|
|
15959
|
+
return composeFolderAwareResponse(
|
|
15960
|
+
`Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15806
15961
|
target.createdFolder
|
|
15807
15962
|
);
|
|
15808
15963
|
}
|
|
@@ -15890,7 +16045,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15890
16045
|
let page;
|
|
15891
16046
|
try {
|
|
15892
16047
|
page = await extractContent(wc);
|
|
15893
|
-
} catch {
|
|
16048
|
+
} catch (err) {
|
|
16049
|
+
logger$a.warn("Failed to extract content for suggest:", err);
|
|
15894
16050
|
return "Could not read page. Try navigate to a working URL.";
|
|
15895
16051
|
}
|
|
15896
16052
|
const suggestions = [];
|
|
@@ -15995,7 +16151,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15995
16151
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
15996
16152
|
if (firstSel) {
|
|
15997
16153
|
const beforeUrl = wc.getURL();
|
|
15998
|
-
const submitResult = await submitForm
|
|
16154
|
+
const submitResult = await submitForm(wc, { selector: firstSel });
|
|
15999
16155
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
16000
16156
|
const afterUrl = wc.getURL();
|
|
16001
16157
|
results.push(
|
|
@@ -16043,13 +16199,13 @@ ${results.join("\n")}`;
|
|
|
16043
16199
|
);
|
|
16044
16200
|
if (!passSel)
|
|
16045
16201
|
return "Error: Could not find password field. Try providing password_selector.";
|
|
16046
|
-
const userResult = await setElementValue
|
|
16202
|
+
const userResult = await setElementValue(
|
|
16047
16203
|
wc,
|
|
16048
16204
|
userSel,
|
|
16049
16205
|
String(args.username || "")
|
|
16050
16206
|
);
|
|
16051
16207
|
steps.push(userResult);
|
|
16052
|
-
const passResult = await setElementValue
|
|
16208
|
+
const passResult = await setElementValue(
|
|
16053
16209
|
wc,
|
|
16054
16210
|
passSel,
|
|
16055
16211
|
String(args.password || "")
|
|
@@ -16057,7 +16213,7 @@ ${results.join("\n")}`;
|
|
|
16057
16213
|
steps.push(passResult);
|
|
16058
16214
|
const beforeUrl = wc.getURL();
|
|
16059
16215
|
if (args.submit_selector) {
|
|
16060
|
-
await clickResolvedSelector
|
|
16216
|
+
await clickResolvedSelector(wc, args.submit_selector);
|
|
16061
16217
|
} else {
|
|
16062
16218
|
const clicked = await executePageScript(
|
|
16063
16219
|
wc,
|
|
@@ -16096,7 +16252,7 @@ ${steps.join("\n")}`;
|
|
|
16096
16252
|
if (!wc) return "Error: No active tab";
|
|
16097
16253
|
const beforeUrl = wc.getURL();
|
|
16098
16254
|
if (args.selector) {
|
|
16099
|
-
return clickResolvedSelector
|
|
16255
|
+
return clickResolvedSelector(wc, args.selector);
|
|
16100
16256
|
}
|
|
16101
16257
|
const isNext = args.direction === "next";
|
|
16102
16258
|
const clicked = await executePageScript(
|
|
@@ -17221,6 +17377,7 @@ const ALGORITHM = "aes-256-gcm";
|
|
|
17221
17377
|
const IV_LENGTH = 12;
|
|
17222
17378
|
const AUTH_TAG_LENGTH = 16;
|
|
17223
17379
|
let cachedEntries = null;
|
|
17380
|
+
const logger$9 = createLogger("Vault");
|
|
17224
17381
|
function getVaultDir() {
|
|
17225
17382
|
return electron.app.getPath("userData");
|
|
17226
17383
|
}
|
|
@@ -17287,7 +17444,7 @@ function loadVault() {
|
|
|
17287
17444
|
cachedEntries = JSON.parse(json);
|
|
17288
17445
|
return cachedEntries;
|
|
17289
17446
|
} catch (err) {
|
|
17290
|
-
|
|
17447
|
+
logger$9.error("Failed to load vault:", err);
|
|
17291
17448
|
throw new Error(
|
|
17292
17449
|
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
17293
17450
|
);
|
|
@@ -17429,6 +17586,7 @@ async function requestConsent(request) {
|
|
|
17429
17586
|
}
|
|
17430
17587
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
17431
17588
|
const MAX_ENTRIES = 1e3;
|
|
17589
|
+
const logger$8 = createLogger("VaultAudit");
|
|
17432
17590
|
function getAuditPath() {
|
|
17433
17591
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
17434
17592
|
}
|
|
@@ -17438,7 +17596,7 @@ function appendAuditEntry(entry) {
|
|
|
17438
17596
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
17439
17597
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
17440
17598
|
} catch (err) {
|
|
17441
|
-
|
|
17599
|
+
logger$8.error("Failed to write audit log:", err);
|
|
17442
17600
|
}
|
|
17443
17601
|
}
|
|
17444
17602
|
function readAuditLog(limit = 100) {
|
|
@@ -17448,12 +17606,13 @@ function readAuditLog(limit = 100) {
|
|
|
17448
17606
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
17449
17607
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
17450
17608
|
} catch (err) {
|
|
17451
|
-
|
|
17609
|
+
logger$8.error("Failed to read audit log:", err);
|
|
17452
17610
|
return [];
|
|
17453
17611
|
}
|
|
17454
17612
|
}
|
|
17455
17613
|
let httpServer = null;
|
|
17456
17614
|
let mcpAuthToken = null;
|
|
17615
|
+
const logger$7 = createLogger("MCP");
|
|
17457
17616
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
17458
17617
|
function getMcpAuthFilePath() {
|
|
17459
17618
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -17489,7 +17648,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
17489
17648
|
{ mode: 384 }
|
|
17490
17649
|
);
|
|
17491
17650
|
} catch (err) {
|
|
17492
|
-
|
|
17651
|
+
logger$7.warn("Failed to write auth file:", err);
|
|
17493
17652
|
}
|
|
17494
17653
|
}
|
|
17495
17654
|
function clearMcpAuthFile() {
|
|
@@ -17514,15 +17673,17 @@ function clearMcpAuthFile() {
|
|
|
17514
17673
|
{ mode: 384 }
|
|
17515
17674
|
);
|
|
17516
17675
|
} catch (err) {
|
|
17517
|
-
|
|
17676
|
+
logger$7.warn("Failed to clear auth file:", err);
|
|
17518
17677
|
}
|
|
17519
17678
|
}
|
|
17520
17679
|
function asTextResponse(text) {
|
|
17521
17680
|
return { content: [{ type: "text", text }] };
|
|
17522
17681
|
}
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17682
|
+
function asErrorTextResponse(message) {
|
|
17683
|
+
return asTextResponse(`Error: ${message}`);
|
|
17684
|
+
}
|
|
17685
|
+
function asNoActiveTabResponse() {
|
|
17686
|
+
return asErrorTextResponse("No active tab");
|
|
17526
17687
|
}
|
|
17527
17688
|
function asPromptResponse(text) {
|
|
17528
17689
|
return {
|
|
@@ -17537,6 +17698,9 @@ function asPromptResponse(text) {
|
|
|
17537
17698
|
]
|
|
17538
17699
|
};
|
|
17539
17700
|
}
|
|
17701
|
+
function isDangerousMcpAction(name) {
|
|
17702
|
+
return name === "close_tab" || isDangerousAction(name);
|
|
17703
|
+
}
|
|
17540
17704
|
function getActiveTabSummary(tabManager) {
|
|
17541
17705
|
const activeTab = tabManager.getActiveTab();
|
|
17542
17706
|
const activeTabId = tabManager.getActiveTabId();
|
|
@@ -17553,1298 +17717,162 @@ function getActiveTabSummary(tabManager) {
|
|
|
17553
17717
|
humanFocused: true
|
|
17554
17718
|
};
|
|
17555
17719
|
}
|
|
17556
|
-
function
|
|
17557
|
-
const
|
|
17558
|
-
|
|
17559
|
-
const
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17720
|
+
async function getPostActionState(tabManager, name) {
|
|
17721
|
+
const tab = tabManager.getActiveTab();
|
|
17722
|
+
if (!tab) return "";
|
|
17723
|
+
const wc = tab.view.webContents;
|
|
17724
|
+
const navActions = [
|
|
17725
|
+
"navigate",
|
|
17726
|
+
"go_back",
|
|
17727
|
+
"go_forward",
|
|
17728
|
+
"click",
|
|
17729
|
+
"submit_form",
|
|
17730
|
+
"reload",
|
|
17731
|
+
"press_key"
|
|
17732
|
+
];
|
|
17733
|
+
const interactActions = [
|
|
17734
|
+
"type",
|
|
17735
|
+
"type_text",
|
|
17736
|
+
"select_option",
|
|
17737
|
+
"hover",
|
|
17738
|
+
"focus"
|
|
17739
|
+
];
|
|
17740
|
+
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
17741
|
+
if (navActions.includes(name)) {
|
|
17742
|
+
let warning = "";
|
|
17743
|
+
try {
|
|
17744
|
+
const page = await extractContent(wc);
|
|
17745
|
+
const issue = getRecoverableAccessIssue(page);
|
|
17746
|
+
if (issue) {
|
|
17747
|
+
const blockedUrl = wc.getURL();
|
|
17748
|
+
const canRecover = [
|
|
17749
|
+
"navigate",
|
|
17750
|
+
"open_bookmark",
|
|
17751
|
+
"click",
|
|
17752
|
+
"submit_form",
|
|
17753
|
+
"reload",
|
|
17754
|
+
"press_key"
|
|
17755
|
+
].includes(name) && tab.canGoBack();
|
|
17756
|
+
if (canRecover && tab.goBack()) {
|
|
17757
|
+
await waitForLoad(wc);
|
|
17758
|
+
warning = `
|
|
17759
|
+
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
17760
|
+
} else {
|
|
17761
|
+
warning = `
|
|
17762
|
+
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
17763
|
+
}
|
|
17764
|
+
}
|
|
17765
|
+
} catch (err) {
|
|
17766
|
+
logger$7.warn("Failed to compute post-action state warning:", err);
|
|
17767
|
+
}
|
|
17768
|
+
return `${warning}
|
|
17769
|
+
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
17565
17770
|
}
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
const folderId = typeof args.folder_id === "string" ? args.folder_id.trim() : "";
|
|
17570
|
-
if (folderId) {
|
|
17571
|
-
if (folderId === UNSORTED_ID) {
|
|
17572
|
-
return {
|
|
17573
|
-
folderId: UNSORTED_ID,
|
|
17574
|
-
folderName: "Unsorted"
|
|
17575
|
-
};
|
|
17576
|
-
}
|
|
17577
|
-
const folder2 = getFolder(folderId);
|
|
17578
|
-
if (!folder2) {
|
|
17579
|
-
return { folderName: "Unsorted", error: `Folder ${folderId} not found` };
|
|
17580
|
-
}
|
|
17581
|
-
return { folderId: folder2.id, folderName: folder2.name };
|
|
17582
|
-
}
|
|
17583
|
-
const requestedName = typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
17584
|
-
if (!requestedName || requestedName.toLowerCase() === "unsorted") {
|
|
17585
|
-
return {
|
|
17586
|
-
folderId: UNSORTED_ID,
|
|
17587
|
-
folderName: "Unsorted"
|
|
17588
|
-
};
|
|
17589
|
-
}
|
|
17590
|
-
const existing = findFolderByName(requestedName);
|
|
17591
|
-
if (existing) {
|
|
17592
|
-
return { folderId: existing.id, folderName: existing.name };
|
|
17593
|
-
}
|
|
17594
|
-
const createIfMissing = args.create_folder_if_missing !== false;
|
|
17595
|
-
if (!createIfMissing) {
|
|
17596
|
-
return {
|
|
17597
|
-
folderName: requestedName,
|
|
17598
|
-
error: `Folder "${requestedName}" not found`
|
|
17599
|
-
};
|
|
17600
|
-
}
|
|
17601
|
-
const folderSummary = typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
|
|
17602
|
-
const { folder } = ensureFolder(requestedName, folderSummary);
|
|
17603
|
-
return {
|
|
17604
|
-
folderId: folder.id,
|
|
17605
|
-
folderName: folder.name,
|
|
17606
|
-
createdFolder: folder.name
|
|
17607
|
-
};
|
|
17608
|
-
}
|
|
17609
|
-
function composeFolderAwareResponse(message, createdFolder) {
|
|
17610
|
-
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
17611
|
-
` : "";
|
|
17612
|
-
return `${prefix}${message}
|
|
17613
|
-
${formatFolderStatus()}`;
|
|
17614
|
-
}
|
|
17615
|
-
function composeDuplicateBookmarkResponse(args) {
|
|
17616
|
-
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.`;
|
|
17617
|
-
}
|
|
17618
|
-
async function scrollPage(wc, deltaY) {
|
|
17619
|
-
const getScrollY = () => wc.executeJavaScript(`
|
|
17620
|
-
(function() {
|
|
17621
|
-
return Math.max(
|
|
17622
|
-
window.scrollY || 0,
|
|
17623
|
-
window.pageYOffset || 0,
|
|
17624
|
-
document.scrollingElement?.scrollTop || 0,
|
|
17625
|
-
document.documentElement?.scrollTop || 0,
|
|
17626
|
-
document.body?.scrollTop || 0,
|
|
17627
|
-
);
|
|
17628
|
-
})()
|
|
17629
|
-
`);
|
|
17630
|
-
const beforeY = await getScrollY();
|
|
17631
|
-
await wc.executeJavaScript(`window.scrollBy(0, ${deltaY})`);
|
|
17632
|
-
await sleep(100);
|
|
17633
|
-
const afterY = await getScrollY();
|
|
17634
|
-
return {
|
|
17635
|
-
beforeY,
|
|
17636
|
-
afterY,
|
|
17637
|
-
movedY: Math.round(afterY - beforeY)
|
|
17638
|
-
};
|
|
17639
|
-
}
|
|
17640
|
-
async function clickElement(wc, selector) {
|
|
17641
|
-
const target = await wc.executeJavaScript(`
|
|
17642
|
-
(async function() {
|
|
17643
|
-
function matchesTarget(candidate, el) {
|
|
17644
|
-
return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
|
|
17645
|
-
}
|
|
17646
|
-
|
|
17647
|
-
function samplePoints(rect) {
|
|
17648
|
-
const width = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
17649
|
-
const height = window.innerHeight || document.documentElement?.clientHeight || 0;
|
|
17650
|
-
const insetX = Math.min(12, rect.width / 4);
|
|
17651
|
-
const insetY = Math.min(12, rect.height / 4);
|
|
17652
|
-
const raw = [
|
|
17653
|
-
[rect.left + rect.width / 2, rect.top + rect.height / 2],
|
|
17654
|
-
[rect.left + insetX, rect.top + insetY],
|
|
17655
|
-
[rect.right - insetX, rect.top + insetY],
|
|
17656
|
-
[rect.left + insetX, rect.bottom - insetY],
|
|
17657
|
-
[rect.right - insetX, rect.bottom - insetY],
|
|
17658
|
-
];
|
|
17659
|
-
return raw.map(([x, y]) => ({
|
|
17660
|
-
x: Math.min(Math.max(1, x), Math.max(1, width - 1)),
|
|
17661
|
-
y: Math.min(Math.max(1, y), Math.max(1, height - 1)),
|
|
17662
|
-
}));
|
|
17663
|
-
}
|
|
17664
|
-
|
|
17665
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17666
|
-
if (!el) return { error: "Element not found" };
|
|
17667
|
-
|
|
17668
|
-
if (el instanceof HTMLElement) {
|
|
17669
|
-
el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
17670
|
-
}
|
|
17671
|
-
|
|
17672
|
-
await new Promise((resolve) => {
|
|
17673
|
-
let settled = false;
|
|
17674
|
-
const finish = () => {
|
|
17675
|
-
if (settled) return;
|
|
17676
|
-
settled = true;
|
|
17677
|
-
resolve(undefined);
|
|
17678
|
-
};
|
|
17679
|
-
if (
|
|
17680
|
-
typeof requestAnimationFrame === "function" &&
|
|
17681
|
-
document.visibilityState === "visible"
|
|
17682
|
-
) {
|
|
17683
|
-
requestAnimationFrame(() => finish());
|
|
17684
|
-
}
|
|
17685
|
-
setTimeout(finish, 32);
|
|
17686
|
-
});
|
|
17687
|
-
|
|
17688
|
-
const rect = el.getBoundingClientRect();
|
|
17689
|
-
if (rect.width <= 0 || rect.height <= 0) {
|
|
17690
|
-
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." };
|
|
17691
|
-
}
|
|
17692
|
-
|
|
17693
|
-
const points = samplePoints(rect);
|
|
17694
|
-
const hit = points.find((point) => matchesTarget(document.elementFromPoint(point.x, point.y), el));
|
|
17695
|
-
const chosen = hit || points[0];
|
|
17696
|
-
const top = document.elementFromPoint(chosen.x, chosen.y);
|
|
17697
|
-
|
|
17698
|
-
return {
|
|
17699
|
-
x: Math.round(chosen.x),
|
|
17700
|
-
y: Math.round(chosen.y),
|
|
17701
|
-
obstructed: !matchesTarget(top, el),
|
|
17702
|
-
hiddenWindow: document.visibilityState !== "visible",
|
|
17703
|
-
};
|
|
17704
|
-
})()
|
|
17705
|
-
`);
|
|
17706
|
-
if (!target || typeof target !== "object") {
|
|
17707
|
-
return "Error: Could not resolve click target";
|
|
17708
|
-
}
|
|
17709
|
-
if ("error" in target && typeof target.error === "string") {
|
|
17710
|
-
return `Error: ${target.error}`;
|
|
17711
|
-
}
|
|
17712
|
-
const x = typeof target.x === "number" ? target.x : null;
|
|
17713
|
-
const y = typeof target.y === "number" ? target.y : null;
|
|
17714
|
-
const hiddenWindow = target.hiddenWindow === true;
|
|
17715
|
-
if (x == null || y == null) {
|
|
17716
|
-
return "Error: Could not resolve click coordinates";
|
|
17717
|
-
}
|
|
17718
|
-
if (hiddenWindow) {
|
|
17719
|
-
const activationResult = await activateElement(wc, selector);
|
|
17720
|
-
if (activationResult.startsWith("Error:")) {
|
|
17721
|
-
return activationResult;
|
|
17722
|
-
}
|
|
17723
|
-
await sleep(80);
|
|
17724
|
-
return "Clicked via DOM activation";
|
|
17725
|
-
}
|
|
17726
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
17727
|
-
await sleep(16);
|
|
17728
|
-
wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
|
|
17729
|
-
await sleep(24);
|
|
17730
|
-
wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
|
|
17731
|
-
await sleep(80);
|
|
17732
|
-
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
17733
|
-
}
|
|
17734
|
-
async function activateElement(wc, selector) {
|
|
17735
|
-
const activated = await wc.executeJavaScript(`
|
|
17736
|
-
(function() {
|
|
17737
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17738
|
-
if (!el) return { error: "Element not found" };
|
|
17739
|
-
if (el instanceof HTMLElement) {
|
|
17740
|
-
el.focus({ preventScroll: true });
|
|
17741
|
-
}
|
|
17742
|
-
if (typeof el.click === "function") {
|
|
17743
|
-
el.click();
|
|
17744
|
-
return { ok: true };
|
|
17745
|
-
}
|
|
17746
|
-
return { error: "Element is not clickable" };
|
|
17747
|
-
})()
|
|
17748
|
-
`);
|
|
17749
|
-
if (!activated || typeof activated !== "object") {
|
|
17750
|
-
return "Error: Could not activate element";
|
|
17751
|
-
}
|
|
17752
|
-
if ("error" in activated && typeof activated.error === "string") {
|
|
17753
|
-
return `Error: ${activated.error}`;
|
|
17754
|
-
}
|
|
17755
|
-
return "Activated element via DOM click";
|
|
17756
|
-
}
|
|
17757
|
-
async function describeElementForClick(wc, selector) {
|
|
17758
|
-
const result = await wc.executeJavaScript(`
|
|
17759
|
-
(function() {
|
|
17760
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17761
|
-
if (!el) return { error: "Element not found" };
|
|
17762
|
-
const anchor = el instanceof HTMLAnchorElement ? el : el.closest("a[href]");
|
|
17763
|
-
const text = (el.textContent || el.tagName || "Element").trim().slice(0, 100);
|
|
17764
|
-
return {
|
|
17765
|
-
text: text || "Element",
|
|
17766
|
-
href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
|
|
17767
|
-
};
|
|
17768
|
-
})()
|
|
17769
|
-
`);
|
|
17770
|
-
if (!result || typeof result !== "object") {
|
|
17771
|
-
return { error: "Element not found" };
|
|
17772
|
-
}
|
|
17773
|
-
if ("error" in result && typeof result.error === "string") {
|
|
17774
|
-
return { error: result.error };
|
|
17775
|
-
}
|
|
17776
|
-
return {
|
|
17777
|
-
text: "text" in result && typeof result.text === "string" ? result.text : "Element",
|
|
17778
|
-
href: "href" in result && typeof result.href === "string" ? result.href : void 0
|
|
17779
|
-
};
|
|
17780
|
-
}
|
|
17781
|
-
async function clickResolvedSelector(wc, selector) {
|
|
17782
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
17783
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
17784
|
-
const beforeUrl2 = wc.getURL();
|
|
17785
|
-
const idxLabel = await wc.executeJavaScript(
|
|
17786
|
-
`window.__vessel?.getElementText?.(${idx}) || ""`
|
|
17787
|
-
);
|
|
17788
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
|
|
17789
|
-
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).`;
|
|
17790
|
-
}
|
|
17791
|
-
const result = await wc.executeJavaScript(
|
|
17792
|
-
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
|
|
17793
|
-
);
|
|
17794
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17795
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17796
|
-
recordCartClick(beforeUrl2, idxLabel);
|
|
17797
|
-
}
|
|
17798
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17799
|
-
const afterUrl2 = wc.getURL();
|
|
17800
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17801
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17802
|
-
if (!overlayHint2 && typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17803
|
-
await sleep(1200);
|
|
17804
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17805
|
-
}
|
|
17806
|
-
if (!overlayHint2) {
|
|
17807
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17808
|
-
if (hrefMatch) {
|
|
17809
|
-
try {
|
|
17810
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17811
|
-
await waitForLoad(wc, 8e3);
|
|
17812
|
-
const hrefUrl = wc.getURL();
|
|
17813
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17814
|
-
} catch {
|
|
17815
|
-
}
|
|
17816
|
-
}
|
|
17817
|
-
return result;
|
|
17818
|
-
}
|
|
17819
|
-
const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
|
|
17820
|
-
const actionsSuffix = dialogActions ? `
|
|
17821
|
-
${dialogActions}
|
|
17822
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17823
|
-
return `${result}
|
|
17824
|
-
${overlayHint2}${actionsSuffix}`;
|
|
17825
|
-
}
|
|
17826
|
-
if (selector.includes(" >>> ")) {
|
|
17827
|
-
const beforeUrl2 = wc.getURL();
|
|
17828
|
-
const shadowLabel = await wc.executeJavaScript(`
|
|
17829
|
-
(function() {
|
|
17830
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17831
|
-
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
17832
|
-
})()
|
|
17833
|
-
`);
|
|
17834
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
|
|
17835
|
-
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).`;
|
|
17836
|
-
}
|
|
17837
|
-
const result = await wc.executeJavaScript(`
|
|
17838
|
-
(function() {
|
|
17839
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17840
|
-
if (!el || !document.contains(el)) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
17841
|
-
if (el instanceof HTMLElement) { el.focus(); el.click(); }
|
|
17842
|
-
var anchor = el instanceof HTMLAnchorElement ? el : el.closest('a[href]');
|
|
17843
|
-
var href = anchor instanceof HTMLAnchorElement ? anchor.href : null;
|
|
17844
|
-
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase()) + (href ? "\\nhref: " + href : "");
|
|
17845
|
-
})()
|
|
17846
|
-
`);
|
|
17847
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17848
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17849
|
-
recordCartClick(beforeUrl2, shadowLabel);
|
|
17850
|
-
}
|
|
17851
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17852
|
-
const afterUrl2 = wc.getURL();
|
|
17853
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17854
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17855
|
-
if (!overlayHint2 && typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17856
|
-
await sleep(1200);
|
|
17857
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17858
|
-
}
|
|
17859
|
-
if (!overlayHint2) {
|
|
17860
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17861
|
-
if (hrefMatch) {
|
|
17862
|
-
try {
|
|
17863
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17864
|
-
await waitForLoad(wc, 8e3);
|
|
17865
|
-
const hrefUrl = wc.getURL();
|
|
17866
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17867
|
-
} catch {
|
|
17868
|
-
}
|
|
17869
|
-
}
|
|
17870
|
-
return result;
|
|
17871
|
-
}
|
|
17872
|
-
const dialogActions2 = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
|
|
17873
|
-
const actionsSuffix2 = dialogActions2 ? `
|
|
17874
|
-
${dialogActions2}
|
|
17875
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17876
|
-
return `${result}
|
|
17877
|
-
${overlayHint2}${actionsSuffix2}`;
|
|
17878
|
-
}
|
|
17879
|
-
const beforeUrl = wc.getURL();
|
|
17880
|
-
const elInfo = await describeElementForClick(wc, selector);
|
|
17881
|
-
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
17882
|
-
const cartMatch = isAddToCartText(elInfo.text);
|
|
17883
|
-
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
17884
|
-
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).`;
|
|
17885
|
-
}
|
|
17886
|
-
if (!cartMatch) {
|
|
17887
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17888
|
-
if (dialogActions) {
|
|
17889
|
-
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
17890
|
-
${dialogActions}
|
|
17891
|
-
Click one of these dialog actions instead.`;
|
|
17892
|
-
}
|
|
17893
|
-
}
|
|
17894
|
-
if (elInfo.href) {
|
|
17895
|
-
const validation = await validateLinkDestination(elInfo.href);
|
|
17896
|
-
if (validation.status === "dead") {
|
|
17897
|
-
return formatDeadLinkMessage(elInfo.text, validation);
|
|
17898
|
-
}
|
|
17899
|
-
}
|
|
17900
|
-
if (cartMatch) {
|
|
17901
|
-
recordCartClick(beforeUrl, elInfo.text);
|
|
17902
|
-
}
|
|
17903
|
-
const clickText = `Clicked: ${elInfo.text}`;
|
|
17904
|
-
const clickResult = await clickElement(wc, selector);
|
|
17905
|
-
if (clickResult.startsWith("Error:")) return clickResult;
|
|
17906
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
17907
|
-
const afterUrl = wc.getURL();
|
|
17908
|
-
if (afterUrl !== beforeUrl) {
|
|
17909
|
-
return `${clickText} -> ${afterUrl}`;
|
|
17910
|
-
}
|
|
17911
|
-
const overlayHint = await detectPostClickOverlay(wc);
|
|
17912
|
-
if (overlayHint) {
|
|
17913
|
-
const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
|
|
17914
|
-
const actionsSuffix = dialogActions ? `
|
|
17915
|
-
${dialogActions}
|
|
17916
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17917
|
-
return `${clickText} (${clickResult})
|
|
17918
|
-
${overlayHint}${actionsSuffix}`;
|
|
17919
|
-
}
|
|
17920
|
-
if (cartMatch) {
|
|
17921
|
-
await sleep(1200);
|
|
17922
|
-
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
17923
|
-
if (delayedOverlayHint) {
|
|
17924
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17925
|
-
const actionsSuffix = dialogActions ? `
|
|
17926
|
-
${dialogActions}
|
|
17927
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17928
|
-
return `${clickText} (${clickResult})
|
|
17929
|
-
${delayedOverlayHint}${actionsSuffix}`;
|
|
17930
|
-
}
|
|
17931
|
-
return `${clickText} (${clickResult})`;
|
|
17932
|
-
}
|
|
17933
|
-
const activationResult = await activateElement(wc, selector);
|
|
17934
|
-
if (!activationResult.startsWith("Error:")) {
|
|
17935
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
17936
|
-
const fallbackUrl = wc.getURL();
|
|
17937
|
-
if (fallbackUrl !== beforeUrl) {
|
|
17938
|
-
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
17939
|
-
}
|
|
17940
|
-
}
|
|
17941
|
-
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
17942
|
-
if (postActivationOverlayHint) {
|
|
17943
|
-
return `${clickText} (${clickResult})
|
|
17944
|
-
${postActivationOverlayHint}`;
|
|
17945
|
-
}
|
|
17946
|
-
return `${clickText} (${clickResult})`;
|
|
17947
|
-
}
|
|
17948
|
-
async function getCartDialogActions(wc) {
|
|
17949
|
-
const result = await wc.executeJavaScript(`
|
|
17950
|
-
(function() {
|
|
17951
|
-
function isVisible(el) {
|
|
17952
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
17953
|
-
const style = getComputedStyle(el);
|
|
17954
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
17955
|
-
const rect = el.getBoundingClientRect();
|
|
17956
|
-
return rect.width >= 20 && rect.height >= 10;
|
|
17957
|
-
}
|
|
17958
|
-
|
|
17959
|
-
function findDialogRoot() {
|
|
17960
|
-
const selectors = [
|
|
17961
|
-
'[data-test="basket-flyout"]',
|
|
17962
|
-
'[role="dialog"]',
|
|
17963
|
-
'dialog[open]',
|
|
17964
|
-
'[role="alertdialog"]',
|
|
17965
|
-
'[aria-modal="true"]',
|
|
17966
|
-
];
|
|
17967
|
-
for (const selector of selectors) {
|
|
17968
|
-
const nodes = document.querySelectorAll(selector);
|
|
17969
|
-
for (const node of nodes) {
|
|
17970
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
17971
|
-
const text = (node.textContent || "").slice(0, 800).toLowerCase();
|
|
17972
|
-
const cartSignals = [
|
|
17973
|
-
"added to cart", "added to bag", "added to basket",
|
|
17974
|
-
"item added", "your basket", "your cart", "your bag",
|
|
17975
|
-
"view basket", "view cart", "continue shopping",
|
|
17976
|
-
];
|
|
17977
|
-
if (cartSignals.some((signal) => text.includes(signal))) {
|
|
17978
|
-
return node;
|
|
17979
|
-
}
|
|
17980
|
-
}
|
|
17981
|
-
}
|
|
17982
|
-
return null;
|
|
17983
|
-
}
|
|
17984
|
-
|
|
17985
|
-
const dialog = findDialogRoot();
|
|
17986
|
-
if (!dialog) return { found: false, actions: [] };
|
|
17987
|
-
|
|
17988
|
-
const actions = [];
|
|
17989
|
-
dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
|
|
17990
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
17991
|
-
const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
|
|
17992
|
-
if (!label || label.length < 2) return;
|
|
17993
|
-
const href = el.getAttribute("href") || "";
|
|
17994
|
-
const selector = el.id ? "#" + el.id
|
|
17995
|
-
: el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
|
|
17996
|
-
: el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
|
|
17997
|
-
: null;
|
|
17998
|
-
if (selector) {
|
|
17999
|
-
actions.push({ label: label, href: href, selector: selector });
|
|
18000
|
-
}
|
|
18001
|
-
});
|
|
18002
|
-
|
|
18003
|
-
return {
|
|
18004
|
-
found: true,
|
|
18005
|
-
actions: actions.map((action) =>
|
|
18006
|
-
'- "' + action.label + '"' +
|
|
18007
|
-
(action.href ? ' -> ' + action.href : "") +
|
|
18008
|
-
(action.selector ? ' (selector: ' + action.selector + ')' : "")
|
|
18009
|
-
),
|
|
18010
|
-
};
|
|
18011
|
-
})()
|
|
18012
|
-
`);
|
|
18013
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18014
|
-
return null;
|
|
18015
|
-
}
|
|
18016
|
-
if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
|
|
18017
|
-
return null;
|
|
18018
|
-
}
|
|
18019
|
-
return `Available dialog actions:
|
|
18020
|
-
${result.actions.join("\n")}`;
|
|
18021
|
-
}
|
|
18022
|
-
async function detectPostClickOverlay(wc) {
|
|
18023
|
-
const result = await wc.executeJavaScript(`
|
|
18024
|
-
(function() {
|
|
18025
|
-
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
18026
|
-
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
18027
|
-
var vpArea = Math.max(1, vw * vh);
|
|
18028
|
-
|
|
18029
|
-
function isVisible(el) {
|
|
18030
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
18031
|
-
var style = getComputedStyle(el);
|
|
18032
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
18033
|
-
return el.getBoundingClientRect().width > 0;
|
|
18034
|
-
}
|
|
18035
|
-
|
|
18036
|
-
function hasFixedAncestor(el) {
|
|
18037
|
-
var current = el.parentElement;
|
|
18038
|
-
while (current && current !== document.body) {
|
|
18039
|
-
var position = getComputedStyle(current).position;
|
|
18040
|
-
if (position === "fixed" || position === "sticky") return true;
|
|
18041
|
-
current = current.parentElement;
|
|
18042
|
-
}
|
|
18043
|
-
return false;
|
|
18044
|
-
}
|
|
18045
|
-
|
|
18046
|
-
function effectiveZ(el) {
|
|
18047
|
-
var current = el;
|
|
18048
|
-
while (current && current !== document.body) {
|
|
18049
|
-
var z = parseInt(getComputedStyle(current).zIndex, 10);
|
|
18050
|
-
if (z > 0) return z;
|
|
18051
|
-
current = current.parentElement;
|
|
18052
|
-
}
|
|
18053
|
-
return 0;
|
|
18054
|
-
}
|
|
18055
|
-
|
|
18056
|
-
function touchesViewportEdge(rect) {
|
|
18057
|
-
return rect.left <= 24 || rect.top <= 24 ||
|
|
18058
|
-
rect.right >= vw - 24 || rect.bottom >= vh - 24;
|
|
18059
|
-
}
|
|
18060
|
-
|
|
18061
|
-
var cartPhrases = [
|
|
18062
|
-
"added to cart", "added to bag", "added to basket",
|
|
18063
|
-
"added to your cart", "added to your bag", "added to your basket",
|
|
18064
|
-
];
|
|
18065
|
-
var cartActions = [
|
|
18066
|
-
"view cart", "go to cart", "view basket", "go to basket",
|
|
18067
|
-
"continue shopping", "keep shopping", "checkout",
|
|
18068
|
-
];
|
|
18069
|
-
|
|
18070
|
-
var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
|
|
18071
|
-
var candidates = document.querySelectorAll(selectors);
|
|
18072
|
-
var hit = null;
|
|
18073
|
-
for (var i = 0; i < candidates.length; i++) {
|
|
18074
|
-
if (isVisible(candidates[i])) {
|
|
18075
|
-
hit = candidates[i];
|
|
18076
|
-
break;
|
|
18077
|
-
}
|
|
18078
|
-
}
|
|
18079
|
-
|
|
18080
|
-
if (!hit) {
|
|
18081
|
-
var elements = document.querySelectorAll("*");
|
|
18082
|
-
for (var j = 0; j < elements.length; j++) {
|
|
18083
|
-
var el = elements[j];
|
|
18084
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
|
|
18085
|
-
var style = getComputedStyle(el);
|
|
18086
|
-
var position = style.position;
|
|
18087
|
-
var isFixed = position === "fixed" || position === "sticky";
|
|
18088
|
-
var isAbsolute = position === "absolute";
|
|
18089
|
-
if (!isFixed && !isAbsolute) continue;
|
|
18090
|
-
if (isAbsolute && !hasFixedAncestor(el)) continue;
|
|
18091
|
-
if (effectiveZ(el) < 5) continue;
|
|
18092
|
-
var rect = el.getBoundingClientRect();
|
|
18093
|
-
var areaRatio = (rect.width * rect.height) / vpArea;
|
|
18094
|
-
if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
|
|
18095
|
-
hit = el;
|
|
18096
|
-
break;
|
|
18097
|
-
}
|
|
18098
|
-
}
|
|
18099
|
-
}
|
|
18100
|
-
|
|
18101
|
-
if (!hit) return { found: false, label: "", cartLike: false };
|
|
18102
|
-
var text = (hit.textContent || "").slice(0, 800).toLowerCase();
|
|
18103
|
-
var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
|
|
18104
|
-
return text.indexOf(signal) !== -1;
|
|
18105
|
-
});
|
|
18106
|
-
var heading = hit.querySelector("h1,h2,h3,h4");
|
|
18107
|
-
var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
|
|
18108
|
-
return { found: true, label: label, cartLike: cartLike };
|
|
18109
|
-
})()
|
|
18110
|
-
`);
|
|
18111
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18112
|
-
return null;
|
|
18113
|
-
}
|
|
18114
|
-
const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
|
|
18115
|
-
if ("cartLike" in result && result.cartLike) {
|
|
18116
|
-
return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
|
|
18117
|
-
}
|
|
18118
|
-
return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
|
|
18119
|
-
}
|
|
18120
|
-
async function dismissPopup(wc) {
|
|
18121
|
-
const before = await extractContent(wc);
|
|
18122
|
-
const initialBlocking = before.overlays.filter(
|
|
18123
|
-
(overlay) => overlay.blocksInteraction
|
|
18124
|
-
).length;
|
|
18125
|
-
if (initialBlocking > 0) {
|
|
18126
|
-
const overlayText = before.overlays.map(
|
|
18127
|
-
(o) => [o.label, o.text].filter(Boolean).join(" ")
|
|
18128
|
-
).join(" ").toLowerCase();
|
|
18129
|
-
const cartSignals = [
|
|
18130
|
-
"added to cart",
|
|
18131
|
-
"added to bag",
|
|
18132
|
-
"added to basket",
|
|
18133
|
-
"item added",
|
|
18134
|
-
"items in your basket",
|
|
18135
|
-
"items in your cart",
|
|
18136
|
-
"items in your bag",
|
|
18137
|
-
"your basket",
|
|
18138
|
-
"your cart",
|
|
18139
|
-
"your bag",
|
|
18140
|
-
"view basket",
|
|
18141
|
-
"view cart",
|
|
18142
|
-
"continue shopping"
|
|
18143
|
-
];
|
|
18144
|
-
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
18145
|
-
const continueResult = await wc.executeJavaScript(`
|
|
18146
|
-
(function() {
|
|
18147
|
-
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
18148
|
-
if (!dialog) return "Error: dialog not found";
|
|
18149
|
-
var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
|
|
18150
|
-
var continueBtn = null;
|
|
18151
|
-
var viewCartBtn = null;
|
|
18152
|
-
for (var i = 0; i < buttons.length; i++) {
|
|
18153
|
-
var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
|
|
18154
|
-
if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
|
|
18155
|
-
if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
|
|
18156
|
-
}
|
|
18157
|
-
var target = continueBtn || viewCartBtn;
|
|
18158
|
-
if (!target) return "Error: no dialog action found";
|
|
18159
|
-
var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
|
|
18160
|
-
if (target.tagName === 'A' && target.href) {
|
|
18161
|
-
window.location.href = target.href;
|
|
18162
|
-
return "Clicked: " + actionLabel + " -> " + target.href;
|
|
18163
|
-
}
|
|
18164
|
-
target.click();
|
|
18165
|
-
return "Clicked: " + actionLabel;
|
|
18166
|
-
})()
|
|
18167
|
-
`);
|
|
18168
|
-
if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
18169
|
-
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
18170
|
-
}
|
|
18171
|
-
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.";
|
|
18172
|
-
}
|
|
18173
|
-
}
|
|
18174
|
-
const initialDormant = before.dormantOverlays.length;
|
|
18175
|
-
const candidates = await wc.executeJavaScript(`
|
|
18176
|
-
(function() {
|
|
18177
|
-
function text(value) {
|
|
18178
|
-
const trimmed = value == null ? "" : String(value).trim();
|
|
18179
|
-
return trimmed || "";
|
|
18180
|
-
}
|
|
18181
|
-
|
|
18182
|
-
${selectorHelpersJS(["data-testid", "data-test", "aria-label", "name", "title"])}
|
|
18183
|
-
|
|
18184
|
-
function isVisible(el) {
|
|
18185
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
18186
|
-
const style = window.getComputedStyle(el);
|
|
18187
|
-
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
18188
|
-
return false;
|
|
18189
|
-
}
|
|
18190
|
-
if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
|
|
18191
|
-
return false;
|
|
18192
|
-
}
|
|
18193
|
-
const rect = el.getBoundingClientRect();
|
|
18194
|
-
return rect.width > 0 && rect.height > 0;
|
|
18195
|
-
}
|
|
18196
|
-
|
|
18197
|
-
function overlayRoots() {
|
|
18198
|
-
const nodes = [];
|
|
18199
|
-
document.querySelectorAll("dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']").forEach((el) => {
|
|
18200
|
-
if (isVisible(el)) nodes.push(el);
|
|
18201
|
-
});
|
|
18202
|
-
// Detect known consent manager containers
|
|
18203
|
-
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) => {
|
|
18204
|
-
if (el instanceof HTMLElement && isVisible(el)) nodes.push(el);
|
|
18205
|
-
});
|
|
18206
|
-
document.querySelectorAll("body *").forEach((el) => {
|
|
18207
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18208
|
-
const style = window.getComputedStyle(el);
|
|
18209
|
-
const rect = el.getBoundingClientRect();
|
|
18210
|
-
const zIndex = Number.parseInt(style.zIndex, 10);
|
|
18211
|
-
const coversCenter =
|
|
18212
|
-
rect.left <= (window.innerWidth || 0) / 2 &&
|
|
18213
|
-
rect.right >= (window.innerWidth || 0) / 2 &&
|
|
18214
|
-
rect.top <= (window.innerHeight || 0) / 2 &&
|
|
18215
|
-
rect.bottom >= (window.innerHeight || 0) / 2;
|
|
18216
|
-
if (
|
|
18217
|
-
(style.position === "fixed" || style.position === "sticky") &&
|
|
18218
|
-
Number.isFinite(zIndex) &&
|
|
18219
|
-
zIndex >= 10 &&
|
|
18220
|
-
coversCenter
|
|
18221
|
-
) {
|
|
18222
|
-
nodes.push(el);
|
|
18223
|
-
}
|
|
18224
|
-
});
|
|
18225
|
-
return Array.from(new Set(nodes));
|
|
18226
|
-
}
|
|
18227
|
-
|
|
18228
|
-
function scoreCandidate(el, rooted) {
|
|
18229
|
-
const label = text(
|
|
18230
|
-
el.getAttribute("aria-label") ||
|
|
18231
|
-
el.getAttribute("title") ||
|
|
18232
|
-
el.textContent ||
|
|
18233
|
-
el.getAttribute("value"),
|
|
18234
|
-
).toLowerCase();
|
|
18235
|
-
const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18236
|
-
const idText = text(el.id).toLowerCase();
|
|
18237
|
-
const combined = classText + " " + idText;
|
|
18238
|
-
let score = rooted ? 30 : 0;
|
|
18239
|
-
if (/^x$|^×$/.test(label)) score += 120;
|
|
18240
|
-
if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
|
|
18241
|
-
if (/close|dismiss|modal-close|overlay-close/.test(combined)) score += 90;
|
|
18242
|
-
if (/onetrust-close|onetrust-reject|cookie.*close|consent.*close|cookie.*reject|consent.*reject/.test(combined)) score += 110;
|
|
18243
|
-
if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
|
|
18244
|
-
if (el.getAttribute("aria-label")) score += 20;
|
|
18245
|
-
if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
|
|
18246
|
-
const rect = el.getBoundingClientRect();
|
|
18247
|
-
if (rect.top < 120) score += 10;
|
|
18248
|
-
if (rect.right > (window.innerWidth || 0) - 120) score += 15;
|
|
18249
|
-
return score;
|
|
18250
|
-
}
|
|
18251
|
-
|
|
18252
|
-
const selector = "button, [role='button'], a[href], input[type='button'], input[type='submit'], [aria-label], [title]";
|
|
18253
|
-
const results = [];
|
|
18254
|
-
const roots = overlayRoots();
|
|
18255
|
-
|
|
18256
|
-
function collect(container, rooted) {
|
|
18257
|
-
container.querySelectorAll(selector).forEach((el) => {
|
|
18258
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18259
|
-
const candidateSelector = selectorFor(el);
|
|
18260
|
-
if (!candidateSelector) return;
|
|
18261
|
-
var label = text(
|
|
18262
|
-
el.getAttribute("aria-label") ||
|
|
18263
|
-
el.getAttribute("title") ||
|
|
18264
|
-
el.textContent ||
|
|
18265
|
-
el.getAttribute("value"),
|
|
18266
|
-
);
|
|
18267
|
-
if (!label) {
|
|
18268
|
-
var idLower = (el.id || "").toLowerCase();
|
|
18269
|
-
var classLower = (typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18270
|
-
var combined = idLower + " " + classLower;
|
|
18271
|
-
if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(combined)) {
|
|
18272
|
-
label = idLower.includes("accept") ? "Accept cookies"
|
|
18273
|
-
: idLower.includes("reject") ? "Reject cookies"
|
|
18274
|
-
: idLower.includes("close") || classLower.includes("close") ? "Close"
|
|
18275
|
-
: "Consent button";
|
|
18276
|
-
} else {
|
|
18277
|
-
return;
|
|
18278
|
-
}
|
|
18279
|
-
}
|
|
18280
|
-
results.push({
|
|
18281
|
-
selector: candidateSelector,
|
|
18282
|
-
label: label.slice(0, 120),
|
|
18283
|
-
score: scoreCandidate(el, rooted),
|
|
18284
|
-
});
|
|
18285
|
-
});
|
|
18286
|
-
}
|
|
18287
|
-
|
|
18288
|
-
roots.forEach((root) => collect(root, true));
|
|
18289
|
-
if (results.length === 0) {
|
|
18290
|
-
collect(document, false);
|
|
18291
|
-
}
|
|
18292
|
-
|
|
18293
|
-
const seen = new Set();
|
|
18294
|
-
return results
|
|
18295
|
-
.filter((candidate) => {
|
|
18296
|
-
if (seen.has(candidate.selector)) return false;
|
|
18297
|
-
seen.add(candidate.selector);
|
|
18298
|
-
return candidate.score > 0;
|
|
18299
|
-
})
|
|
18300
|
-
.sort((a, b) => b.score - a.score)
|
|
18301
|
-
.slice(0, 8);
|
|
18302
|
-
})()
|
|
18303
|
-
`);
|
|
18304
|
-
if (Array.isArray(candidates)) {
|
|
18305
|
-
for (const candidate of candidates) {
|
|
18306
|
-
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
18307
|
-
continue;
|
|
18308
|
-
}
|
|
18309
|
-
const result = await clickElement(wc, candidate.selector);
|
|
18310
|
-
if (result.startsWith("Error:")) continue;
|
|
18311
|
-
await sleep(250);
|
|
18312
|
-
const after = await extractContent(wc);
|
|
18313
|
-
const blocking = after.overlays.filter(
|
|
18314
|
-
(overlay) => overlay.blocksInteraction
|
|
18315
|
-
).length;
|
|
18316
|
-
if (blocking < initialBlocking || initialBlocking > 0 && blocking === 0) {
|
|
18317
|
-
const label = typeof candidate.label === "string" && candidate.label ? candidate.label : "popup control";
|
|
18318
|
-
return `Dismissed popup using "${label}"`;
|
|
18319
|
-
}
|
|
18320
|
-
}
|
|
18321
|
-
}
|
|
18322
|
-
wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
|
|
18323
|
-
await sleep(16);
|
|
18324
|
-
wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
|
|
18325
|
-
await sleep(200);
|
|
18326
|
-
const afterEscape = await extractContent(wc);
|
|
18327
|
-
const escapeBlocking = afterEscape.overlays.filter(
|
|
18328
|
-
(overlay) => overlay.blocksInteraction
|
|
18329
|
-
).length;
|
|
18330
|
-
if (escapeBlocking < initialBlocking || initialBlocking > 0 && escapeBlocking === 0) {
|
|
18331
|
-
return "Dismissed popup with Escape";
|
|
18332
|
-
}
|
|
18333
|
-
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";
|
|
18334
|
-
}
|
|
18335
|
-
function isDangerousAction(name) {
|
|
18336
|
-
return [
|
|
18337
|
-
"navigate",
|
|
18338
|
-
"click",
|
|
18339
|
-
"type",
|
|
18340
|
-
"select_option",
|
|
18341
|
-
"submit_form",
|
|
18342
|
-
"press_key",
|
|
18343
|
-
"create_tab",
|
|
18344
|
-
"switch_tab",
|
|
18345
|
-
"close_tab",
|
|
18346
|
-
"restore_checkpoint",
|
|
18347
|
-
"login",
|
|
18348
|
-
"fill_form",
|
|
18349
|
-
"search",
|
|
18350
|
-
"paginate"
|
|
18351
|
-
].includes(name);
|
|
18352
|
-
}
|
|
18353
|
-
function getTabByMatch(tabManager, match) {
|
|
18354
|
-
const lowered = match.toLowerCase();
|
|
18355
|
-
return tabManager.getAllStates().find(
|
|
18356
|
-
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
18357
|
-
) || null;
|
|
18358
|
-
}
|
|
18359
|
-
async function getPostActionState(tabManager, name) {
|
|
18360
|
-
const tab = tabManager.getActiveTab();
|
|
18361
|
-
if (!tab) return "";
|
|
18362
|
-
const wc = tab.view.webContents;
|
|
18363
|
-
const navActions = [
|
|
18364
|
-
"navigate",
|
|
18365
|
-
"go_back",
|
|
18366
|
-
"go_forward",
|
|
18367
|
-
"click",
|
|
18368
|
-
"submit_form",
|
|
18369
|
-
"reload",
|
|
18370
|
-
"press_key"
|
|
18371
|
-
];
|
|
18372
|
-
const interactActions = [
|
|
18373
|
-
"type",
|
|
18374
|
-
"type_text",
|
|
18375
|
-
"select_option",
|
|
18376
|
-
"hover",
|
|
18377
|
-
"focus"
|
|
18378
|
-
];
|
|
18379
|
-
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
18380
|
-
if (navActions.includes(name)) {
|
|
18381
|
-
let warning = "";
|
|
18382
|
-
try {
|
|
18383
|
-
const page = await extractContent(wc);
|
|
18384
|
-
const issue = getRecoverableAccessIssue(page);
|
|
18385
|
-
if (issue) {
|
|
18386
|
-
const blockedUrl = wc.getURL();
|
|
18387
|
-
const canRecover = [
|
|
18388
|
-
"navigate",
|
|
18389
|
-
"open_bookmark",
|
|
18390
|
-
"click",
|
|
18391
|
-
"submit_form",
|
|
18392
|
-
"reload",
|
|
18393
|
-
"press_key"
|
|
18394
|
-
].includes(name) && tab.canGoBack();
|
|
18395
|
-
if (canRecover && tab.goBack()) {
|
|
18396
|
-
await waitForLoad(wc);
|
|
18397
|
-
warning = `
|
|
18398
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
18399
|
-
} else {
|
|
18400
|
-
warning = `
|
|
18401
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
18402
|
-
}
|
|
18403
|
-
}
|
|
18404
|
-
} catch {
|
|
18405
|
-
}
|
|
18406
|
-
return `${warning}
|
|
18407
|
-
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
18408
|
-
}
|
|
18409
|
-
if (interactActions.includes(name)) {
|
|
18410
|
-
return `
|
|
18411
|
-
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
18412
|
-
}
|
|
18413
|
-
if (tabActions.includes(name)) {
|
|
18414
|
-
const activeId = tabManager.getActiveTabId();
|
|
18415
|
-
const active = getActiveTabSummary(tabManager);
|
|
18416
|
-
const count = tabManager.getAllStates().length;
|
|
18417
|
-
return `
|
|
18418
|
-
[state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
|
|
18419
|
-
}
|
|
18420
|
-
return "";
|
|
18421
|
-
}
|
|
18422
|
-
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
18423
|
-
try {
|
|
18424
|
-
const result = await runtime2.runControlledAction({
|
|
18425
|
-
source: "mcp",
|
|
18426
|
-
name,
|
|
18427
|
-
args,
|
|
18428
|
-
tabId: tabManager.getActiveTabId(),
|
|
18429
|
-
dangerous: isDangerousAction(name),
|
|
18430
|
-
executor
|
|
18431
|
-
});
|
|
18432
|
-
const stateInfo = await getPostActionState(tabManager, name);
|
|
18433
|
-
const flowCtx = runtime2.getFlowContext();
|
|
18434
|
-
return asTextResponse(result + stateInfo + flowCtx);
|
|
18435
|
-
} catch (error) {
|
|
18436
|
-
return asTextResponse(
|
|
18437
|
-
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
18438
|
-
);
|
|
18439
|
-
}
|
|
18440
|
-
}
|
|
18441
|
-
async function setElementValue(wc, selector, value) {
|
|
18442
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
18443
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
18444
|
-
return wc.executeJavaScript(
|
|
18445
|
-
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
18446
|
-
);
|
|
18447
|
-
}
|
|
18448
|
-
if (selector.includes(" >>> ")) {
|
|
18449
|
-
return wc.executeJavaScript(`
|
|
18450
|
-
(function() {
|
|
18451
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
18452
|
-
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
18453
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
|
|
18454
|
-
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18455
|
-
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
18456
|
-
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
18457
|
-
el.focus();
|
|
18458
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
18459
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
18460
|
-
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
18461
|
-
})()
|
|
18462
|
-
`);
|
|
18463
|
-
}
|
|
18464
|
-
return wc.executeJavaScript(`
|
|
18465
|
-
(function() {
|
|
18466
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18467
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18468
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18469
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18470
|
-
}
|
|
18471
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18472
|
-
return 'Error[disabled]: Input is disabled';
|
|
18473
|
-
}
|
|
18474
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18475
|
-
? HTMLTextAreaElement.prototype
|
|
18476
|
-
: HTMLInputElement.prototype;
|
|
18477
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18478
|
-
if (descriptor && descriptor.set) {
|
|
18479
|
-
descriptor.set.call(el, ${JSON.stringify(value)});
|
|
18480
|
-
} else {
|
|
18481
|
-
el.value = ${JSON.stringify(value)};
|
|
18482
|
-
}
|
|
18483
|
-
el.focus();
|
|
18484
|
-
el.dispatchEvent(new InputEvent('input', {
|
|
18485
|
-
bubbles: true,
|
|
18486
|
-
cancelable: true,
|
|
18487
|
-
data: ${JSON.stringify(value)},
|
|
18488
|
-
inputType: 'insertText',
|
|
18489
|
-
}));
|
|
18490
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18491
|
-
return 'Typed into: ' +
|
|
18492
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18493
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18494
|
-
})()
|
|
18495
|
-
`);
|
|
18496
|
-
}
|
|
18497
|
-
async function typeKeystroke(wc, selector, value) {
|
|
18498
|
-
return wc.executeJavaScript(`
|
|
18499
|
-
(async function() {
|
|
18500
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18501
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18502
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18503
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18504
|
-
}
|
|
18505
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18506
|
-
return 'Error[disabled]: Input is disabled';
|
|
18507
|
-
}
|
|
18508
|
-
el.focus();
|
|
18509
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18510
|
-
? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18511
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18512
|
-
if (descriptor && descriptor.set) {
|
|
18513
|
-
descriptor.set.call(el, '');
|
|
18514
|
-
} else {
|
|
18515
|
-
el.value = '';
|
|
18516
|
-
}
|
|
18517
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: '', inputType: 'deleteContentBackward' }));
|
|
18518
|
-
const chars = ${JSON.stringify(value)}.split('');
|
|
18519
|
-
for (const ch of chars) {
|
|
18520
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true, cancelable: true }));
|
|
18521
|
-
el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true, cancelable: true }));
|
|
18522
|
-
if (descriptor && descriptor.set) {
|
|
18523
|
-
descriptor.set.call(el, el.value + ch);
|
|
18524
|
-
} else {
|
|
18525
|
-
el.value += ch;
|
|
18526
|
-
}
|
|
18527
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: ch, inputType: 'insertText' }));
|
|
18528
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true, cancelable: true }));
|
|
18529
|
-
}
|
|
18530
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18531
|
-
return 'Typed into: ' +
|
|
18532
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18533
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18534
|
-
})()
|
|
18535
|
-
`);
|
|
18536
|
-
}
|
|
18537
|
-
async function hoverElement(wc, selector) {
|
|
18538
|
-
const pos = await wc.executeJavaScript(`
|
|
18539
|
-
(function() {
|
|
18540
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18541
|
-
if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
|
|
18542
|
-
if (el instanceof HTMLElement) {
|
|
18543
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
|
|
18544
|
-
}
|
|
18545
|
-
const rect = el.getBoundingClientRect();
|
|
18546
|
-
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.' };
|
|
18547
|
-
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
|
|
18548
|
-
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
|
|
18549
|
-
const label = (el.textContent || el.tagName || 'Element').trim().slice(0, 80);
|
|
18550
|
-
return {
|
|
18551
|
-
x: Math.round(rect.left + rect.width / 2),
|
|
18552
|
-
y: Math.round(rect.top + rect.height / 2),
|
|
18553
|
-
label: label,
|
|
18554
|
-
};
|
|
18555
|
-
})()
|
|
18556
|
-
`);
|
|
18557
|
-
if (!pos || typeof pos !== "object") return "Error: Could not hover element";
|
|
18558
|
-
if ("error" in pos && typeof pos.error === "string") return pos.error;
|
|
18559
|
-
const x = typeof pos.x === "number" ? pos.x : null;
|
|
18560
|
-
const y = typeof pos.y === "number" ? pos.y : null;
|
|
18561
|
-
if (x == null || y == null)
|
|
18562
|
-
return "Error: Could not resolve hover coordinates";
|
|
18563
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
18564
|
-
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
18565
|
-
return `Hovered: ${label}`;
|
|
18566
|
-
}
|
|
18567
|
-
async function focusElement(wc, selector) {
|
|
18568
|
-
return wc.executeJavaScript(`
|
|
18569
|
-
(function() {
|
|
18570
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18571
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18572
|
-
if (!(el instanceof HTMLElement)) return 'Error[not-interactive]: Element is not focusable';
|
|
18573
|
-
if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
|
|
18574
|
-
return 'Error[disabled]: Element is disabled';
|
|
18575
|
-
}
|
|
18576
|
-
el.focus({ preventScroll: false });
|
|
18577
|
-
return 'Focused: ' + (el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
18578
|
-
})()
|
|
18579
|
-
`);
|
|
18580
|
-
}
|
|
18581
|
-
async function selectOption(wc, index, selector, label, value) {
|
|
18582
|
-
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18583
|
-
if (!resolvedSelector)
|
|
18584
|
-
return "Error: No select element index or selector provided";
|
|
18585
|
-
return wc.executeJavaScript(`
|
|
18586
|
-
(function() {
|
|
18587
|
-
const el = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18588
|
-
if (!(el instanceof HTMLSelectElement)) {
|
|
18589
|
-
return 'Element is not a select dropdown';
|
|
18590
|
-
}
|
|
18591
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18592
|
-
return 'Select is disabled';
|
|
18593
|
-
}
|
|
18594
|
-
const requestedLabel = ${JSON.stringify(label || "")}.trim().toLowerCase();
|
|
18595
|
-
const requestedValue = ${JSON.stringify(value || "")}.trim();
|
|
18596
|
-
const option = Array.from(el.options).find((item) => {
|
|
18597
|
-
const optionLabel = (item.textContent || '').trim().toLowerCase();
|
|
18598
|
-
return (requestedLabel && optionLabel === requestedLabel) ||
|
|
18599
|
-
(requestedValue && item.value === requestedValue);
|
|
18600
|
-
});
|
|
18601
|
-
if (!option) return 'Option not found';
|
|
18602
|
-
el.value = option.value;
|
|
18603
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
18604
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18605
|
-
return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
|
|
18606
|
-
})()
|
|
18607
|
-
`);
|
|
18608
|
-
}
|
|
18609
|
-
async function submitForm(wc, index, selector) {
|
|
18610
|
-
const beforeUrl = wc.getURL();
|
|
18611
|
-
let resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18612
|
-
if (!resolvedSelector) {
|
|
18613
|
-
resolvedSelector = await wc.executeJavaScript(`
|
|
18614
|
-
(function() {
|
|
18615
|
-
var forms = document.querySelectorAll('form');
|
|
18616
|
-
for (var i = 0; i < forms.length; i++) {
|
|
18617
|
-
var f = forms[i];
|
|
18618
|
-
var rect = f.getBoundingClientRect();
|
|
18619
|
-
if (rect.width > 0 && rect.height > 0) return 'form';
|
|
18620
|
-
}
|
|
18621
|
-
return forms.length > 0 ? 'form' : null;
|
|
18622
|
-
})()
|
|
18623
|
-
`);
|
|
18624
|
-
if (!resolvedSelector) return "Error: No form found on the page";
|
|
18625
|
-
}
|
|
18626
|
-
const formInfo = await wc.executeJavaScript(`
|
|
18627
|
-
(function() {
|
|
18628
|
-
const target = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18629
|
-
if (!target) return { error: 'Target not found' };
|
|
18630
|
-
// Find the form: nested, or linked via form="id" attribute
|
|
18631
|
-
var form = target instanceof HTMLFormElement ? target : target.closest('form');
|
|
18632
|
-
if (!form) {
|
|
18633
|
-
const formId = target.getAttribute('form');
|
|
18634
|
-
if (formId) {
|
|
18635
|
-
const linked = document.getElementById(formId);
|
|
18636
|
-
if (linked instanceof HTMLFormElement) form = linked;
|
|
18637
|
-
}
|
|
18638
|
-
}
|
|
18639
|
-
if (!form) return { error: 'No parent form found' };
|
|
18640
|
-
function isSubmitControl(el) {
|
|
18641
|
-
return (
|
|
18642
|
-
(el instanceof HTMLButtonElement &&
|
|
18643
|
-
((el.getAttribute('type') || '').trim().toLowerCase() === '' ||
|
|
18644
|
-
el.type === 'submit')) ||
|
|
18645
|
-
(el instanceof HTMLInputElement &&
|
|
18646
|
-
(el.type === 'submit' || el.type === 'image'))
|
|
18647
|
-
);
|
|
18648
|
-
}
|
|
18649
|
-
const submitter = isSubmitControl(target)
|
|
18650
|
-
? target
|
|
18651
|
-
: Array.from(document.querySelectorAll('button, input[type="submit"], input[type="image"]')).find(
|
|
18652
|
-
(candidate) => isSubmitControl(candidate) && candidate.form === form,
|
|
18653
|
-
);
|
|
18654
|
-
if (
|
|
18655
|
-
submitter instanceof HTMLElement &&
|
|
18656
|
-
(submitter.hasAttribute('disabled') ||
|
|
18657
|
-
submitter.getAttribute('aria-disabled') === 'true')
|
|
18658
|
-
) {
|
|
18659
|
-
return { error: 'Submit control is disabled' };
|
|
18660
|
-
}
|
|
18661
|
-
// Collect form data and determine method
|
|
18662
|
-
const submitterActionAttr =
|
|
18663
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18664
|
-
submitter instanceof HTMLInputElement
|
|
18665
|
-
? submitter.getAttribute('formaction')?.trim()
|
|
18666
|
-
: '') || '';
|
|
18667
|
-
const action = submitterActionAttr
|
|
18668
|
-
? new URL(submitterActionAttr, document.baseURI).toString()
|
|
18669
|
-
: form.action || window.location.href;
|
|
18670
|
-
const submitterMethodAttr =
|
|
18671
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18672
|
-
submitter instanceof HTMLInputElement
|
|
18673
|
-
? submitter.getAttribute('formmethod')?.trim()
|
|
18674
|
-
: '') || '';
|
|
18675
|
-
const method = (
|
|
18676
|
-
submitterMethodAttr ||
|
|
18677
|
-
form.getAttribute('method') ||
|
|
18678
|
-
form.method ||
|
|
18679
|
-
'GET'
|
|
18680
|
-
).toUpperCase();
|
|
18681
|
-
let fd;
|
|
18682
|
-
try {
|
|
18683
|
-
fd = submitter instanceof HTMLElement
|
|
18684
|
-
? new FormData(form, submitter)
|
|
18685
|
-
: new FormData(form);
|
|
18686
|
-
} catch {
|
|
18687
|
-
fd = new FormData(form);
|
|
18688
|
-
}
|
|
18689
|
-
const params = new URLSearchParams();
|
|
18690
|
-
for (const [k, v] of fd.entries()) {
|
|
18691
|
-
if (typeof v === 'string') params.append(k, v);
|
|
18692
|
-
}
|
|
18693
|
-
// Use requestSubmit to fire JS submit handlers for all methods
|
|
18694
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18695
|
-
try {
|
|
18696
|
-
if (
|
|
18697
|
-
submitter instanceof HTMLButtonElement ||
|
|
18698
|
-
submitter instanceof HTMLInputElement
|
|
18699
|
-
) {
|
|
18700
|
-
form.requestSubmit(submitter);
|
|
18701
|
-
} else {
|
|
18702
|
-
form.requestSubmit();
|
|
18703
|
-
}
|
|
18704
|
-
} catch {
|
|
18705
|
-
form.requestSubmit();
|
|
18706
|
-
}
|
|
18707
|
-
return { submitted: true, method };
|
|
18708
|
-
}
|
|
18709
|
-
if (submitter instanceof HTMLElement && typeof submitter.click === 'function') {
|
|
18710
|
-
submitter.click();
|
|
18711
|
-
return { submitted: true, method };
|
|
18712
|
-
}
|
|
18713
|
-
// Last resort: form.submit() bypasses JS handlers but at least submits
|
|
18714
|
-
if (method === 'GET') {
|
|
18715
|
-
return { action, method, params: params.toString(), found: true };
|
|
18716
|
-
}
|
|
18717
|
-
form.submit();
|
|
18718
|
-
return { submitted: true, method };
|
|
18719
|
-
})()
|
|
18720
|
-
`);
|
|
18721
|
-
if (formInfo.error) return formInfo.error;
|
|
18722
|
-
if (formInfo.found && formInfo.method === "GET") {
|
|
18723
|
-
const url = new URL(formInfo.action);
|
|
18724
|
-
if (formInfo.params) {
|
|
18725
|
-
url.search = formInfo.params;
|
|
18726
|
-
}
|
|
18727
|
-
await loadPermittedUrl(wc, url.toString());
|
|
18728
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18729
|
-
const afterUrl = wc.getURL();
|
|
18730
|
-
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
17771
|
+
if (interactActions.includes(name)) {
|
|
17772
|
+
return `
|
|
17773
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
18731
17774
|
}
|
|
18732
|
-
if (
|
|
18733
|
-
|
|
18734
|
-
const
|
|
18735
|
-
|
|
17775
|
+
if (tabActions.includes(name)) {
|
|
17776
|
+
const activeId = tabManager.getActiveTabId();
|
|
17777
|
+
const active = getActiveTabSummary(tabManager);
|
|
17778
|
+
const count = tabManager.getAllStates().length;
|
|
17779
|
+
return `
|
|
17780
|
+
[state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
|
|
18736
17781
|
}
|
|
18737
|
-
return "
|
|
17782
|
+
return "";
|
|
18738
17783
|
}
|
|
18739
|
-
async function
|
|
18740
|
-
|
|
18741
|
-
|
|
18742
|
-
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
if (key === 'Enter') {
|
|
18756
|
-
if (tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button'))) {
|
|
18757
|
-
target.click();
|
|
18758
|
-
} else if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
18759
|
-
const form = target.closest('form');
|
|
18760
|
-
if (form) {
|
|
18761
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18762
|
-
form.requestSubmit();
|
|
18763
|
-
} else {
|
|
18764
|
-
const submitBtn = form.querySelector('[type="submit"]');
|
|
18765
|
-
if (submitBtn) submitBtn.click();
|
|
18766
|
-
else form.submit();
|
|
18767
|
-
}
|
|
18768
|
-
}
|
|
18769
|
-
}
|
|
18770
|
-
}
|
|
18771
|
-
target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
|
|
18772
|
-
return 'Pressed key: ' + key;
|
|
18773
|
-
})()
|
|
18774
|
-
`);
|
|
17784
|
+
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
17785
|
+
try {
|
|
17786
|
+
const result = await runtime2.runControlledAction({
|
|
17787
|
+
source: "mcp",
|
|
17788
|
+
name,
|
|
17789
|
+
args,
|
|
17790
|
+
tabId: tabManager.getActiveTabId(),
|
|
17791
|
+
dangerous: isDangerousMcpAction(name),
|
|
17792
|
+
executor
|
|
17793
|
+
});
|
|
17794
|
+
const stateInfo = await getPostActionState(tabManager, name);
|
|
17795
|
+
const flowCtx = runtime2.getFlowContext();
|
|
17796
|
+
return asTextResponse(result + stateInfo + flowCtx);
|
|
17797
|
+
} catch (error) {
|
|
17798
|
+
return asErrorTextResponse(getErrorMessage(error));
|
|
17799
|
+
}
|
|
18775
17800
|
}
|
|
18776
|
-
async function
|
|
17801
|
+
async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
18777
17802
|
const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
|
|
18778
17803
|
const expectedText = (text || "").trim();
|
|
18779
17804
|
const expectedSelector = (selector || "").trim();
|
|
18780
|
-
|
|
17805
|
+
const startedAt = Date.now();
|
|
17806
|
+
const result = await waitForConditionDirect(
|
|
17807
|
+
wc,
|
|
17808
|
+
expectedText,
|
|
17809
|
+
expectedSelector,
|
|
17810
|
+
effectiveTimeout
|
|
17811
|
+
);
|
|
17812
|
+
const elapsedMs = Date.now() - startedAt;
|
|
17813
|
+
if (result === "Error: wait_for requires text or selector") {
|
|
18781
17814
|
return JSON.stringify({
|
|
18782
17815
|
matched: false,
|
|
18783
17816
|
error: "wait_for requires text or selector"
|
|
18784
17817
|
});
|
|
18785
17818
|
}
|
|
18786
|
-
if (
|
|
18787
|
-
|
|
17819
|
+
if (result.startsWith("Error: Invalid selector ")) {
|
|
17820
|
+
return JSON.stringify({
|
|
17821
|
+
matched: false,
|
|
17822
|
+
error: result.slice("Error: ".length)
|
|
17823
|
+
});
|
|
18788
17824
|
}
|
|
18789
|
-
|
|
18790
|
-
|
|
18791
|
-
|
|
17825
|
+
if (result.startsWith("Error: Page is still busy; wait_for timed out")) {
|
|
17826
|
+
return JSON.stringify({
|
|
17827
|
+
matched: false,
|
|
17828
|
+
error: result.slice("Error: ".length),
|
|
17829
|
+
elapsed_ms: elapsedMs,
|
|
17830
|
+
timeout_ms: effectiveTimeout
|
|
17831
|
+
});
|
|
17832
|
+
}
|
|
17833
|
+
if (expectedSelector && result === `Matched selector ${expectedSelector}`) {
|
|
17834
|
+
return JSON.stringify({
|
|
17835
|
+
matched: true,
|
|
17836
|
+
type: "selector",
|
|
17837
|
+
value: expectedSelector,
|
|
17838
|
+
elapsed_ms: elapsedMs
|
|
17839
|
+
});
|
|
17840
|
+
}
|
|
17841
|
+
const matchedTextPrefix = 'Matched text "';
|
|
17842
|
+
if (result.startsWith(matchedTextPrefix) && result.endsWith('"')) {
|
|
17843
|
+
return JSON.stringify({
|
|
17844
|
+
matched: true,
|
|
17845
|
+
type: "text",
|
|
17846
|
+
value: result.slice(matchedTextPrefix.length, -1),
|
|
17847
|
+
elapsed_ms: elapsedMs
|
|
17848
|
+
});
|
|
17849
|
+
}
|
|
17850
|
+
const timeoutPayload = {
|
|
17851
|
+
matched: false,
|
|
17852
|
+
type: expectedSelector ? "selector" : "text",
|
|
17853
|
+
value: expectedSelector || expectedText.slice(0, 80),
|
|
17854
|
+
elapsed_ms: elapsedMs,
|
|
17855
|
+
timeout_ms: effectiveTimeout
|
|
17856
|
+
};
|
|
17857
|
+
if (expectedSelector) {
|
|
17858
|
+
const diagnostic = await wc.executeJavaScript(`
|
|
18792
17859
|
(function() {
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
} catch (e) {
|
|
18799
|
-
return 'invalid_selector:' + e.message;
|
|
18800
|
-
}
|
|
17860
|
+
try {
|
|
17861
|
+
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
17862
|
+
return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
|
|
17863
|
+
} catch (e) {
|
|
17864
|
+
return 'selector error: ' + e.message;
|
|
18801
17865
|
}
|
|
18802
|
-
if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
|
|
18803
|
-
return '';
|
|
18804
17866
|
})()
|
|
18805
|
-
`)
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
value: expectedSelector,
|
|
18812
|
-
elapsed_ms: elapsedMs2
|
|
18813
|
-
});
|
|
18814
|
-
}
|
|
18815
|
-
if (result === "text") {
|
|
18816
|
-
return JSON.stringify({
|
|
18817
|
-
matched: true,
|
|
18818
|
-
type: "text",
|
|
18819
|
-
value: expectedText.slice(0, 80),
|
|
18820
|
-
elapsed_ms: elapsedMs2
|
|
18821
|
-
});
|
|
18822
|
-
}
|
|
18823
|
-
if (typeof result === "string" && result.startsWith("invalid_selector:")) {
|
|
18824
|
-
return JSON.stringify({
|
|
18825
|
-
matched: false,
|
|
18826
|
-
error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
|
|
18827
|
-
});
|
|
17867
|
+
`).catch((err) => {
|
|
17868
|
+
logger$7.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
17869
|
+
return null;
|
|
17870
|
+
});
|
|
17871
|
+
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
17872
|
+
timeoutPayload.diagnostic = diagnostic;
|
|
18828
17873
|
}
|
|
18829
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
18830
17874
|
}
|
|
18831
|
-
|
|
18832
|
-
const diagnostic = expectedSelector ? await wc.executeJavaScript(`
|
|
18833
|
-
(function() {
|
|
18834
|
-
try {
|
|
18835
|
-
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
18836
|
-
return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
|
|
18837
|
-
} catch (e) { return 'selector error: ' + e.message; }
|
|
18838
|
-
})()
|
|
18839
|
-
`) : null;
|
|
18840
|
-
return JSON.stringify({
|
|
18841
|
-
matched: false,
|
|
18842
|
-
type: expectedSelector ? "selector" : "text",
|
|
18843
|
-
value: expectedSelector ? expectedSelector : expectedText.slice(0, 80),
|
|
18844
|
-
elapsed_ms: elapsedMs,
|
|
18845
|
-
timeout_ms: effectiveTimeout,
|
|
18846
|
-
...diagnostic ? { diagnostic } : {}
|
|
18847
|
-
});
|
|
17875
|
+
return JSON.stringify(timeoutPayload);
|
|
18848
17876
|
}
|
|
18849
17877
|
function registerTools(server, tabManager, runtime2) {
|
|
18850
17878
|
server.registerPrompt(
|
|
@@ -18923,7 +17951,8 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
18923
17951
|
pageTitle = wc.getTitle();
|
|
18924
17952
|
const page = await extractContent(wc);
|
|
18925
17953
|
pageType = detectPageType(page);
|
|
18926
|
-
} catch {
|
|
17954
|
+
} catch (err) {
|
|
17955
|
+
logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
18927
17956
|
}
|
|
18928
17957
|
}
|
|
18929
17958
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -18975,7 +18004,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
18975
18004
|
},
|
|
18976
18005
|
async () => {
|
|
18977
18006
|
const activeTab = getActiveTabSummary(tabManager);
|
|
18978
|
-
if (!activeTab) return
|
|
18007
|
+
if (!activeTab) return asNoActiveTabResponse();
|
|
18979
18008
|
return asTextResponse(JSON.stringify(activeTab, null, 2));
|
|
18980
18009
|
}
|
|
18981
18010
|
);
|
|
@@ -19084,7 +18113,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19084
18113
|
},
|
|
19085
18114
|
async ({ mode }) => {
|
|
19086
18115
|
const tab = tabManager.getActiveTab();
|
|
19087
|
-
if (!tab) return
|
|
18116
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19088
18117
|
try {
|
|
19089
18118
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19090
18119
|
const effectiveMode = mode || "full";
|
|
@@ -19116,7 +18145,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19116
18145
|
},
|
|
19117
18146
|
async ({ mode }) => {
|
|
19118
18147
|
const tab = tabManager.getActiveTab();
|
|
19119
|
-
if (!tab) return
|
|
18148
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19120
18149
|
try {
|
|
19121
18150
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19122
18151
|
const effectiveMode = mode || "full";
|
|
@@ -19165,7 +18194,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19165
18194
|
},
|
|
19166
18195
|
async ({ url, postBody }) => {
|
|
19167
18196
|
const tab = tabManager.getActiveTab();
|
|
19168
|
-
if (!tab) return
|
|
18197
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19169
18198
|
const preCheck = await validateLinkDestination(url);
|
|
19170
18199
|
if (preCheck.status === "dead") {
|
|
19171
18200
|
return asTextResponse(
|
|
@@ -19207,7 +18236,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19207
18236
|
async ({ enabled, tabId, match, reload }) => {
|
|
19208
18237
|
const activeTab = tabManager.getActiveTab();
|
|
19209
18238
|
if (!activeTab && !tabId && !match) {
|
|
19210
|
-
return
|
|
18239
|
+
return asNoActiveTabResponse();
|
|
19211
18240
|
}
|
|
19212
18241
|
return withAction(
|
|
19213
18242
|
runtime2,
|
|
@@ -19250,7 +18279,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19250
18279
|
},
|
|
19251
18280
|
async ({ type }) => {
|
|
19252
18281
|
const tab = tabManager.getActiveTab();
|
|
19253
|
-
if (!tab) return
|
|
18282
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19254
18283
|
try {
|
|
19255
18284
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19256
18285
|
const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
|
|
@@ -19298,7 +18327,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19298
18327
|
},
|
|
19299
18328
|
async () => {
|
|
19300
18329
|
const tab = tabManager.getActiveTab();
|
|
19301
|
-
if (!tab) return
|
|
18330
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19302
18331
|
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
19303
18332
|
if (!tab.canGoBack()) {
|
|
19304
18333
|
return "No previous page in history";
|
|
@@ -19319,7 +18348,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19319
18348
|
},
|
|
19320
18349
|
async () => {
|
|
19321
18350
|
const tab = tabManager.getActiveTab();
|
|
19322
|
-
if (!tab) return
|
|
18351
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19323
18352
|
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
19324
18353
|
if (!tab.canGoForward()) {
|
|
19325
18354
|
return "No forward page in history";
|
|
@@ -19340,7 +18369,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19340
18369
|
},
|
|
19341
18370
|
async () => {
|
|
19342
18371
|
const tab = tabManager.getActiveTab();
|
|
19343
|
-
if (!tab) return
|
|
18372
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19344
18373
|
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
19345
18374
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
19346
18375
|
await waitForLoad(tab.view.webContents);
|
|
@@ -19360,7 +18389,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19360
18389
|
},
|
|
19361
18390
|
async ({ index, selector }) => {
|
|
19362
18391
|
const tab = tabManager.getActiveTab();
|
|
19363
|
-
if (!tab) return
|
|
18392
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19364
18393
|
return withAction(
|
|
19365
18394
|
runtime2,
|
|
19366
18395
|
tabManager,
|
|
@@ -19389,7 +18418,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19389
18418
|
},
|
|
19390
18419
|
async ({ index, selector }) => {
|
|
19391
18420
|
const tab = tabManager.getActiveTab();
|
|
19392
|
-
if (!tab) return
|
|
18421
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19393
18422
|
return withAction(
|
|
19394
18423
|
runtime2,
|
|
19395
18424
|
tabManager,
|
|
@@ -19418,7 +18447,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19418
18447
|
},
|
|
19419
18448
|
async ({ index, selector }) => {
|
|
19420
18449
|
const tab = tabManager.getActiveTab();
|
|
19421
|
-
if (!tab) return
|
|
18450
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19422
18451
|
return withAction(
|
|
19423
18452
|
runtime2,
|
|
19424
18453
|
tabManager,
|
|
@@ -19447,11 +18476,11 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19447
18476
|
},
|
|
19448
18477
|
async ({ index, selector }) => {
|
|
19449
18478
|
const tab = tabManager.getActiveTab();
|
|
19450
|
-
if (!tab) return
|
|
18479
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19451
18480
|
const wc = tab.view.webContents;
|
|
19452
18481
|
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19453
18482
|
if (!resolvedSelector) {
|
|
19454
|
-
return
|
|
18483
|
+
return asErrorTextResponse("No index or selector provided");
|
|
19455
18484
|
}
|
|
19456
18485
|
const result = await wc.executeJavaScript(`
|
|
19457
18486
|
(function() {
|
|
@@ -19501,7 +18530,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19501
18530
|
);
|
|
19502
18531
|
}
|
|
19503
18532
|
if ("error" in result && typeof result.error === "string") {
|
|
19504
|
-
return
|
|
18533
|
+
return asErrorTextResponse(result.error);
|
|
19505
18534
|
}
|
|
19506
18535
|
const parts = [`<${result.tag}>`];
|
|
19507
18536
|
if ("role" in result && typeof result.role === "string" && result.role.trim()) {
|
|
@@ -19532,7 +18561,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19532
18561
|
},
|
|
19533
18562
|
async ({ index, selector, text, mode }) => {
|
|
19534
18563
|
const tab = tabManager.getActiveTab();
|
|
19535
|
-
if (!tab) return
|
|
18564
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19536
18565
|
return withAction(
|
|
19537
18566
|
runtime2,
|
|
19538
18567
|
tabManager,
|
|
@@ -19571,7 +18600,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19571
18600
|
},
|
|
19572
18601
|
async ({ index, selector, text, mode }) => {
|
|
19573
18602
|
const tab = tabManager.getActiveTab();
|
|
19574
|
-
if (!tab) return
|
|
18603
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19575
18604
|
return withAction(
|
|
19576
18605
|
runtime2,
|
|
19577
18606
|
tabManager,
|
|
@@ -19608,13 +18637,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19608
18637
|
},
|
|
19609
18638
|
async ({ index, selector, label, value }) => {
|
|
19610
18639
|
const tab = tabManager.getActiveTab();
|
|
19611
|
-
if (!tab) return
|
|
18640
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19612
18641
|
return withAction(
|
|
19613
18642
|
runtime2,
|
|
19614
18643
|
tabManager,
|
|
19615
18644
|
"select_option",
|
|
19616
18645
|
{ index, selector, label, value },
|
|
19617
|
-
async () =>
|
|
18646
|
+
async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
|
|
19618
18647
|
);
|
|
19619
18648
|
}
|
|
19620
18649
|
);
|
|
@@ -19630,23 +18659,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19630
18659
|
},
|
|
19631
18660
|
async ({ index, selector }) => {
|
|
19632
18661
|
const tab = tabManager.getActiveTab();
|
|
19633
|
-
if (!tab) return
|
|
18662
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19634
18663
|
return withAction(
|
|
19635
18664
|
runtime2,
|
|
19636
18665
|
tabManager,
|
|
19637
18666
|
"submit_form",
|
|
19638
18667
|
{ index, selector },
|
|
19639
|
-
async () =>
|
|
19640
|
-
const wc = tab.view.webContents;
|
|
19641
|
-
const beforeUrl = wc.getURL();
|
|
19642
|
-
const result = await submitForm(wc, index, selector);
|
|
19643
|
-
if (result.startsWith("Error") || result.startsWith("Target") || result.startsWith("No parent") || result.startsWith("Submit control")) {
|
|
19644
|
-
return result;
|
|
19645
|
-
}
|
|
19646
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
19647
|
-
const afterUrl = wc.getURL();
|
|
19648
|
-
return afterUrl !== beforeUrl ? `${result} -> ${afterUrl}` : result;
|
|
19649
|
-
}
|
|
18668
|
+
async () => submitFormDirect(tab.view.webContents, index, selector)
|
|
19650
18669
|
);
|
|
19651
18670
|
}
|
|
19652
18671
|
);
|
|
@@ -19663,7 +18682,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19663
18682
|
},
|
|
19664
18683
|
async ({ key, index, selector }) => {
|
|
19665
18684
|
const tab = tabManager.getActiveTab();
|
|
19666
|
-
if (!tab) return
|
|
18685
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19667
18686
|
return withAction(
|
|
19668
18687
|
runtime2,
|
|
19669
18688
|
tabManager,
|
|
@@ -19672,7 +18691,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19672
18691
|
async () => {
|
|
19673
18692
|
const wc = tab.view.webContents;
|
|
19674
18693
|
const beforeUrl = wc.getURL();
|
|
19675
|
-
const result = await
|
|
18694
|
+
const result = await pressKeyDirect(wc, key, index, selector);
|
|
19676
18695
|
if (key === "Enter") {
|
|
19677
18696
|
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
19678
18697
|
const afterUrl = wc.getURL();
|
|
@@ -19699,7 +18718,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19699
18718
|
},
|
|
19700
18719
|
async ({ direction, amount }) => {
|
|
19701
18720
|
const tab = tabManager.getActiveTab();
|
|
19702
|
-
if (!tab) return
|
|
18721
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19703
18722
|
return withAction(
|
|
19704
18723
|
runtime2,
|
|
19705
18724
|
tabManager,
|
|
@@ -19722,7 +18741,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19722
18741
|
},
|
|
19723
18742
|
async () => {
|
|
19724
18743
|
const tab = tabManager.getActiveTab();
|
|
19725
|
-
if (!tab) return
|
|
18744
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19726
18745
|
return withAction(
|
|
19727
18746
|
runtime2,
|
|
19728
18747
|
tabManager,
|
|
@@ -19745,7 +18764,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19745
18764
|
},
|
|
19746
18765
|
async ({ strategy }) => {
|
|
19747
18766
|
const tab = tabManager.getActiveTab();
|
|
19748
|
-
if (!tab) return
|
|
18767
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19749
18768
|
return withAction(
|
|
19750
18769
|
runtime2,
|
|
19751
18770
|
tabManager,
|
|
@@ -19771,13 +18790,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19771
18790
|
},
|
|
19772
18791
|
async ({ text, selector, timeoutMs }) => {
|
|
19773
18792
|
const tab = tabManager.getActiveTab();
|
|
19774
|
-
if (!tab) return
|
|
18793
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19775
18794
|
return withAction(
|
|
19776
18795
|
runtime2,
|
|
19777
18796
|
tabManager,
|
|
19778
18797
|
"wait_for",
|
|
19779
18798
|
{ text, selector, timeoutMs },
|
|
19780
|
-
async () =>
|
|
18799
|
+
async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
|
|
19781
18800
|
);
|
|
19782
18801
|
}
|
|
19783
18802
|
);
|
|
@@ -19958,7 +18977,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19958
18977
|
},
|
|
19959
18978
|
async () => {
|
|
19960
18979
|
const tab = tabManager.getActiveTab();
|
|
19961
|
-
if (!tab) return
|
|
18980
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19962
18981
|
try {
|
|
19963
18982
|
const bounds = tab.view.getBounds();
|
|
19964
18983
|
if (bounds.width <= 0 || bounds.height <= 0) {
|
|
@@ -20027,7 +19046,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20027
19046
|
},
|
|
20028
19047
|
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
20029
19048
|
const tab = tabManager.getActiveTab();
|
|
20030
|
-
if (!tab) return
|
|
19049
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20031
19050
|
const normalizedText = normalizeLooseString(text);
|
|
20032
19051
|
return withAction(
|
|
20033
19052
|
runtime2,
|
|
@@ -20077,7 +19096,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20077
19096
|
},
|
|
20078
19097
|
async () => {
|
|
20079
19098
|
const tab = tabManager.getActiveTab();
|
|
20080
|
-
if (!tab) return
|
|
19099
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20081
19100
|
return withAction(
|
|
20082
19101
|
runtime2,
|
|
20083
19102
|
tabManager,
|
|
@@ -20216,8 +19235,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20216
19235
|
h.label,
|
|
20217
19236
|
void 0,
|
|
20218
19237
|
h.color
|
|
20219
|
-
).catch(
|
|
20220
|
-
|
|
19238
|
+
).catch(
|
|
19239
|
+
(err) => logger$7.warn("Failed to restore highlight after removal:", err)
|
|
19240
|
+
);
|
|
20221
19241
|
}
|
|
20222
19242
|
}
|
|
20223
19243
|
return asTextResponse(`Removed highlight ${id}`);
|
|
@@ -20281,7 +19301,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20281
19301
|
note: zod.z.string().optional().describe("Optional note about why this was bookmarked"),
|
|
20282
19302
|
on_duplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe(
|
|
20283
19303
|
'How to handle an existing bookmark with the same URL in the same folder: "ask" (default), "update", or "duplicate"'
|
|
20284
|
-
)
|
|
19304
|
+
),
|
|
19305
|
+
intent: zod.z.string().optional().describe(
|
|
19306
|
+
"Human-readable description of what this bookmark is for"
|
|
19307
|
+
),
|
|
19308
|
+
expected_content: zod.z.string().optional().describe(
|
|
19309
|
+
"Brief description of the content the agent should expect to find here"
|
|
19310
|
+
),
|
|
19311
|
+
key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
|
|
19312
|
+
agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
|
|
20285
19313
|
}
|
|
20286
19314
|
},
|
|
20287
19315
|
async ({
|
|
@@ -20294,7 +19322,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20294
19322
|
folder_summary,
|
|
20295
19323
|
create_folder_if_missing,
|
|
20296
19324
|
note,
|
|
20297
|
-
on_duplicate
|
|
19325
|
+
on_duplicate,
|
|
19326
|
+
intent,
|
|
19327
|
+
expected_content,
|
|
19328
|
+
key_fields,
|
|
19329
|
+
agent_hints
|
|
20298
19330
|
}) => {
|
|
20299
19331
|
return withAction(
|
|
20300
19332
|
runtime2,
|
|
@@ -20309,7 +19341,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20309
19341
|
folder_name,
|
|
20310
19342
|
folder_summary,
|
|
20311
19343
|
create_folder_if_missing,
|
|
20312
|
-
note
|
|
19344
|
+
note,
|
|
19345
|
+
intent,
|
|
19346
|
+
expected_content,
|
|
19347
|
+
key_fields,
|
|
19348
|
+
agent_hints
|
|
20313
19349
|
},
|
|
20314
19350
|
async () => {
|
|
20315
19351
|
const currentTab = tabManager.getActiveTab();
|
|
@@ -20339,7 +19375,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20339
19375
|
source.title,
|
|
20340
19376
|
target.folderId,
|
|
20341
19377
|
note,
|
|
20342
|
-
{
|
|
19378
|
+
{
|
|
19379
|
+
onDuplicate: on_duplicate ?? "ask",
|
|
19380
|
+
extra: getBookmarkMetadataFromArgs({
|
|
19381
|
+
intent,
|
|
19382
|
+
expected_content,
|
|
19383
|
+
key_fields,
|
|
19384
|
+
agent_hints
|
|
19385
|
+
})
|
|
19386
|
+
}
|
|
20343
19387
|
);
|
|
20344
19388
|
if (result.status === "conflict" && result.existing) {
|
|
20345
19389
|
return composeFolderAwareResponse(
|
|
@@ -20436,7 +19480,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20436
19480
|
folder_summary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
|
|
20437
19481
|
create_folder_if_missing: zod.z.boolean().optional().describe("Create folder_name automatically when it does not exist"),
|
|
20438
19482
|
note: zod.z.string().optional().describe("Optional note to attach or update on the bookmark"),
|
|
20439
|
-
archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
|
|
19483
|
+
archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
|
|
19484
|
+
intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
|
|
19485
|
+
expected_content: zod.z.string().optional().describe("Brief description of content the agent should expect"),
|
|
19486
|
+
key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
|
|
19487
|
+
agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
|
|
20440
19488
|
}
|
|
20441
19489
|
},
|
|
20442
19490
|
async (args) => {
|
|
@@ -20472,7 +19520,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20472
19520
|
const updated = updateBookmark(existing.id, {
|
|
20473
19521
|
folderId: target.folderId,
|
|
20474
19522
|
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
|
|
20475
|
-
note
|
|
19523
|
+
note,
|
|
19524
|
+
...getBookmarkMetadataFromArgs(args)
|
|
20476
19525
|
});
|
|
20477
19526
|
if (!updated) {
|
|
20478
19527
|
return `Bookmark ${existing.id} not found`;
|
|
@@ -20483,12 +19532,18 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20483
19532
|
);
|
|
20484
19533
|
}
|
|
20485
19534
|
if ("error" in source) return `Error: ${source.error}`;
|
|
20486
|
-
const
|
|
19535
|
+
const result = saveBookmarkWithPolicy(
|
|
20487
19536
|
source.url,
|
|
20488
19537
|
source.title,
|
|
20489
19538
|
target.folderId,
|
|
20490
|
-
note
|
|
19539
|
+
note,
|
|
19540
|
+
{
|
|
19541
|
+
onDuplicate: "update",
|
|
19542
|
+
extra: getBookmarkMetadataFromArgs(args)
|
|
19543
|
+
}
|
|
20491
19544
|
);
|
|
19545
|
+
const bookmark = result.bookmark;
|
|
19546
|
+
if (!bookmark) return "Error: Bookmark save failed";
|
|
20492
19547
|
return composeFolderAwareResponse(
|
|
20493
19548
|
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
20494
19549
|
target.createdFolder
|
|
@@ -20867,7 +19922,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20867
19922
|
},
|
|
20868
19923
|
async ({ title, folder, summary, note, tags }) => {
|
|
20869
19924
|
const tab = tabManager.getActiveTab();
|
|
20870
|
-
if (!tab) return
|
|
19925
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20871
19926
|
return withAction(
|
|
20872
19927
|
runtime2,
|
|
20873
19928
|
tabManager,
|
|
@@ -21011,7 +20066,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21011
20066
|
let page;
|
|
21012
20067
|
try {
|
|
21013
20068
|
page = await extractContent(wc);
|
|
21014
|
-
} catch {
|
|
20069
|
+
} catch (err) {
|
|
20070
|
+
logger$7.warn("Failed to extract page while generating suggestions:", err);
|
|
21015
20071
|
return asTextResponse(
|
|
21016
20072
|
"Could not read page. Try navigate to a working URL."
|
|
21017
20073
|
);
|
|
@@ -21118,7 +20174,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21118
20174
|
},
|
|
21119
20175
|
async ({ fields, submit }) => {
|
|
21120
20176
|
const tab = tabManager.getActiveTab();
|
|
21121
|
-
if (!tab) return
|
|
20177
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21122
20178
|
return withAction(
|
|
21123
20179
|
runtime2,
|
|
21124
20180
|
tabManager,
|
|
@@ -21132,7 +20188,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21132
20188
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
21133
20189
|
if (firstSel) {
|
|
21134
20190
|
const beforeUrl = wc.getURL();
|
|
21135
|
-
const submitResult = await
|
|
20191
|
+
const submitResult = await submitFormDirect(wc, void 0, firstSel);
|
|
21136
20192
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21137
20193
|
const afterUrl = wc.getURL();
|
|
21138
20194
|
results.push(
|
|
@@ -21175,7 +20231,7 @@ ${results.join("\n")}`;
|
|
|
21175
20231
|
submit_selector
|
|
21176
20232
|
}) => {
|
|
21177
20233
|
const tab = tabManager.getActiveTab();
|
|
21178
|
-
if (!tab) return
|
|
20234
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21179
20235
|
return withAction(
|
|
21180
20236
|
runtime2,
|
|
21181
20237
|
tabManager,
|
|
@@ -21249,7 +20305,7 @@ ${steps.join("\n")}`;
|
|
|
21249
20305
|
},
|
|
21250
20306
|
async ({ query, selector }) => {
|
|
21251
20307
|
const tab = tabManager.getActiveTab();
|
|
21252
|
-
if (!tab) return
|
|
20308
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21253
20309
|
const qLower = query.toLowerCase().trim();
|
|
21254
20310
|
const buttonLabels = [
|
|
21255
20311
|
"add to cart",
|
|
@@ -21334,7 +20390,7 @@ ${steps.join("\n")}`;
|
|
|
21334
20390
|
},
|
|
21335
20391
|
async ({ direction, selector }) => {
|
|
21336
20392
|
const tab = tabManager.getActiveTab();
|
|
21337
|
-
if (!tab) return
|
|
20393
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21338
20394
|
return withAction(
|
|
21339
20395
|
runtime2,
|
|
21340
20396
|
tabManager,
|
|
@@ -21385,7 +20441,7 @@ ${steps.join("\n")}`;
|
|
|
21385
20441
|
},
|
|
21386
20442
|
async () => {
|
|
21387
20443
|
const tab = tabManager.getActiveTab();
|
|
21388
|
-
if (!tab) return
|
|
20444
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21389
20445
|
return withAction(
|
|
21390
20446
|
runtime2,
|
|
21391
20447
|
tabManager,
|
|
@@ -21443,7 +20499,7 @@ ${steps.join("\n")}`;
|
|
|
21443
20499
|
},
|
|
21444
20500
|
async ({ index, selector: rawSelector }) => {
|
|
21445
20501
|
const tab = tabManager.getActiveTab();
|
|
21446
|
-
if (!tab) return
|
|
20502
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21447
20503
|
return withAction(
|
|
21448
20504
|
runtime2,
|
|
21449
20505
|
tabManager,
|
|
@@ -21498,7 +20554,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21498
20554
|
},
|
|
21499
20555
|
async ({ index, selector: rawSelector, position }) => {
|
|
21500
20556
|
const tab = tabManager.getActiveTab();
|
|
21501
|
-
if (!tab) return
|
|
20557
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21502
20558
|
return withAction(
|
|
21503
20559
|
runtime2,
|
|
21504
20560
|
tabManager,
|
|
@@ -21556,7 +20612,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21556
20612
|
},
|
|
21557
20613
|
async ({ timeoutMs }) => {
|
|
21558
20614
|
const tab = tabManager.getActiveTab();
|
|
21559
|
-
if (!tab) return
|
|
20615
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21560
20616
|
return withAction(
|
|
21561
20617
|
runtime2,
|
|
21562
20618
|
tabManager,
|
|
@@ -21616,11 +20672,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21616
20672
|
let targetDomain = domain;
|
|
21617
20673
|
if (!targetDomain) {
|
|
21618
20674
|
const tab = tabManager.getActiveTab();
|
|
21619
|
-
if (!tab) return
|
|
20675
|
+
if (!tab) return asErrorTextResponse("No active tab and no domain specified");
|
|
21620
20676
|
try {
|
|
21621
20677
|
targetDomain = new URL(tab.state.url).hostname;
|
|
21622
|
-
} catch {
|
|
21623
|
-
|
|
20678
|
+
} catch (err) {
|
|
20679
|
+
logger$7.warn("Failed to parse active tab URL for vault_status:", err);
|
|
20680
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21624
20681
|
}
|
|
21625
20682
|
}
|
|
21626
20683
|
const matches = findEntriesForDomain(
|
|
@@ -21679,13 +20736,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21679
20736
|
submit_index
|
|
21680
20737
|
}) => {
|
|
21681
20738
|
const tab = tabManager.getActiveTab();
|
|
21682
|
-
if (!tab) return
|
|
20739
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21683
20740
|
const wc = tab.view.webContents;
|
|
21684
20741
|
let hostname;
|
|
21685
20742
|
try {
|
|
21686
20743
|
hostname = new URL(tab.state.url).hostname;
|
|
21687
|
-
} catch {
|
|
21688
|
-
|
|
20744
|
+
} catch (err) {
|
|
20745
|
+
logger$7.warn("Failed to parse active tab URL for vault_login:", err);
|
|
20746
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21689
20747
|
}
|
|
21690
20748
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21691
20749
|
if (matches.length === 0) {
|
|
@@ -21721,7 +20779,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21721
20779
|
}
|
|
21722
20780
|
const creds = getCredential(match.id);
|
|
21723
20781
|
if (!creds) {
|
|
21724
|
-
return
|
|
20782
|
+
return asErrorTextResponse("Credential not found in vault");
|
|
21725
20783
|
}
|
|
21726
20784
|
const results = [];
|
|
21727
20785
|
if (username_index != null) {
|
|
@@ -21772,13 +20830,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21772
20830
|
},
|
|
21773
20831
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
21774
20832
|
const tab = tabManager.getActiveTab();
|
|
21775
|
-
if (!tab) return
|
|
20833
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21776
20834
|
const wc = tab.view.webContents;
|
|
21777
20835
|
let hostname;
|
|
21778
20836
|
try {
|
|
21779
20837
|
hostname = new URL(tab.state.url).hostname;
|
|
21780
|
-
} catch {
|
|
21781
|
-
|
|
20838
|
+
} catch (err) {
|
|
20839
|
+
logger$7.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
20840
|
+
return asErrorTextResponse("Could not parse active tab URL");
|
|
21782
20841
|
}
|
|
21783
20842
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21784
20843
|
const match = credential_label ? matches.find(
|
|
@@ -21949,7 +21008,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21949
21008
|
await mcpServer.connect(transport);
|
|
21950
21009
|
await transport.handleRequest(req, res);
|
|
21951
21010
|
} catch (error) {
|
|
21952
|
-
|
|
21011
|
+
logger$7.error("Error handling request:", error);
|
|
21953
21012
|
if (!res.headersSent) {
|
|
21954
21013
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
21955
21014
|
res.end(
|
|
@@ -21968,7 +21027,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21968
21027
|
};
|
|
21969
21028
|
server.once("error", (error) => {
|
|
21970
21029
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
21971
|
-
|
|
21030
|
+
logger$7.error("Server error:", error);
|
|
21972
21031
|
clearMcpAuthFile();
|
|
21973
21032
|
setMcpHealth({
|
|
21974
21033
|
configuredPort: port,
|
|
@@ -21980,14 +21039,12 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21980
21039
|
if (httpServer === server) {
|
|
21981
21040
|
httpServer = null;
|
|
21982
21041
|
}
|
|
21983
|
-
finish({
|
|
21984
|
-
ok: false,
|
|
21042
|
+
finish(errorResult(message, {
|
|
21985
21043
|
configuredPort: port,
|
|
21986
21044
|
activePort: null,
|
|
21987
21045
|
endpoint: null,
|
|
21988
|
-
authToken: null
|
|
21989
|
-
|
|
21990
|
-
});
|
|
21046
|
+
authToken: null
|
|
21047
|
+
}));
|
|
21991
21048
|
});
|
|
21992
21049
|
server.listen(port, "127.0.0.1", () => {
|
|
21993
21050
|
httpServer = server;
|
|
@@ -22002,7 +21059,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22002
21059
|
message: `MCP server listening on ${endpoint}.`
|
|
22003
21060
|
});
|
|
22004
21061
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22005
|
-
|
|
21062
|
+
logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
22006
21063
|
}
|
|
22007
21064
|
if (mcpAuthToken) {
|
|
22008
21065
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -22041,7 +21098,7 @@ function stopMcpServer() {
|
|
|
22041
21098
|
message: "MCP server is stopped."
|
|
22042
21099
|
});
|
|
22043
21100
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22044
|
-
|
|
21101
|
+
logger$7.info("Server stopped");
|
|
22045
21102
|
}
|
|
22046
21103
|
resolve();
|
|
22047
21104
|
});
|
|
@@ -22062,6 +21119,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
22062
21119
|
function isSafeAutomationKitId(id) {
|
|
22063
21120
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
22064
21121
|
}
|
|
21122
|
+
const logger$6 = createLogger("KitRegistry");
|
|
22065
21123
|
function getUserKitsDir() {
|
|
22066
21124
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
22067
21125
|
}
|
|
@@ -22099,10 +21157,10 @@ function getInstalledKits() {
|
|
|
22099
21157
|
if (isValidKit(parsed)) {
|
|
22100
21158
|
kits.push(parsed);
|
|
22101
21159
|
} else {
|
|
22102
|
-
|
|
21160
|
+
logger$6.warn(`Skipping invalid kit file: ${file}`);
|
|
22103
21161
|
}
|
|
22104
|
-
} catch {
|
|
22105
|
-
|
|
21162
|
+
} catch (err) {
|
|
21163
|
+
logger$6.warn(`Failed to read kit file: ${file}`, err);
|
|
22106
21164
|
}
|
|
22107
21165
|
}
|
|
22108
21166
|
return kits;
|
|
@@ -22114,69 +21172,67 @@ async function installKitFromFile() {
|
|
|
22114
21172
|
properties: ["openFile"]
|
|
22115
21173
|
});
|
|
22116
21174
|
if (canceled || filePaths.length === 0) {
|
|
22117
|
-
return
|
|
21175
|
+
return errorResult("canceled");
|
|
22118
21176
|
}
|
|
22119
21177
|
let raw;
|
|
22120
21178
|
try {
|
|
22121
21179
|
raw = fs$1.readFileSync(filePaths[0], "utf-8");
|
|
22122
21180
|
} catch {
|
|
22123
|
-
return
|
|
21181
|
+
return errorResult("Could not read the selected file.");
|
|
22124
21182
|
}
|
|
22125
21183
|
let parsed;
|
|
22126
21184
|
try {
|
|
22127
21185
|
parsed = JSON.parse(raw);
|
|
22128
21186
|
} catch {
|
|
22129
|
-
return
|
|
21187
|
+
return errorResult("File is not valid JSON.");
|
|
22130
21188
|
}
|
|
22131
21189
|
if (!isValidKit(parsed)) {
|
|
22132
|
-
return
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
};
|
|
21190
|
+
return errorResult(
|
|
21191
|
+
"File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
21192
|
+
);
|
|
22136
21193
|
}
|
|
22137
21194
|
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
22138
|
-
return
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
};
|
|
21195
|
+
return errorResult(
|
|
21196
|
+
`Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
21197
|
+
);
|
|
22142
21198
|
}
|
|
22143
21199
|
ensureKitsDir();
|
|
22144
21200
|
const dest = getKitFilePath(parsed.id);
|
|
22145
21201
|
if (!dest) {
|
|
22146
|
-
return
|
|
21202
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22147
21203
|
}
|
|
22148
21204
|
try {
|
|
22149
21205
|
fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
22150
21206
|
} catch {
|
|
22151
|
-
return
|
|
21207
|
+
return errorResult("Failed to save the kit file.");
|
|
22152
21208
|
}
|
|
22153
|
-
return {
|
|
21209
|
+
return okResult({ kit: parsed });
|
|
22154
21210
|
}
|
|
22155
21211
|
function uninstallKit(id, scheduledKitIds) {
|
|
22156
21212
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
22157
|
-
return
|
|
21213
|
+
return errorResult("Built-in kits cannot be removed.");
|
|
22158
21214
|
}
|
|
22159
21215
|
if (scheduledKitIds?.has(id)) {
|
|
22160
|
-
return
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
};
|
|
21216
|
+
return errorResult(
|
|
21217
|
+
"This kit has active scheduled jobs. Delete or reassign them first."
|
|
21218
|
+
);
|
|
22164
21219
|
}
|
|
22165
21220
|
ensureKitsDir();
|
|
22166
21221
|
const target = getKitFilePath(id);
|
|
22167
21222
|
if (!target) {
|
|
22168
|
-
return
|
|
21223
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22169
21224
|
}
|
|
22170
21225
|
if (!fs$1.existsSync(target)) {
|
|
22171
|
-
return
|
|
21226
|
+
return errorResult("Kit not found.");
|
|
22172
21227
|
}
|
|
22173
21228
|
try {
|
|
22174
21229
|
fs$1.unlinkSync(target);
|
|
22175
|
-
return
|
|
21230
|
+
return okResult();
|
|
22176
21231
|
} catch {
|
|
22177
|
-
return
|
|
21232
|
+
return errorResult("Failed to remove the kit file.");
|
|
22178
21233
|
}
|
|
22179
21234
|
}
|
|
21235
|
+
const logger$5 = createLogger("Scheduler");
|
|
22180
21236
|
let jobs = [];
|
|
22181
21237
|
let removeIdleListener = null;
|
|
22182
21238
|
let broadcastFn = null;
|
|
@@ -22201,7 +21257,7 @@ function saveJobs() {
|
|
|
22201
21257
|
try {
|
|
22202
21258
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
22203
21259
|
} catch (err) {
|
|
22204
|
-
|
|
21260
|
+
logger$5.warn("Failed to save jobs:", err);
|
|
22205
21261
|
}
|
|
22206
21262
|
}
|
|
22207
21263
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -22323,7 +21379,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22323
21379
|
};
|
|
22324
21380
|
startActivity();
|
|
22325
21381
|
if (!settings2.chatProvider) {
|
|
22326
|
-
|
|
21382
|
+
logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
22327
21383
|
appendActivity(
|
|
22328
21384
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
22329
21385
|
);
|
|
@@ -22331,7 +21387,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22331
21387
|
return;
|
|
22332
21388
|
}
|
|
22333
21389
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
22334
|
-
|
|
21390
|
+
logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
22335
21391
|
}
|
|
22336
21392
|
try {
|
|
22337
21393
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -22384,7 +21440,7 @@ function tick(windowState, runtime2) {
|
|
|
22384
21440
|
saveJobs();
|
|
22385
21441
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
22386
21442
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
22387
|
-
|
|
21443
|
+
logger$5.warn("Unexpected error firing job:", err);
|
|
22388
21444
|
}).finally(fireNext);
|
|
22389
21445
|
};
|
|
22390
21446
|
fireNext();
|
|
@@ -22942,6 +21998,7 @@ function registerWindowControlHandlers(mainWindow) {
|
|
|
22942
21998
|
});
|
|
22943
21999
|
}
|
|
22944
22000
|
let activeChatProvider = null;
|
|
22001
|
+
const logger$4 = createLogger("IPC");
|
|
22945
22002
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
22946
22003
|
function registerIpcHandlers(windowState, runtime2) {
|
|
22947
22004
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
@@ -23018,7 +22075,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23018
22075
|
let parsed;
|
|
23019
22076
|
try {
|
|
23020
22077
|
parsed = new URL(rawUrl);
|
|
23021
|
-
} catch {
|
|
22078
|
+
} catch (err) {
|
|
22079
|
+
logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
|
|
23022
22080
|
return;
|
|
23023
22081
|
}
|
|
23024
22082
|
if (parsed.origin !== premiumApiOrigin) return;
|
|
@@ -23075,7 +22133,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23075
22133
|
if (wc.isDestroyed()) return 0;
|
|
23076
22134
|
try {
|
|
23077
22135
|
return await getHighlightCount(wc) ?? 0;
|
|
23078
|
-
} catch {
|
|
22136
|
+
} catch (err) {
|
|
22137
|
+
logger$4.warn("Failed to get active highlight count:", err);
|
|
23079
22138
|
return 0;
|
|
23080
22139
|
}
|
|
23081
22140
|
};
|
|
@@ -23183,13 +22242,13 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23183
22242
|
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
|
|
23184
22243
|
try {
|
|
23185
22244
|
if (!config || typeof config !== "object" || !("id" in config)) {
|
|
23186
|
-
return
|
|
22245
|
+
return errorResult("Invalid provider configuration", { models: [] });
|
|
23187
22246
|
}
|
|
23188
22247
|
return await fetchProviderModels(
|
|
23189
22248
|
config
|
|
23190
22249
|
);
|
|
23191
22250
|
} catch (err) {
|
|
23192
|
-
return {
|
|
22251
|
+
return errorResult(getErrorMessage(err), { models: [] });
|
|
23193
22252
|
}
|
|
23194
22253
|
});
|
|
23195
22254
|
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
|
|
@@ -23344,14 +22403,21 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23344
22403
|
Channels.BOOKMARK_SAVE,
|
|
23345
22404
|
(_, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
23346
22405
|
trackBookmarkAction("save");
|
|
23347
|
-
|
|
22406
|
+
const result = saveBookmarkWithPolicy(url, title, folderId, note, {
|
|
22407
|
+
onDuplicate: "update",
|
|
23348
22408
|
extra: {
|
|
23349
|
-
|
|
23350
|
-
|
|
23351
|
-
|
|
23352
|
-
|
|
22409
|
+
...normalizeBookmarkMetadata({
|
|
22410
|
+
intent,
|
|
22411
|
+
expectedContent,
|
|
22412
|
+
keyFields,
|
|
22413
|
+
agentHints
|
|
22414
|
+
})
|
|
23353
22415
|
}
|
|
23354
22416
|
});
|
|
22417
|
+
if (!result.bookmark) {
|
|
22418
|
+
throw new Error("Bookmark save failed");
|
|
22419
|
+
}
|
|
22420
|
+
return result.bookmark;
|
|
23355
22421
|
}
|
|
23356
22422
|
);
|
|
23357
22423
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
|
|
@@ -23377,12 +22443,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23377
22443
|
const wc = activeTab.view.webContents;
|
|
23378
22444
|
const result = await captureSelectionHighlight(wc);
|
|
23379
22445
|
if (result.success && result.text) {
|
|
23380
|
-
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
23381
|
-
|
|
22446
|
+
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
22447
|
+
(err) => logger$4.warn("Failed to highlight captured selection:", err)
|
|
22448
|
+
);
|
|
23382
22449
|
await emitHighlightCount();
|
|
23383
22450
|
}
|
|
23384
22451
|
return result;
|
|
23385
|
-
} catch {
|
|
22452
|
+
} catch (err) {
|
|
22453
|
+
logger$4.warn("Failed to capture highlight from active tab:", err);
|
|
23386
22454
|
return { success: false, message: "Could not capture selection" };
|
|
23387
22455
|
}
|
|
23388
22456
|
});
|
|
@@ -23406,7 +22474,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23406
22474
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
23407
22475
|
}
|
|
23408
22476
|
});
|
|
23409
|
-
} catch {
|
|
22477
|
+
} catch (err) {
|
|
22478
|
+
logger$4.warn("Failed to persist auto-highlight selection:", err);
|
|
23410
22479
|
}
|
|
23411
22480
|
});
|
|
23412
22481
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
@@ -23419,7 +22488,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23419
22488
|
if (wc.isDestroyed()) return false;
|
|
23420
22489
|
try {
|
|
23421
22490
|
return scrollToHighlight(wc, index);
|
|
23422
|
-
} catch {
|
|
22491
|
+
} catch (err) {
|
|
22492
|
+
logger$4.warn("Failed to scroll to highlight:", err);
|
|
23423
22493
|
return false;
|
|
23424
22494
|
}
|
|
23425
22495
|
});
|
|
@@ -23434,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23434
22504
|
await emitHighlightCount();
|
|
23435
22505
|
}
|
|
23436
22506
|
return removed;
|
|
23437
|
-
} catch {
|
|
22507
|
+
} catch (err) {
|
|
22508
|
+
logger$4.warn("Failed to remove highlight at index:", err);
|
|
23438
22509
|
return false;
|
|
23439
22510
|
}
|
|
23440
22511
|
});
|
|
@@ -23449,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23449
22520
|
await emitHighlightCount();
|
|
23450
22521
|
}
|
|
23451
22522
|
return cleared;
|
|
23452
|
-
} catch {
|
|
22523
|
+
} catch (err) {
|
|
22524
|
+
logger$4.warn("Failed to clear highlight elements:", err);
|
|
23453
22525
|
return false;
|
|
23454
22526
|
}
|
|
23455
22527
|
});
|
|
@@ -23533,7 +22605,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23533
22605
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
|
|
23534
22606
|
assertString(email, "email");
|
|
23535
22607
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23536
|
-
return
|
|
22608
|
+
return errorResult("Invalid email format");
|
|
23537
22609
|
}
|
|
23538
22610
|
trackPremiumFunnel("activation_attempted");
|
|
23539
22611
|
const result = await requestActivationCode(email);
|
|
@@ -23549,11 +22621,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23549
22621
|
assertString(code, "code");
|
|
23550
22622
|
assertString(challengeToken, "challengeToken");
|
|
23551
22623
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23552
|
-
return {
|
|
23553
|
-
|
|
23554
|
-
|
|
23555
|
-
error: "Invalid email format"
|
|
23556
|
-
};
|
|
22624
|
+
return errorResult("Invalid email format", {
|
|
22625
|
+
state: getPremiumState()
|
|
22626
|
+
});
|
|
23557
22627
|
}
|
|
23558
22628
|
trackPremiumFunnel("activation_attempted");
|
|
23559
22629
|
const result = await verifyActivationCode(email, code, challengeToken);
|
|
@@ -23988,6 +23058,7 @@ ${lines.join("\n")}
|
|
|
23988
23058
|
}
|
|
23989
23059
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
23990
23060
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
23061
|
+
const logger$3 = createLogger("Runtime");
|
|
23991
23062
|
function clone(value) {
|
|
23992
23063
|
return JSON.parse(JSON.stringify(value));
|
|
23993
23064
|
}
|
|
@@ -24377,9 +23448,7 @@ ${progress}
|
|
|
24377
23448
|
JSON.stringify(persisted, null, 2),
|
|
24378
23449
|
"utf-8"
|
|
24379
23450
|
)
|
|
24380
|
-
).catch(
|
|
24381
|
-
(err) => console.error("[Vessel] Failed to persist runtime state:", err)
|
|
24382
|
-
);
|
|
23451
|
+
).catch((err) => logger$3.error("Failed to persist runtime state:", err));
|
|
24383
23452
|
}
|
|
24384
23453
|
schedulePersist() {
|
|
24385
23454
|
this.persistDirty = true;
|
|
@@ -24644,6 +23713,7 @@ function installDownloadHandler(chromeView) {
|
|
|
24644
23713
|
});
|
|
24645
23714
|
});
|
|
24646
23715
|
}
|
|
23716
|
+
const logger$2 = createLogger("Shortcuts");
|
|
24647
23717
|
function registerHighlightShortcut(mainWindow, tabManager) {
|
|
24648
23718
|
const register = () => {
|
|
24649
23719
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
@@ -24653,7 +23723,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
|
|
|
24653
23723
|
tabManager.captureHighlightFromActiveTab();
|
|
24654
23724
|
});
|
|
24655
23725
|
if (!success) {
|
|
24656
|
-
|
|
23726
|
+
logger$2.warn("Failed to register Ctrl+H shortcut");
|
|
24657
23727
|
}
|
|
24658
23728
|
};
|
|
24659
23729
|
register();
|
|
@@ -24722,6 +23792,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
24722
23792
|
});
|
|
24723
23793
|
}
|
|
24724
23794
|
}
|
|
23795
|
+
const logger$1 = createLogger("Splash");
|
|
24725
23796
|
function findIconBase64() {
|
|
24726
23797
|
const candidates = [
|
|
24727
23798
|
path$1.join(process.resourcesPath, "vessel-icon.png"),
|
|
@@ -24884,7 +23955,7 @@ function createSplashWindow() {
|
|
|
24884
23955
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|
|
24885
23956
|
void splash.loadFile(tmpPath);
|
|
24886
23957
|
} catch (err) {
|
|
24887
|
-
|
|
23958
|
+
logger$1.warn("Failed to write temp HTML, using fallback:", err);
|
|
24888
23959
|
void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
|
|
24889
23960
|
}
|
|
24890
23961
|
return splash;
|
|
@@ -24894,6 +23965,7 @@ function closeSplash(splash, delayMs = 0) {
|
|
|
24894
23965
|
if (!splash.isDestroyed()) splash.close();
|
|
24895
23966
|
}, delayMs);
|
|
24896
23967
|
}
|
|
23968
|
+
const logger = createLogger("Bootstrap");
|
|
24897
23969
|
let runtime = null;
|
|
24898
23970
|
function checkWritableUserData(userDataPath) {
|
|
24899
23971
|
const issues = [];
|
|
@@ -25013,7 +24085,7 @@ async function bootstrap() {
|
|
|
25013
24085
|
};
|
|
25014
24086
|
let didInitializeChromeRenderer = false;
|
|
25015
24087
|
const splashTimeout = setTimeout(() => {
|
|
25016
|
-
|
|
24088
|
+
logger.warn("Renderer did not finish loading before splash timeout");
|
|
25017
24089
|
revealMainWindow();
|
|
25018
24090
|
}, 8e3);
|
|
25019
24091
|
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
@@ -25064,8 +24136,8 @@ async function bootstrap() {
|
|
|
25064
24136
|
"did-fail-load",
|
|
25065
24137
|
(_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
|
25066
24138
|
if (!isMainFrame) return;
|
|
25067
|
-
|
|
25068
|
-
"
|
|
24139
|
+
logger.error(
|
|
24140
|
+
"Chrome renderer failed to load:",
|
|
25069
24141
|
errorCode,
|
|
25070
24142
|
errorDescription,
|
|
25071
24143
|
validatedURL
|
|
@@ -25076,21 +24148,21 @@ async function bootstrap() {
|
|
|
25076
24148
|
);
|
|
25077
24149
|
loadRenderers(chromeView, sidebarView, devtoolsPanelView);
|
|
25078
24150
|
startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
|
|
25079
|
-
|
|
24151
|
+
logger.error("MCP server failed to start:", err);
|
|
25080
24152
|
});
|
|
25081
24153
|
}
|
|
25082
24154
|
process.on("uncaughtException", (error) => {
|
|
25083
|
-
|
|
24155
|
+
logger.error("Uncaught exception:", error.message, error.stack);
|
|
25084
24156
|
electron.app.quit();
|
|
25085
24157
|
});
|
|
25086
24158
|
process.on("unhandledRejection", (reason) => {
|
|
25087
|
-
|
|
25088
|
-
"
|
|
24159
|
+
logger.error(
|
|
24160
|
+
"Unhandled rejection:",
|
|
25089
24161
|
reason instanceof Error ? reason.message : reason
|
|
25090
24162
|
);
|
|
25091
24163
|
});
|
|
25092
24164
|
electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
25093
|
-
|
|
24165
|
+
logger.error("Failed to bootstrap application:", error);
|
|
25094
24166
|
electron.app.quit();
|
|
25095
24167
|
});
|
|
25096
24168
|
electron.app.on("window-all-closed", () => {
|