@quanta-intellect/vessel-browser 0.1.63 → 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;
|
|
@@ -4376,6 +4419,7 @@ function inferPageSchema(page) {
|
|
|
4376
4419
|
confidence
|
|
4377
4420
|
};
|
|
4378
4421
|
}
|
|
4422
|
+
const logger$e = createLogger("Extractor");
|
|
4379
4423
|
const EMPTY_PAGE_CONTENT = {
|
|
4380
4424
|
title: "",
|
|
4381
4425
|
content: "",
|
|
@@ -5122,7 +5166,8 @@ async function executeScript(webContents, script) {
|
|
|
5122
5166
|
timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
|
|
5123
5167
|
})
|
|
5124
5168
|
]);
|
|
5125
|
-
} catch {
|
|
5169
|
+
} catch (err) {
|
|
5170
|
+
logger$e.warn("Failed to execute page script:", err);
|
|
5126
5171
|
return null;
|
|
5127
5172
|
} finally {
|
|
5128
5173
|
if (timer) {
|
|
@@ -5230,7 +5275,8 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5230
5275
|
);
|
|
5231
5276
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
5232
5277
|
}
|
|
5233
|
-
} catch {
|
|
5278
|
+
} catch (err) {
|
|
5279
|
+
logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
5234
5280
|
}
|
|
5235
5281
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
5236
5282
|
}
|
|
@@ -6019,6 +6065,9 @@ function isClickReadLoop(names) {
|
|
|
6019
6065
|
}
|
|
6020
6066
|
return clickReadPairs >= 2;
|
|
6021
6067
|
}
|
|
6068
|
+
function isRecord(value) {
|
|
6069
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6070
|
+
}
|
|
6022
6071
|
class AnthropicProvider {
|
|
6023
6072
|
agentToolProfile = "default";
|
|
6024
6073
|
client;
|
|
@@ -6115,10 +6164,14 @@ class AnthropicProvider {
|
|
|
6115
6164
|
}
|
|
6116
6165
|
} else if (event.type === "content_block_stop" && currentToolUse) {
|
|
6117
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
|
+
}
|
|
6118
6171
|
toolUseBlocks.push({
|
|
6119
6172
|
id: currentToolUse.id,
|
|
6120
6173
|
name: currentToolUse.name,
|
|
6121
|
-
input
|
|
6174
|
+
input
|
|
6122
6175
|
});
|
|
6123
6176
|
} catch {
|
|
6124
6177
|
toolUseBlocks.push({
|
|
@@ -6165,7 +6218,7 @@ class AnthropicProvider {
|
|
|
6165
6218
|
});
|
|
6166
6219
|
continue;
|
|
6167
6220
|
}
|
|
6168
|
-
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) ?? "";
|
|
6169
6222
|
onChunk(`
|
|
6170
6223
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
6171
6224
|
`);
|
|
@@ -6392,6 +6445,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
6392
6445
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
6393
6446
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
6394
6447
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
6448
|
+
const logger$d = createLogger("OpenAIProvider");
|
|
6395
6449
|
function shouldDebugAgentLoop() {
|
|
6396
6450
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
6397
6451
|
return value === "1" || value === "true";
|
|
@@ -6798,9 +6852,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
6798
6852
|
function logAgentLoopDebug(payload) {
|
|
6799
6853
|
if (!shouldDebugAgentLoop()) return;
|
|
6800
6854
|
try {
|
|
6801
|
-
|
|
6855
|
+
logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
6802
6856
|
} catch (err) {
|
|
6803
|
-
|
|
6857
|
+
logger$d.warn("Failed to serialize debug payload:", err);
|
|
6804
6858
|
}
|
|
6805
6859
|
}
|
|
6806
6860
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -6820,7 +6874,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
|
6820
6874
|
const argsJson = match[2] ?? "{}";
|
|
6821
6875
|
let parsedArgs = {};
|
|
6822
6876
|
try {
|
|
6823
|
-
|
|
6877
|
+
const raw = JSON.parse(argsJson);
|
|
6878
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
6879
|
+
parsedArgs = raw;
|
|
6880
|
+
}
|
|
6824
6881
|
} catch {
|
|
6825
6882
|
continue;
|
|
6826
6883
|
}
|
|
@@ -7217,7 +7274,7 @@ class OpenAICompatProvider {
|
|
|
7217
7274
|
}
|
|
7218
7275
|
continue;
|
|
7219
7276
|
}
|
|
7220
|
-
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) ?? "";
|
|
7221
7278
|
onChunk(`
|
|
7222
7279
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
7223
7280
|
`);
|
|
@@ -7375,7 +7432,7 @@ async function fetchProviderModels(config) {
|
|
|
7375
7432
|
if (normalized.id === "anthropic") {
|
|
7376
7433
|
const client2 = new Anthropic({ apiKey: normalized.apiKey });
|
|
7377
7434
|
const page2 = await client2.models.list();
|
|
7378
|
-
return {
|
|
7435
|
+
return okResult({ models: page2.data.map((model) => model.id) });
|
|
7379
7436
|
}
|
|
7380
7437
|
const meta = PROVIDERS[normalized.id];
|
|
7381
7438
|
const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
|
|
@@ -7387,9 +7444,10 @@ async function fetchProviderModels(config) {
|
|
|
7387
7444
|
const models = page.data.map((model) => model.id);
|
|
7388
7445
|
const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
|
|
7389
7446
|
return {
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7447
|
+
...okResult({
|
|
7448
|
+
models,
|
|
7449
|
+
...warning ? { warning } : {}
|
|
7450
|
+
})
|
|
7393
7451
|
};
|
|
7394
7452
|
}
|
|
7395
7453
|
function createProvider(config) {
|
|
@@ -7404,6 +7462,7 @@ function createProvider(config) {
|
|
|
7404
7462
|
return new OpenAICompatProvider(normalized);
|
|
7405
7463
|
}
|
|
7406
7464
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
7465
|
+
const logger$c = createLogger("DevTrace");
|
|
7407
7466
|
let cachedFactory;
|
|
7408
7467
|
function createNoopTraceSession() {
|
|
7409
7468
|
return {
|
|
@@ -7436,7 +7495,7 @@ function loadLocalFactory() {
|
|
|
7436
7495
|
return cachedFactory;
|
|
7437
7496
|
}
|
|
7438
7497
|
} catch (err) {
|
|
7439
|
-
|
|
7498
|
+
logger$c.warn("Failed to load local trace logger:", err);
|
|
7440
7499
|
}
|
|
7441
7500
|
}
|
|
7442
7501
|
return cachedFactory;
|
|
@@ -11171,6 +11230,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
11171
11230
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
11172
11231
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
11173
11232
|
}
|
|
11233
|
+
const logger$b = createLogger("Screenshot");
|
|
11174
11234
|
async function captureScreenshot(wc) {
|
|
11175
11235
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
11176
11236
|
await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
|
|
@@ -11180,13 +11240,21 @@ async function captureScreenshot(wc) {
|
|
|
11180
11240
|
const size = image.getSize();
|
|
11181
11241
|
const base64 = image.toPNG().toString("base64");
|
|
11182
11242
|
if (base64) {
|
|
11183
|
-
return {
|
|
11243
|
+
return okResult({
|
|
11244
|
+
base64,
|
|
11245
|
+
width: size.width,
|
|
11246
|
+
height: size.height
|
|
11247
|
+
});
|
|
11184
11248
|
}
|
|
11185
11249
|
}
|
|
11186
|
-
} catch {
|
|
11250
|
+
} catch (err) {
|
|
11251
|
+
logger$b.debug(
|
|
11252
|
+
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
11253
|
+
getErrorMessage(err)
|
|
11254
|
+
);
|
|
11187
11255
|
}
|
|
11188
11256
|
}
|
|
11189
|
-
return
|
|
11257
|
+
return errorResult("Page image was empty after 3 attempts");
|
|
11190
11258
|
}
|
|
11191
11259
|
const SESSION_VERSION = 1;
|
|
11192
11260
|
function getSessionsDir() {
|
|
@@ -12081,17 +12149,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
12081
12149
|
appliedFilters
|
|
12082
12150
|
};
|
|
12083
12151
|
}
|
|
12152
|
+
const logger$a = createLogger("PageActions");
|
|
12084
12153
|
function getBookmarkMetadataFromArgs(args) {
|
|
12085
12154
|
return normalizeBookmarkMetadata({
|
|
12086
|
-
intent: args.intent,
|
|
12087
|
-
expectedContent: args.expectedContent,
|
|
12088
|
-
keyFields: args.keyFields,
|
|
12089
|
-
agentHints: args.agentHints
|
|
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
|
|
12090
12159
|
});
|
|
12091
12160
|
}
|
|
12092
12161
|
const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
|
|
12093
12162
|
const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
|
|
12094
|
-
async function loadPermittedUrl
|
|
12163
|
+
async function loadPermittedUrl(wc, url) {
|
|
12095
12164
|
assertPermittedNavigationURL(url);
|
|
12096
12165
|
await wc.loadURL(url);
|
|
12097
12166
|
}
|
|
@@ -12264,7 +12333,9 @@ async function executePageScript(wc, script, options) {
|
|
|
12264
12333
|
return PAGE_SCRIPT_TIMEOUT;
|
|
12265
12334
|
}
|
|
12266
12335
|
return result;
|
|
12267
|
-
} catch {
|
|
12336
|
+
} catch (err) {
|
|
12337
|
+
const label = options?.label ? ` (${options.label})` : "";
|
|
12338
|
+
logger$a.warn(`Failed to execute page script${label}:`, err);
|
|
12268
12339
|
return null;
|
|
12269
12340
|
} finally {
|
|
12270
12341
|
if (timer) {
|
|
@@ -12364,7 +12435,8 @@ async function getPostSearchSummary(wc) {
|
|
|
12364
12435
|
Search results snapshot:
|
|
12365
12436
|
${truncated}`;
|
|
12366
12437
|
}
|
|
12367
|
-
} catch {
|
|
12438
|
+
} catch (err) {
|
|
12439
|
+
logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
12368
12440
|
}
|
|
12369
12441
|
const fallback = await getPostNavSummary(wc);
|
|
12370
12442
|
return fallback ? `${fallback}
|
|
@@ -12386,11 +12458,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
|
|
|
12386
12458
|
Page snapshot after navigation:
|
|
12387
12459
|
${truncated}`;
|
|
12388
12460
|
}
|
|
12389
|
-
} catch {
|
|
12461
|
+
} catch (err) {
|
|
12462
|
+
logger$a.warn("Failed to build post-click navigation summary:", err);
|
|
12390
12463
|
}
|
|
12391
12464
|
return "";
|
|
12392
12465
|
}
|
|
12393
|
-
async function scrollPage
|
|
12466
|
+
async function scrollPage(wc, deltaY) {
|
|
12394
12467
|
const getScrollY = async () => {
|
|
12395
12468
|
const scrollY = await executePageScript(
|
|
12396
12469
|
wc,
|
|
@@ -12434,7 +12507,7 @@ async function scrollPage$1(wc, deltaY) {
|
|
|
12434
12507
|
movedY: Math.round(afterY - beforeY)
|
|
12435
12508
|
};
|
|
12436
12509
|
}
|
|
12437
|
-
async function clickElement
|
|
12510
|
+
async function clickElement(wc, selector) {
|
|
12438
12511
|
const target = await executePageScript(
|
|
12439
12512
|
wc,
|
|
12440
12513
|
`
|
|
@@ -12523,7 +12596,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12523
12596
|
return "Error: Could not resolve click coordinates";
|
|
12524
12597
|
}
|
|
12525
12598
|
if (hiddenWindow) {
|
|
12526
|
-
const activationResult = await activateElement
|
|
12599
|
+
const activationResult = await activateElement(wc, selector);
|
|
12527
12600
|
if (activationResult.startsWith("Error:")) {
|
|
12528
12601
|
return activationResult;
|
|
12529
12602
|
}
|
|
@@ -12538,7 +12611,7 @@ async function clickElement$1(wc, selector) {
|
|
|
12538
12611
|
await sleep(80);
|
|
12539
12612
|
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
12540
12613
|
}
|
|
12541
|
-
async function activateElement
|
|
12614
|
+
async function activateElement(wc, selector) {
|
|
12542
12615
|
const activated = await executePageScript(
|
|
12543
12616
|
wc,
|
|
12544
12617
|
`
|
|
@@ -12570,7 +12643,7 @@ async function activateElement$1(wc, selector) {
|
|
|
12570
12643
|
}
|
|
12571
12644
|
return "Activated element via DOM click";
|
|
12572
12645
|
}
|
|
12573
|
-
async function describeElementForClick
|
|
12646
|
+
async function describeElementForClick(wc, selector) {
|
|
12574
12647
|
const result = await executePageScript(
|
|
12575
12648
|
wc,
|
|
12576
12649
|
`
|
|
@@ -12879,7 +12952,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12879
12952
|
return;
|
|
12880
12953
|
}
|
|
12881
12954
|
}
|
|
12882
|
-
} catch {
|
|
12955
|
+
} catch (err) {
|
|
12956
|
+
logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
12883
12957
|
}
|
|
12884
12958
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
12885
12959
|
try {
|
|
@@ -12887,14 +12961,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
12887
12961
|
await wc.loadURL(snapshot.url);
|
|
12888
12962
|
await waitForLoad(wc, 3e3);
|
|
12889
12963
|
return;
|
|
12890
|
-
} catch {
|
|
12964
|
+
} catch (err) {
|
|
12965
|
+
logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
12891
12966
|
}
|
|
12892
12967
|
}
|
|
12893
12968
|
if (snapshot.url) {
|
|
12894
12969
|
try {
|
|
12895
12970
|
await wc.reload();
|
|
12896
12971
|
await waitForLoad(wc, 3e3);
|
|
12897
|
-
} catch {
|
|
12972
|
+
} catch (err) {
|
|
12973
|
+
logger$a.warn("Failed to restore locale via page reload:", err);
|
|
12898
12974
|
}
|
|
12899
12975
|
}
|
|
12900
12976
|
}
|
|
@@ -13025,14 +13101,14 @@ Go back to search results to select the next product.`;
|
|
|
13025
13101
|
if (!overlayHint) {
|
|
13026
13102
|
return cartSummary;
|
|
13027
13103
|
}
|
|
13028
|
-
const dialogActions = await getCartDialogActions
|
|
13104
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13029
13105
|
const actionsSuffix = dialogActions ? `
|
|
13030
13106
|
${dialogActions}
|
|
13031
13107
|
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
13032
13108
|
return `
|
|
13033
13109
|
${overlayHint}${actionsSuffix}${cartSummary}`;
|
|
13034
13110
|
}
|
|
13035
|
-
async function clickResolvedSelector
|
|
13111
|
+
async function clickResolvedSelector(wc, selector) {
|
|
13036
13112
|
if (selector.startsWith("__vessel_idx:")) {
|
|
13037
13113
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
13038
13114
|
const beforeUrl2 = wc.getURL();
|
|
@@ -13065,10 +13141,10 @@ Go back and select a different product.`;
|
|
|
13065
13141
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
13066
13142
|
const afterUrl2 = wc.getURL();
|
|
13067
13143
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
13068
|
-
let idxOverlay = await detectPostClickOverlay
|
|
13144
|
+
let idxOverlay = await detectPostClickOverlay(wc);
|
|
13069
13145
|
if (!idxOverlay && idxCartMatch) {
|
|
13070
13146
|
await sleep(1200);
|
|
13071
|
-
idxOverlay = await detectPostClickOverlay
|
|
13147
|
+
idxOverlay = await detectPostClickOverlay(wc);
|
|
13072
13148
|
}
|
|
13073
13149
|
if (idxCartMatch) {
|
|
13074
13150
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
|
|
@@ -13077,7 +13153,7 @@ Go back and select a different product.`;
|
|
|
13077
13153
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
13078
13154
|
if (hrefMatch) {
|
|
13079
13155
|
try {
|
|
13080
|
-
await loadPermittedUrl
|
|
13156
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13081
13157
|
await waitForLoad(wc, 8e3);
|
|
13082
13158
|
const hrefUrl = wc.getURL();
|
|
13083
13159
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13132,10 +13208,10 @@ Go back and select a different product.`;
|
|
|
13132
13208
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
13133
13209
|
const afterUrl2 = wc.getURL();
|
|
13134
13210
|
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
13135
|
-
let shadowOverlay = await detectPostClickOverlay
|
|
13211
|
+
let shadowOverlay = await detectPostClickOverlay(wc);
|
|
13136
13212
|
if (!shadowOverlay && shadowCartMatch) {
|
|
13137
13213
|
await sleep(1200);
|
|
13138
|
-
shadowOverlay = await detectPostClickOverlay
|
|
13214
|
+
shadowOverlay = await detectPostClickOverlay(wc);
|
|
13139
13215
|
}
|
|
13140
13216
|
if (shadowCartMatch) {
|
|
13141
13217
|
return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
|
|
@@ -13144,7 +13220,7 @@ Go back and select a different product.`;
|
|
|
13144
13220
|
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
13145
13221
|
if (hrefMatch) {
|
|
13146
13222
|
try {
|
|
13147
|
-
await loadPermittedUrl
|
|
13223
|
+
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
13148
13224
|
await waitForLoad(wc, 8e3);
|
|
13149
13225
|
const hrefUrl = wc.getURL();
|
|
13150
13226
|
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
@@ -13157,14 +13233,14 @@ ${shadowOverlay}` : `${result}
|
|
|
13157
13233
|
Note: Page did not change after click.`;
|
|
13158
13234
|
}
|
|
13159
13235
|
const beforeUrl = wc.getURL();
|
|
13160
|
-
const elInfo = await describeElementForClick
|
|
13236
|
+
const elInfo = await describeElementForClick(wc, selector);
|
|
13161
13237
|
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
13162
13238
|
const cartMatch = isAddToCartText(elInfo.text);
|
|
13163
13239
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
13164
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).`;
|
|
13165
13241
|
}
|
|
13166
13242
|
if (!cartMatch && recentCartClicks.has(beforeUrl)) {
|
|
13167
|
-
const dialogActions = await getCartDialogActions
|
|
13243
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13168
13244
|
if (dialogActions) {
|
|
13169
13245
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
13170
13246
|
${dialogActions}
|
|
@@ -13187,14 +13263,14 @@ Go back and select a different product.`;
|
|
|
13187
13263
|
}
|
|
13188
13264
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
13189
13265
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
13190
|
-
const clickResult = await clickElement
|
|
13266
|
+
const clickResult = await clickElement(wc, selector);
|
|
13191
13267
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
13192
13268
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13193
13269
|
const afterUrl = wc.getURL();
|
|
13194
13270
|
if (afterUrl !== beforeUrl) {
|
|
13195
13271
|
return `${clickText} -> ${afterUrl}`;
|
|
13196
13272
|
}
|
|
13197
|
-
const overlayHint = await detectPostClickOverlay
|
|
13273
|
+
const overlayHint = await detectPostClickOverlay(wc);
|
|
13198
13274
|
if (overlayHint) {
|
|
13199
13275
|
if (cartMatch) {
|
|
13200
13276
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
@@ -13208,7 +13284,7 @@ ${overlayHint}`;
|
|
|
13208
13284
|
}
|
|
13209
13285
|
if (cartMatch) {
|
|
13210
13286
|
await sleep(1200);
|
|
13211
|
-
const delayedOverlayHint = await detectPostClickOverlay
|
|
13287
|
+
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
13212
13288
|
if (delayedOverlayHint) {
|
|
13213
13289
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
|
|
13214
13290
|
wc,
|
|
@@ -13221,7 +13297,7 @@ ${overlayHint}`;
|
|
|
13221
13297
|
beforeUrl
|
|
13222
13298
|
)}`;
|
|
13223
13299
|
}
|
|
13224
|
-
const activationResult = await activateElement
|
|
13300
|
+
const activationResult = await activateElement(wc, selector);
|
|
13225
13301
|
if (!activationResult.startsWith("Error:")) {
|
|
13226
13302
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
13227
13303
|
const fallbackUrl = wc.getURL();
|
|
@@ -13229,7 +13305,7 @@ ${overlayHint}`;
|
|
|
13229
13305
|
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
13230
13306
|
}
|
|
13231
13307
|
}
|
|
13232
|
-
const postActivationOverlayHint = await detectPostClickOverlay
|
|
13308
|
+
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
13233
13309
|
if (postActivationOverlayHint) {
|
|
13234
13310
|
return `${clickText} (${clickResult})
|
|
13235
13311
|
${postActivationOverlayHint}`;
|
|
@@ -13239,13 +13315,14 @@ ${postActivationOverlayHint}`;
|
|
|
13239
13315
|
const validation = await validateLinkDestination(elInfo.href);
|
|
13240
13316
|
if (validation.status !== "dead") {
|
|
13241
13317
|
try {
|
|
13242
|
-
await loadPermittedUrl
|
|
13318
|
+
await loadPermittedUrl(wc, elInfo.href);
|
|
13243
13319
|
await waitForLoad(wc, 8e3);
|
|
13244
13320
|
const hrefFallbackUrl = wc.getURL();
|
|
13245
13321
|
if (hrefFallbackUrl !== beforeUrl) {
|
|
13246
13322
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
13247
13323
|
}
|
|
13248
|
-
} catch {
|
|
13324
|
+
} catch (err) {
|
|
13325
|
+
logger$a.warn("Failed href fallback after click, returning generic click result:", err);
|
|
13249
13326
|
}
|
|
13250
13327
|
}
|
|
13251
13328
|
}
|
|
@@ -13289,11 +13366,12 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
13289
13366
|
await sleep(500);
|
|
13290
13367
|
return result;
|
|
13291
13368
|
}
|
|
13292
|
-
} catch {
|
|
13369
|
+
} catch (err) {
|
|
13370
|
+
logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
13293
13371
|
}
|
|
13294
13372
|
return null;
|
|
13295
13373
|
}
|
|
13296
|
-
async function getCartDialogActions
|
|
13374
|
+
async function getCartDialogActions(wc) {
|
|
13297
13375
|
const result = await executePageScript(
|
|
13298
13376
|
wc,
|
|
13299
13377
|
`
|
|
@@ -13338,7 +13416,7 @@ async function getCartDialogActions$1(wc) {
|
|
|
13338
13416
|
return `Available dialog actions:
|
|
13339
13417
|
${result.actions.join("\n")}`;
|
|
13340
13418
|
}
|
|
13341
|
-
async function detectPostClickOverlay
|
|
13419
|
+
async function detectPostClickOverlay(wc) {
|
|
13342
13420
|
const result = await executePageScript(
|
|
13343
13421
|
wc,
|
|
13344
13422
|
`
|
|
@@ -13444,7 +13522,7 @@ async function detectPostClickOverlay$1(wc) {
|
|
|
13444
13522
|
const desc = result.label ? ` ("${result.label}")` : "";
|
|
13445
13523
|
return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
|
|
13446
13524
|
}
|
|
13447
|
-
async function dismissPopup
|
|
13525
|
+
async function dismissPopup(wc) {
|
|
13448
13526
|
const before = await extractContent(wc);
|
|
13449
13527
|
const initialBlocking = before.overlays.filter(
|
|
13450
13528
|
(overlay) => overlay.blocksInteraction
|
|
@@ -13497,7 +13575,7 @@ async function dismissPopup$1(wc) {
|
|
|
13497
13575
|
if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
13498
13576
|
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
13499
13577
|
}
|
|
13500
|
-
const dialogActions = await getCartDialogActions
|
|
13578
|
+
const dialogActions = await getCartDialogActions(wc);
|
|
13501
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."}`;
|
|
13502
13580
|
}
|
|
13503
13581
|
}
|
|
@@ -13657,7 +13735,7 @@ async function dismissPopup$1(wc) {
|
|
|
13657
13735
|
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
13658
13736
|
continue;
|
|
13659
13737
|
}
|
|
13660
|
-
const result = await clickElement
|
|
13738
|
+
const result = await clickElement(wc, candidate.selector);
|
|
13661
13739
|
if (result.startsWith("Error:")) continue;
|
|
13662
13740
|
await sleep(250);
|
|
13663
13741
|
const postClickLocale = await getLocaleSnapshot(wc);
|
|
@@ -13699,7 +13777,7 @@ function describeOverlayState(page) {
|
|
|
13699
13777
|
}
|
|
13700
13778
|
async function clickOverlayCandidate(wc, action) {
|
|
13701
13779
|
if (!action?.selector) return null;
|
|
13702
|
-
const result = await clickResolvedSelector
|
|
13780
|
+
const result = await clickResolvedSelector(wc, action.selector);
|
|
13703
13781
|
return `${action.label || action.selector}: ${result}`;
|
|
13704
13782
|
}
|
|
13705
13783
|
async function tryDismissConsentIframe(wc) {
|
|
@@ -13910,7 +13988,7 @@ Submitted modal: ${submitResult}`;
|
|
|
13910
13988
|
}
|
|
13911
13989
|
}
|
|
13912
13990
|
if (!actionMessage) {
|
|
13913
|
-
actionMessage = `Fallback popup handling: ${await dismissPopup
|
|
13991
|
+
actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
|
|
13914
13992
|
}
|
|
13915
13993
|
steps.push(actionMessage);
|
|
13916
13994
|
if (overlay.kind === "cookie_consent") {
|
|
@@ -14085,7 +14163,7 @@ async function fillFormFields(wc, fields) {
|
|
|
14085
14163
|
});
|
|
14086
14164
|
continue;
|
|
14087
14165
|
}
|
|
14088
|
-
const result = await setElementValue
|
|
14166
|
+
const result = await setElementValue(
|
|
14089
14167
|
wc,
|
|
14090
14168
|
selector,
|
|
14091
14169
|
String(field.value || "")
|
|
@@ -14094,14 +14172,14 @@ async function fillFormFields(wc, fields) {
|
|
|
14094
14172
|
}
|
|
14095
14173
|
return results;
|
|
14096
14174
|
}
|
|
14097
|
-
function getTabByMatch
|
|
14175
|
+
function getTabByMatch(tabManager, match) {
|
|
14098
14176
|
if (!match) return null;
|
|
14099
14177
|
const lowered = match.toLowerCase();
|
|
14100
14178
|
return tabManager.getAllStates().find(
|
|
14101
14179
|
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
14102
14180
|
) || null;
|
|
14103
14181
|
}
|
|
14104
|
-
function isDangerousAction
|
|
14182
|
+
function isDangerousAction(name) {
|
|
14105
14183
|
return [
|
|
14106
14184
|
"navigate",
|
|
14107
14185
|
"open_bookmark",
|
|
@@ -14112,6 +14190,7 @@ function isDangerousAction$1(name) {
|
|
|
14112
14190
|
"press_key",
|
|
14113
14191
|
"create_tab",
|
|
14114
14192
|
"switch_tab",
|
|
14193
|
+
"close_tab",
|
|
14115
14194
|
"restore_checkpoint",
|
|
14116
14195
|
"load_session",
|
|
14117
14196
|
"login",
|
|
@@ -14120,7 +14199,7 @@ function isDangerousAction$1(name) {
|
|
|
14120
14199
|
"paginate"
|
|
14121
14200
|
].includes(name);
|
|
14122
14201
|
}
|
|
14123
|
-
async function setElementValue
|
|
14202
|
+
async function setElementValue(wc, selector, value) {
|
|
14124
14203
|
if (selector.startsWith("__vessel_idx:")) {
|
|
14125
14204
|
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
14126
14205
|
const result2 = await executePageScript(
|
|
@@ -14224,7 +14303,7 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
14224
14303
|
);
|
|
14225
14304
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14226
14305
|
}
|
|
14227
|
-
async function typeKeystroke
|
|
14306
|
+
async function typeKeystroke(wc, selector, value) {
|
|
14228
14307
|
const result = await executePageScript(
|
|
14229
14308
|
wc,
|
|
14230
14309
|
`
|
|
@@ -14272,7 +14351,7 @@ async function typeKeystroke$1(wc, selector, value) {
|
|
|
14272
14351
|
);
|
|
14273
14352
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
|
|
14274
14353
|
}
|
|
14275
|
-
async function hoverElement
|
|
14354
|
+
async function hoverElement(wc, selector) {
|
|
14276
14355
|
const pos = await wc.executeJavaScript(`
|
|
14277
14356
|
(function() {
|
|
14278
14357
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14302,7 +14381,7 @@ async function hoverElement$1(wc, selector) {
|
|
|
14302
14381
|
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
14303
14382
|
return `Hovered: ${label}`;
|
|
14304
14383
|
}
|
|
14305
|
-
async function focusElement
|
|
14384
|
+
async function focusElement(wc, selector) {
|
|
14306
14385
|
return wc.executeJavaScript(`
|
|
14307
14386
|
(function() {
|
|
14308
14387
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -14316,7 +14395,7 @@ async function focusElement$1(wc, selector) {
|
|
|
14316
14395
|
})()
|
|
14317
14396
|
`);
|
|
14318
14397
|
}
|
|
14319
|
-
async function waitForCondition
|
|
14398
|
+
async function waitForCondition(wc, args) {
|
|
14320
14399
|
const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
|
|
14321
14400
|
const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
|
|
14322
14401
|
const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
|
|
@@ -14375,8 +14454,8 @@ function findCheckpoint(checkpoints, args) {
|
|
|
14375
14454
|
}
|
|
14376
14455
|
return null;
|
|
14377
14456
|
}
|
|
14378
|
-
function resolveBookmarkFolderTarget
|
|
14379
|
-
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() : "";
|
|
14380
14459
|
if (folderId) {
|
|
14381
14460
|
if (folderId === UNSORTED_ID) {
|
|
14382
14461
|
return {
|
|
@@ -14390,7 +14469,7 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14390
14469
|
}
|
|
14391
14470
|
return { folderId: folder2.id, folderName: folder2.name };
|
|
14392
14471
|
}
|
|
14393
|
-
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 : "";
|
|
14394
14473
|
if (!folderName || folderName.toLowerCase() === "unsorted") {
|
|
14395
14474
|
return {
|
|
14396
14475
|
folderId: UNSORTED_ID,
|
|
@@ -14401,10 +14480,11 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14401
14480
|
if (existing) {
|
|
14402
14481
|
return { folderId: existing.id, folderName: existing.name };
|
|
14403
14482
|
}
|
|
14404
|
-
|
|
14483
|
+
const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
|
|
14484
|
+
if (createIfMissing === false) {
|
|
14405
14485
|
return { folderName, error: `Folder "${folderName}" not found` };
|
|
14406
14486
|
}
|
|
14407
|
-
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;
|
|
14408
14488
|
const { folder } = ensureFolder(folderName, folderSummary);
|
|
14409
14489
|
return {
|
|
14410
14490
|
folderId: folder.id,
|
|
@@ -14412,27 +14492,27 @@ function resolveBookmarkFolderTarget$1(args) {
|
|
|
14412
14492
|
createdFolder: folder.name
|
|
14413
14493
|
};
|
|
14414
14494
|
}
|
|
14415
|
-
function formatFolderStatus
|
|
14495
|
+
function formatFolderStatus(limit = 6) {
|
|
14416
14496
|
const folders = listFolderOverviews();
|
|
14417
14497
|
const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
|
|
14418
14498
|
return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
|
|
14419
14499
|
}
|
|
14420
|
-
function describeFolder
|
|
14500
|
+
function describeFolder(folderId) {
|
|
14421
14501
|
if (!folderId || folderId === UNSORTED_ID) {
|
|
14422
14502
|
return "Unsorted";
|
|
14423
14503
|
}
|
|
14424
14504
|
return getFolder(folderId)?.name ?? folderId;
|
|
14425
14505
|
}
|
|
14426
|
-
function composeDuplicateBookmarkResponse
|
|
14506
|
+
function composeDuplicateBookmarkResponse(args) {
|
|
14427
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.`;
|
|
14428
14508
|
}
|
|
14429
|
-
function composeFolderAwareResponse
|
|
14509
|
+
function composeFolderAwareResponse(message, createdFolder) {
|
|
14430
14510
|
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
14431
14511
|
` : "";
|
|
14432
14512
|
return `${prefix}${message}
|
|
14433
|
-
${formatFolderStatus
|
|
14513
|
+
${formatFolderStatus()}`;
|
|
14434
14514
|
}
|
|
14435
|
-
async function selectOption
|
|
14515
|
+
async function selectOption(wc, args) {
|
|
14436
14516
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
14437
14517
|
if (!selector) return "Error: No select element index or selector provided";
|
|
14438
14518
|
const result = await executePageScript(
|
|
@@ -14468,7 +14548,7 @@ async function selectOption$1(wc, args) {
|
|
|
14468
14548
|
);
|
|
14469
14549
|
return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
|
|
14470
14550
|
}
|
|
14471
|
-
async function submitForm
|
|
14551
|
+
async function submitForm(wc, args) {
|
|
14472
14552
|
const beforeUrl = wc.getURL();
|
|
14473
14553
|
let selector = await resolveSelector(wc, args.index, args.selector);
|
|
14474
14554
|
if (!selector) {
|
|
@@ -14609,7 +14689,7 @@ async function submitForm$1(wc, args) {
|
|
|
14609
14689
|
if (formInfo.params) {
|
|
14610
14690
|
url.search = formInfo.params;
|
|
14611
14691
|
}
|
|
14612
|
-
await loadPermittedUrl
|
|
14692
|
+
await loadPermittedUrl(wc, url.toString());
|
|
14613
14693
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
14614
14694
|
const afterUrl = wc.getURL();
|
|
14615
14695
|
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
@@ -14651,8 +14731,20 @@ async function submitForm$1(wc, args) {
|
|
|
14651
14731
|
}
|
|
14652
14732
|
return "Submitted form";
|
|
14653
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
|
+
}
|
|
14654
14746
|
async function clickElementBySelector(wc, selector) {
|
|
14655
|
-
return clickResolvedSelector
|
|
14747
|
+
return clickResolvedSelector(wc, selector);
|
|
14656
14748
|
}
|
|
14657
14749
|
function normalizeSearchQuery(query) {
|
|
14658
14750
|
return query.replace(/\s+/g, " ").trim();
|
|
@@ -14936,7 +15028,7 @@ async function searchPage(wc, args) {
|
|
|
14936
15028
|
const shortcut = buildSearchShortcut(wc.getURL(), query);
|
|
14937
15029
|
if (shortcut) {
|
|
14938
15030
|
const beforeUrl2 = wc.getURL();
|
|
14939
|
-
await loadPermittedUrl
|
|
15031
|
+
await loadPermittedUrl(wc, shortcut.url);
|
|
14940
15032
|
await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
|
|
14941
15033
|
const afterUrl2 = wc.getURL();
|
|
14942
15034
|
const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
|
|
@@ -14954,13 +15046,13 @@ async function searchPage(wc, args) {
|
|
|
14954
15046
|
if (!searchInfo?.selector) {
|
|
14955
15047
|
return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
|
|
14956
15048
|
}
|
|
14957
|
-
const fillResult = await setElementValue
|
|
15049
|
+
const fillResult = await setElementValue(wc, searchInfo.selector, query);
|
|
14958
15050
|
if (fillResult.startsWith("Error:")) {
|
|
14959
15051
|
return fillResult;
|
|
14960
15052
|
}
|
|
14961
15053
|
await sleep(100);
|
|
14962
15054
|
const beforeUrl = wc.getURL();
|
|
14963
|
-
const keyResult = await pressKey
|
|
15055
|
+
const keyResult = await pressKey(wc, {
|
|
14964
15056
|
key: "Enter",
|
|
14965
15057
|
selector: searchInfo.selector
|
|
14966
15058
|
});
|
|
@@ -14984,7 +15076,7 @@ async function searchPage(wc, args) {
|
|
|
14984
15076
|
}
|
|
14985
15077
|
return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
|
|
14986
15078
|
}
|
|
14987
|
-
async function pressKey
|
|
15079
|
+
async function pressKey(wc, args) {
|
|
14988
15080
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
14989
15081
|
if (!key) return "Error: No key provided";
|
|
14990
15082
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
@@ -15196,9 +15288,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
15196
15288
|
"metrics",
|
|
15197
15289
|
"wait_for_navigation"
|
|
15198
15290
|
]);
|
|
15199
|
-
async function executeAction(name,
|
|
15291
|
+
async function executeAction(name, args, ctx) {
|
|
15200
15292
|
name = normalizeToolAlias(name);
|
|
15201
|
-
const args = rawArgs;
|
|
15202
15293
|
if (!KNOWN_TOOLS.has(name)) {
|
|
15203
15294
|
for (const known of KNOWN_TOOLS) {
|
|
15204
15295
|
if (name.startsWith(known) && name.length > known.length) {
|
|
@@ -15247,7 +15338,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15247
15338
|
name,
|
|
15248
15339
|
args,
|
|
15249
15340
|
tabId,
|
|
15250
|
-
dangerous: isDangerousAction
|
|
15341
|
+
dangerous: isDangerousAction(name),
|
|
15251
15342
|
executor: async () => {
|
|
15252
15343
|
switch (name) {
|
|
15253
15344
|
case "screenshot": {
|
|
@@ -15295,7 +15386,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15295
15386
|
case "switch_tab": {
|
|
15296
15387
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15297
15388
|
if (!targetId) {
|
|
15298
|
-
targetId = getTabByMatch
|
|
15389
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15299
15390
|
}
|
|
15300
15391
|
if (!targetId) return "Error: No matching tab found";
|
|
15301
15392
|
ctx.tabManager.switchTab(targetId);
|
|
@@ -15389,7 +15480,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15389
15480
|
if (!selector) {
|
|
15390
15481
|
return "Error: No element index, selector, or visible text provided";
|
|
15391
15482
|
}
|
|
15392
|
-
return clickResolvedSelector
|
|
15483
|
+
return clickResolvedSelector(wc, selector);
|
|
15393
15484
|
}
|
|
15394
15485
|
case "inspect_element": {
|
|
15395
15486
|
if (!wc) return "Error: No active tab";
|
|
@@ -15421,18 +15512,18 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15421
15512
|
if (!selector) return "Error: No element index or selector provided";
|
|
15422
15513
|
const mode = typeof args.mode === "string" ? args.mode : "default";
|
|
15423
15514
|
if (mode === "keystroke") {
|
|
15424
|
-
return typeKeystroke
|
|
15515
|
+
return typeKeystroke(wc, selector, String(args.text || ""));
|
|
15425
15516
|
}
|
|
15426
|
-
return setElementValue
|
|
15517
|
+
return setElementValue(wc, selector, String(args.text || ""));
|
|
15427
15518
|
}
|
|
15428
15519
|
case "select_option": {
|
|
15429
15520
|
if (!wc) return "Error: No active tab";
|
|
15430
|
-
return selectOption
|
|
15521
|
+
return selectOption(wc, args);
|
|
15431
15522
|
}
|
|
15432
15523
|
case "submit_form": {
|
|
15433
15524
|
if (!wc) return "Error: No active tab";
|
|
15434
15525
|
const beforeUrl = wc.getURL();
|
|
15435
|
-
const result2 = await submitForm
|
|
15526
|
+
const result2 = await submitForm(wc, args);
|
|
15436
15527
|
if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
|
|
15437
15528
|
return result2;
|
|
15438
15529
|
}
|
|
@@ -15443,7 +15534,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15443
15534
|
case "press_key": {
|
|
15444
15535
|
if (!wc) return "Error: No active tab";
|
|
15445
15536
|
const beforeUrl = wc.getURL();
|
|
15446
|
-
const result2 = await pressKey
|
|
15537
|
+
const result2 = await pressKey(wc, args);
|
|
15447
15538
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
15448
15539
|
if (key === "Enter") {
|
|
15449
15540
|
await waitForPotentialNavigation(wc, beforeUrl, 3e3);
|
|
@@ -15460,20 +15551,20 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15460
15551
|
if (!wc) return "Error: No active tab";
|
|
15461
15552
|
const pixels = coerceOptionalNumber(args.amount) ?? 500;
|
|
15462
15553
|
const dir = args.direction === "up" ? -pixels : pixels;
|
|
15463
|
-
const result2 = await scrollPage
|
|
15554
|
+
const result2 = await scrollPage(wc, dir);
|
|
15464
15555
|
return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
|
|
15465
15556
|
}
|
|
15466
15557
|
case "hover": {
|
|
15467
15558
|
if (!wc) return "Error: No active tab";
|
|
15468
15559
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15469
15560
|
if (!selector) return "Error: No element index or selector provided";
|
|
15470
|
-
return hoverElement
|
|
15561
|
+
return hoverElement(wc, selector);
|
|
15471
15562
|
}
|
|
15472
15563
|
case "focus": {
|
|
15473
15564
|
if (!wc) return "Error: No active tab";
|
|
15474
15565
|
const selector = await resolveSelector(wc, args.index, args.selector);
|
|
15475
15566
|
if (!selector) return "Error: No element index or selector provided";
|
|
15476
|
-
return focusElement
|
|
15567
|
+
return focusElement(wc, selector);
|
|
15477
15568
|
}
|
|
15478
15569
|
case "set_ad_blocking": {
|
|
15479
15570
|
const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
|
|
@@ -15482,7 +15573,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15482
15573
|
}
|
|
15483
15574
|
let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
|
|
15484
15575
|
if (!targetId) {
|
|
15485
|
-
targetId = getTabByMatch
|
|
15576
|
+
targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
|
|
15486
15577
|
}
|
|
15487
15578
|
if (!targetId) {
|
|
15488
15579
|
targetId = ctx.tabManager.getActiveTabId() || "";
|
|
@@ -15501,7 +15592,7 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15501
15592
|
}
|
|
15502
15593
|
case "dismiss_popup": {
|
|
15503
15594
|
if (!wc) return "Error: No active tab";
|
|
15504
|
-
return dismissPopup
|
|
15595
|
+
return dismissPopup(wc);
|
|
15505
15596
|
}
|
|
15506
15597
|
case "clear_overlays": {
|
|
15507
15598
|
if (!wc) return "Error: No active tab";
|
|
@@ -15524,7 +15615,8 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15524
15615
|
}, 6e3)
|
|
15525
15616
|
)
|
|
15526
15617
|
]);
|
|
15527
|
-
} catch {
|
|
15618
|
+
} catch (err) {
|
|
15619
|
+
logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
15528
15620
|
content = null;
|
|
15529
15621
|
}
|
|
15530
15622
|
if (!content || content.content.length === 0) {
|
|
@@ -15540,11 +15632,13 @@ async function executeAction(name, rawArgs, ctx) {
|
|
|
15540
15632
|
extractContent(wc),
|
|
15541
15633
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
15542
15634
|
]);
|
|
15543
|
-
} catch {
|
|
15635
|
+
} catch (err) {
|
|
15636
|
+
logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
15544
15637
|
content = null;
|
|
15545
15638
|
}
|
|
15546
15639
|
}
|
|
15547
|
-
} catch {
|
|
15640
|
+
} catch (err) {
|
|
15641
|
+
logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
15548
15642
|
}
|
|
15549
15643
|
}
|
|
15550
15644
|
if (content && content.content.length > 0) {
|
|
@@ -15584,7 +15678,7 @@ ${truncated}`;
|
|
|
15584
15678
|
}
|
|
15585
15679
|
case "wait_for": {
|
|
15586
15680
|
if (!wc) return "Error: No active tab";
|
|
15587
|
-
return waitForCondition
|
|
15681
|
+
return waitForCondition(wc, args);
|
|
15588
15682
|
}
|
|
15589
15683
|
case "wait_for_navigation": {
|
|
15590
15684
|
if (!wc) return "Error: No active tab";
|
|
@@ -15727,12 +15821,12 @@ ${truncated}`;
|
|
|
15727
15821
|
(folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
|
|
15728
15822
|
);
|
|
15729
15823
|
if (existing) {
|
|
15730
|
-
return composeFolderAwareResponse
|
|
15824
|
+
return composeFolderAwareResponse(
|
|
15731
15825
|
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
15732
15826
|
);
|
|
15733
15827
|
}
|
|
15734
15828
|
const folder = createFolderWithSummary(name2, summary);
|
|
15735
|
-
return composeFolderAwareResponse
|
|
15829
|
+
return composeFolderAwareResponse(
|
|
15736
15830
|
`Created folder "${folder.name}" (id=${folder.id})`
|
|
15737
15831
|
);
|
|
15738
15832
|
}
|
|
@@ -15744,7 +15838,7 @@ ${truncated}`;
|
|
|
15744
15838
|
resolvedSelector
|
|
15745
15839
|
});
|
|
15746
15840
|
if ("error" in source) return `Error: ${source.error}`;
|
|
15747
|
-
const target = resolveBookmarkFolderTarget
|
|
15841
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15748
15842
|
if (target.error) return target.error;
|
|
15749
15843
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
15750
15844
|
const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
|
|
@@ -15759,10 +15853,10 @@ ${truncated}`;
|
|
|
15759
15853
|
}
|
|
15760
15854
|
);
|
|
15761
15855
|
if (result2.status === "conflict" && result2.existing) {
|
|
15762
|
-
return composeFolderAwareResponse
|
|
15763
|
-
composeDuplicateBookmarkResponse
|
|
15856
|
+
return composeFolderAwareResponse(
|
|
15857
|
+
composeDuplicateBookmarkResponse({
|
|
15764
15858
|
url: source.url,
|
|
15765
|
-
folderName: describeFolder
|
|
15859
|
+
folderName: describeFolder(target.folderId),
|
|
15766
15860
|
bookmarkId: result2.existing.id
|
|
15767
15861
|
}),
|
|
15768
15862
|
target.createdFolder
|
|
@@ -15771,13 +15865,13 @@ ${truncated}`;
|
|
|
15771
15865
|
const bookmark = result2.bookmark;
|
|
15772
15866
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15773
15867
|
const verb = result2.status === "updated" ? "Updated" : "Saved";
|
|
15774
|
-
return composeFolderAwareResponse
|
|
15775
|
-
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder
|
|
15868
|
+
return composeFolderAwareResponse(
|
|
15869
|
+
`${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15776
15870
|
target.createdFolder
|
|
15777
15871
|
);
|
|
15778
15872
|
}
|
|
15779
15873
|
case "organize_bookmark": {
|
|
15780
|
-
const target = resolveBookmarkFolderTarget
|
|
15874
|
+
const target = resolveBookmarkFolderTarget(args);
|
|
15781
15875
|
if (target.error) return target.error;
|
|
15782
15876
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15783
15877
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15801,8 +15895,8 @@ ${truncated}`;
|
|
|
15801
15895
|
if (!updated) {
|
|
15802
15896
|
return `Bookmark ${existing.id} not found`;
|
|
15803
15897
|
}
|
|
15804
|
-
return composeFolderAwareResponse
|
|
15805
|
-
`Organized existing bookmark "${updated.title}" into "${describeFolder
|
|
15898
|
+
return composeFolderAwareResponse(
|
|
15899
|
+
`Organized existing bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15806
15900
|
target.createdFolder
|
|
15807
15901
|
);
|
|
15808
15902
|
}
|
|
@@ -15819,13 +15913,13 @@ ${truncated}`;
|
|
|
15819
15913
|
);
|
|
15820
15914
|
const bookmark = result2.bookmark;
|
|
15821
15915
|
if (!bookmark) return "Error: Bookmark save failed";
|
|
15822
|
-
return composeFolderAwareResponse
|
|
15823
|
-
`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})`,
|
|
15824
15918
|
target.createdFolder
|
|
15825
15919
|
);
|
|
15826
15920
|
}
|
|
15827
15921
|
case "archive_bookmark": {
|
|
15828
|
-
const target = resolveBookmarkFolderTarget
|
|
15922
|
+
const target = resolveBookmarkFolderTarget({ archive: true });
|
|
15829
15923
|
if (target.error) return target.error;
|
|
15830
15924
|
const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
|
|
15831
15925
|
const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
|
|
@@ -15848,8 +15942,8 @@ ${truncated}`;
|
|
|
15848
15942
|
if (!updated) {
|
|
15849
15943
|
return `Bookmark ${existing.id} not found`;
|
|
15850
15944
|
}
|
|
15851
|
-
return composeFolderAwareResponse
|
|
15852
|
-
`Archived bookmark "${updated.title}" into "${describeFolder
|
|
15945
|
+
return composeFolderAwareResponse(
|
|
15946
|
+
`Archived bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
|
|
15853
15947
|
target.createdFolder
|
|
15854
15948
|
);
|
|
15855
15949
|
}
|
|
@@ -15862,8 +15956,8 @@ ${truncated}`;
|
|
|
15862
15956
|
target.folderId,
|
|
15863
15957
|
note
|
|
15864
15958
|
);
|
|
15865
|
-
return composeFolderAwareResponse
|
|
15866
|
-
`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})`,
|
|
15867
15961
|
target.createdFolder
|
|
15868
15962
|
);
|
|
15869
15963
|
}
|
|
@@ -15951,7 +16045,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
15951
16045
|
let page;
|
|
15952
16046
|
try {
|
|
15953
16047
|
page = await extractContent(wc);
|
|
15954
|
-
} catch {
|
|
16048
|
+
} catch (err) {
|
|
16049
|
+
logger$a.warn("Failed to extract content for suggest:", err);
|
|
15955
16050
|
return "Could not read page. Try navigate to a working URL.";
|
|
15956
16051
|
}
|
|
15957
16052
|
const suggestions = [];
|
|
@@ -16056,7 +16151,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
16056
16151
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
16057
16152
|
if (firstSel) {
|
|
16058
16153
|
const beforeUrl = wc.getURL();
|
|
16059
|
-
const submitResult = await submitForm
|
|
16154
|
+
const submitResult = await submitForm(wc, { selector: firstSel });
|
|
16060
16155
|
await waitForPotentialNavigation(wc, beforeUrl);
|
|
16061
16156
|
const afterUrl = wc.getURL();
|
|
16062
16157
|
results.push(
|
|
@@ -16104,13 +16199,13 @@ ${results.join("\n")}`;
|
|
|
16104
16199
|
);
|
|
16105
16200
|
if (!passSel)
|
|
16106
16201
|
return "Error: Could not find password field. Try providing password_selector.";
|
|
16107
|
-
const userResult = await setElementValue
|
|
16202
|
+
const userResult = await setElementValue(
|
|
16108
16203
|
wc,
|
|
16109
16204
|
userSel,
|
|
16110
16205
|
String(args.username || "")
|
|
16111
16206
|
);
|
|
16112
16207
|
steps.push(userResult);
|
|
16113
|
-
const passResult = await setElementValue
|
|
16208
|
+
const passResult = await setElementValue(
|
|
16114
16209
|
wc,
|
|
16115
16210
|
passSel,
|
|
16116
16211
|
String(args.password || "")
|
|
@@ -16118,7 +16213,7 @@ ${results.join("\n")}`;
|
|
|
16118
16213
|
steps.push(passResult);
|
|
16119
16214
|
const beforeUrl = wc.getURL();
|
|
16120
16215
|
if (args.submit_selector) {
|
|
16121
|
-
await clickResolvedSelector
|
|
16216
|
+
await clickResolvedSelector(wc, args.submit_selector);
|
|
16122
16217
|
} else {
|
|
16123
16218
|
const clicked = await executePageScript(
|
|
16124
16219
|
wc,
|
|
@@ -16157,7 +16252,7 @@ ${steps.join("\n")}`;
|
|
|
16157
16252
|
if (!wc) return "Error: No active tab";
|
|
16158
16253
|
const beforeUrl = wc.getURL();
|
|
16159
16254
|
if (args.selector) {
|
|
16160
|
-
return clickResolvedSelector
|
|
16255
|
+
return clickResolvedSelector(wc, args.selector);
|
|
16161
16256
|
}
|
|
16162
16257
|
const isNext = args.direction === "next";
|
|
16163
16258
|
const clicked = await executePageScript(
|
|
@@ -17282,6 +17377,7 @@ const ALGORITHM = "aes-256-gcm";
|
|
|
17282
17377
|
const IV_LENGTH = 12;
|
|
17283
17378
|
const AUTH_TAG_LENGTH = 16;
|
|
17284
17379
|
let cachedEntries = null;
|
|
17380
|
+
const logger$9 = createLogger("Vault");
|
|
17285
17381
|
function getVaultDir() {
|
|
17286
17382
|
return electron.app.getPath("userData");
|
|
17287
17383
|
}
|
|
@@ -17348,7 +17444,7 @@ function loadVault() {
|
|
|
17348
17444
|
cachedEntries = JSON.parse(json);
|
|
17349
17445
|
return cachedEntries;
|
|
17350
17446
|
} catch (err) {
|
|
17351
|
-
|
|
17447
|
+
logger$9.error("Failed to load vault:", err);
|
|
17352
17448
|
throw new Error(
|
|
17353
17449
|
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
17354
17450
|
);
|
|
@@ -17490,6 +17586,7 @@ async function requestConsent(request) {
|
|
|
17490
17586
|
}
|
|
17491
17587
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
17492
17588
|
const MAX_ENTRIES = 1e3;
|
|
17589
|
+
const logger$8 = createLogger("VaultAudit");
|
|
17493
17590
|
function getAuditPath() {
|
|
17494
17591
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
17495
17592
|
}
|
|
@@ -17499,7 +17596,7 @@ function appendAuditEntry(entry) {
|
|
|
17499
17596
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
17500
17597
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
17501
17598
|
} catch (err) {
|
|
17502
|
-
|
|
17599
|
+
logger$8.error("Failed to write audit log:", err);
|
|
17503
17600
|
}
|
|
17504
17601
|
}
|
|
17505
17602
|
function readAuditLog(limit = 100) {
|
|
@@ -17509,20 +17606,13 @@ function readAuditLog(limit = 100) {
|
|
|
17509
17606
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
17510
17607
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
17511
17608
|
} catch (err) {
|
|
17512
|
-
|
|
17609
|
+
logger$8.error("Failed to read audit log:", err);
|
|
17513
17610
|
return [];
|
|
17514
17611
|
}
|
|
17515
17612
|
}
|
|
17516
17613
|
let httpServer = null;
|
|
17517
17614
|
let mcpAuthToken = null;
|
|
17518
|
-
|
|
17519
|
-
return normalizeBookmarkMetadata({
|
|
17520
|
-
intent: args.intent,
|
|
17521
|
-
expectedContent: args.expected_content,
|
|
17522
|
-
keyFields: args.key_fields,
|
|
17523
|
-
agentHints: args.agent_hints
|
|
17524
|
-
});
|
|
17525
|
-
}
|
|
17615
|
+
const logger$7 = createLogger("MCP");
|
|
17526
17616
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
17527
17617
|
function getMcpAuthFilePath() {
|
|
17528
17618
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -17558,7 +17648,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
17558
17648
|
{ mode: 384 }
|
|
17559
17649
|
);
|
|
17560
17650
|
} catch (err) {
|
|
17561
|
-
|
|
17651
|
+
logger$7.warn("Failed to write auth file:", err);
|
|
17562
17652
|
}
|
|
17563
17653
|
}
|
|
17564
17654
|
function clearMcpAuthFile() {
|
|
@@ -17583,15 +17673,17 @@ function clearMcpAuthFile() {
|
|
|
17583
17673
|
{ mode: 384 }
|
|
17584
17674
|
);
|
|
17585
17675
|
} catch (err) {
|
|
17586
|
-
|
|
17676
|
+
logger$7.warn("Failed to clear auth file:", err);
|
|
17587
17677
|
}
|
|
17588
17678
|
}
|
|
17589
17679
|
function asTextResponse(text) {
|
|
17590
17680
|
return { content: [{ type: "text", text }] };
|
|
17591
17681
|
}
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17682
|
+
function asErrorTextResponse(message) {
|
|
17683
|
+
return asTextResponse(`Error: ${message}`);
|
|
17684
|
+
}
|
|
17685
|
+
function asNoActiveTabResponse() {
|
|
17686
|
+
return asErrorTextResponse("No active tab");
|
|
17595
17687
|
}
|
|
17596
17688
|
function asPromptResponse(text) {
|
|
17597
17689
|
return {
|
|
@@ -17606,6 +17698,9 @@ function asPromptResponse(text) {
|
|
|
17606
17698
|
]
|
|
17607
17699
|
};
|
|
17608
17700
|
}
|
|
17701
|
+
function isDangerousMcpAction(name) {
|
|
17702
|
+
return name === "close_tab" || isDangerousAction(name);
|
|
17703
|
+
}
|
|
17609
17704
|
function getActiveTabSummary(tabManager) {
|
|
17610
17705
|
const activeTab = tabManager.getActiveTab();
|
|
17611
17706
|
const activeTabId = tabManager.getActiveTabId();
|
|
@@ -17622,1298 +17717,162 @@ function getActiveTabSummary(tabManager) {
|
|
|
17622
17717
|
humanFocused: true
|
|
17623
17718
|
};
|
|
17624
17719
|
}
|
|
17625
|
-
function
|
|
17626
|
-
const
|
|
17627
|
-
|
|
17628
|
-
const
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
const
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
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);
|
|
17649
17767
|
}
|
|
17650
|
-
return {
|
|
17651
|
-
|
|
17652
|
-
const requestedName = typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
|
|
17653
|
-
if (!requestedName || requestedName.toLowerCase() === "unsorted") {
|
|
17654
|
-
return {
|
|
17655
|
-
folderId: UNSORTED_ID,
|
|
17656
|
-
folderName: "Unsorted"
|
|
17657
|
-
};
|
|
17768
|
+
return `${warning}
|
|
17769
|
+
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
17658
17770
|
}
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17771
|
+
if (interactActions.includes(name)) {
|
|
17772
|
+
return `
|
|
17773
|
+
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
17662
17774
|
}
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
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}]`;
|
|
17669
17781
|
}
|
|
17670
|
-
|
|
17671
|
-
const { folder } = ensureFolder(requestedName, folderSummary);
|
|
17672
|
-
return {
|
|
17673
|
-
folderId: folder.id,
|
|
17674
|
-
folderName: folder.name,
|
|
17675
|
-
createdFolder: folder.name
|
|
17676
|
-
};
|
|
17677
|
-
}
|
|
17678
|
-
function composeFolderAwareResponse(message, createdFolder) {
|
|
17679
|
-
const prefix = createdFolder ? `Created folder "${createdFolder}".
|
|
17680
|
-
` : "";
|
|
17681
|
-
return `${prefix}${message}
|
|
17682
|
-
${formatFolderStatus()}`;
|
|
17683
|
-
}
|
|
17684
|
-
function composeDuplicateBookmarkResponse(args) {
|
|
17685
|
-
return `Bookmark already exists for ${args.url} in "${args.folderName}" (id=${args.bookmarkId}). Retry with on_duplicate="update" to refresh the existing bookmark or on_duplicate="duplicate" to keep both entries.`;
|
|
17686
|
-
}
|
|
17687
|
-
async function scrollPage(wc, deltaY) {
|
|
17688
|
-
const getScrollY = () => wc.executeJavaScript(`
|
|
17689
|
-
(function() {
|
|
17690
|
-
return Math.max(
|
|
17691
|
-
window.scrollY || 0,
|
|
17692
|
-
window.pageYOffset || 0,
|
|
17693
|
-
document.scrollingElement?.scrollTop || 0,
|
|
17694
|
-
document.documentElement?.scrollTop || 0,
|
|
17695
|
-
document.body?.scrollTop || 0,
|
|
17696
|
-
);
|
|
17697
|
-
})()
|
|
17698
|
-
`);
|
|
17699
|
-
const beforeY = await getScrollY();
|
|
17700
|
-
await wc.executeJavaScript(`window.scrollBy(0, ${deltaY})`);
|
|
17701
|
-
await sleep(100);
|
|
17702
|
-
const afterY = await getScrollY();
|
|
17703
|
-
return {
|
|
17704
|
-
beforeY,
|
|
17705
|
-
afterY,
|
|
17706
|
-
movedY: Math.round(afterY - beforeY)
|
|
17707
|
-
};
|
|
17708
|
-
}
|
|
17709
|
-
async function clickElement(wc, selector) {
|
|
17710
|
-
const target = await wc.executeJavaScript(`
|
|
17711
|
-
(async function() {
|
|
17712
|
-
function matchesTarget(candidate, el) {
|
|
17713
|
-
return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
|
|
17714
|
-
}
|
|
17715
|
-
|
|
17716
|
-
function samplePoints(rect) {
|
|
17717
|
-
const width = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
17718
|
-
const height = window.innerHeight || document.documentElement?.clientHeight || 0;
|
|
17719
|
-
const insetX = Math.min(12, rect.width / 4);
|
|
17720
|
-
const insetY = Math.min(12, rect.height / 4);
|
|
17721
|
-
const raw = [
|
|
17722
|
-
[rect.left + rect.width / 2, rect.top + rect.height / 2],
|
|
17723
|
-
[rect.left + insetX, rect.top + insetY],
|
|
17724
|
-
[rect.right - insetX, rect.top + insetY],
|
|
17725
|
-
[rect.left + insetX, rect.bottom - insetY],
|
|
17726
|
-
[rect.right - insetX, rect.bottom - insetY],
|
|
17727
|
-
];
|
|
17728
|
-
return raw.map(([x, y]) => ({
|
|
17729
|
-
x: Math.min(Math.max(1, x), Math.max(1, width - 1)),
|
|
17730
|
-
y: Math.min(Math.max(1, y), Math.max(1, height - 1)),
|
|
17731
|
-
}));
|
|
17732
|
-
}
|
|
17733
|
-
|
|
17734
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17735
|
-
if (!el) return { error: "Element not found" };
|
|
17736
|
-
|
|
17737
|
-
if (el instanceof HTMLElement) {
|
|
17738
|
-
el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
17739
|
-
}
|
|
17740
|
-
|
|
17741
|
-
await new Promise((resolve) => {
|
|
17742
|
-
let settled = false;
|
|
17743
|
-
const finish = () => {
|
|
17744
|
-
if (settled) return;
|
|
17745
|
-
settled = true;
|
|
17746
|
-
resolve(undefined);
|
|
17747
|
-
};
|
|
17748
|
-
if (
|
|
17749
|
-
typeof requestAnimationFrame === "function" &&
|
|
17750
|
-
document.visibilityState === "visible"
|
|
17751
|
-
) {
|
|
17752
|
-
requestAnimationFrame(() => finish());
|
|
17753
|
-
}
|
|
17754
|
-
setTimeout(finish, 32);
|
|
17755
|
-
});
|
|
17756
|
-
|
|
17757
|
-
const rect = el.getBoundingClientRect();
|
|
17758
|
-
if (rect.width <= 0 || rect.height <= 0) {
|
|
17759
|
-
return { error: "Element is not visible. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it (scroll or scroll_to_element) then call read_page to refresh visible elements before clicking again." };
|
|
17760
|
-
}
|
|
17761
|
-
|
|
17762
|
-
const points = samplePoints(rect);
|
|
17763
|
-
const hit = points.find((point) => matchesTarget(document.elementFromPoint(point.x, point.y), el));
|
|
17764
|
-
const chosen = hit || points[0];
|
|
17765
|
-
const top = document.elementFromPoint(chosen.x, chosen.y);
|
|
17766
|
-
|
|
17767
|
-
return {
|
|
17768
|
-
x: Math.round(chosen.x),
|
|
17769
|
-
y: Math.round(chosen.y),
|
|
17770
|
-
obstructed: !matchesTarget(top, el),
|
|
17771
|
-
hiddenWindow: document.visibilityState !== "visible",
|
|
17772
|
-
};
|
|
17773
|
-
})()
|
|
17774
|
-
`);
|
|
17775
|
-
if (!target || typeof target !== "object") {
|
|
17776
|
-
return "Error: Could not resolve click target";
|
|
17777
|
-
}
|
|
17778
|
-
if ("error" in target && typeof target.error === "string") {
|
|
17779
|
-
return `Error: ${target.error}`;
|
|
17780
|
-
}
|
|
17781
|
-
const x = typeof target.x === "number" ? target.x : null;
|
|
17782
|
-
const y = typeof target.y === "number" ? target.y : null;
|
|
17783
|
-
const hiddenWindow = target.hiddenWindow === true;
|
|
17784
|
-
if (x == null || y == null) {
|
|
17785
|
-
return "Error: Could not resolve click coordinates";
|
|
17786
|
-
}
|
|
17787
|
-
if (hiddenWindow) {
|
|
17788
|
-
const activationResult = await activateElement(wc, selector);
|
|
17789
|
-
if (activationResult.startsWith("Error:")) {
|
|
17790
|
-
return activationResult;
|
|
17791
|
-
}
|
|
17792
|
-
await sleep(80);
|
|
17793
|
-
return "Clicked via DOM activation";
|
|
17794
|
-
}
|
|
17795
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
17796
|
-
await sleep(16);
|
|
17797
|
-
wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
|
|
17798
|
-
await sleep(24);
|
|
17799
|
-
wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
|
|
17800
|
-
await sleep(80);
|
|
17801
|
-
return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
|
|
17802
|
-
}
|
|
17803
|
-
async function activateElement(wc, selector) {
|
|
17804
|
-
const activated = await wc.executeJavaScript(`
|
|
17805
|
-
(function() {
|
|
17806
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17807
|
-
if (!el) return { error: "Element not found" };
|
|
17808
|
-
if (el instanceof HTMLElement) {
|
|
17809
|
-
el.focus({ preventScroll: true });
|
|
17810
|
-
}
|
|
17811
|
-
if (typeof el.click === "function") {
|
|
17812
|
-
el.click();
|
|
17813
|
-
return { ok: true };
|
|
17814
|
-
}
|
|
17815
|
-
return { error: "Element is not clickable" };
|
|
17816
|
-
})()
|
|
17817
|
-
`);
|
|
17818
|
-
if (!activated || typeof activated !== "object") {
|
|
17819
|
-
return "Error: Could not activate element";
|
|
17820
|
-
}
|
|
17821
|
-
if ("error" in activated && typeof activated.error === "string") {
|
|
17822
|
-
return `Error: ${activated.error}`;
|
|
17823
|
-
}
|
|
17824
|
-
return "Activated element via DOM click";
|
|
17825
|
-
}
|
|
17826
|
-
async function describeElementForClick(wc, selector) {
|
|
17827
|
-
const result = await wc.executeJavaScript(`
|
|
17828
|
-
(function() {
|
|
17829
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
17830
|
-
if (!el) return { error: "Element not found" };
|
|
17831
|
-
const anchor = el instanceof HTMLAnchorElement ? el : el.closest("a[href]");
|
|
17832
|
-
const text = (el.textContent || el.tagName || "Element").trim().slice(0, 100);
|
|
17833
|
-
return {
|
|
17834
|
-
text: text || "Element",
|
|
17835
|
-
href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
|
|
17836
|
-
};
|
|
17837
|
-
})()
|
|
17838
|
-
`);
|
|
17839
|
-
if (!result || typeof result !== "object") {
|
|
17840
|
-
return { error: "Element not found" };
|
|
17841
|
-
}
|
|
17842
|
-
if ("error" in result && typeof result.error === "string") {
|
|
17843
|
-
return { error: result.error };
|
|
17844
|
-
}
|
|
17845
|
-
return {
|
|
17846
|
-
text: "text" in result && typeof result.text === "string" ? result.text : "Element",
|
|
17847
|
-
href: "href" in result && typeof result.href === "string" ? result.href : void 0
|
|
17848
|
-
};
|
|
17849
|
-
}
|
|
17850
|
-
async function clickResolvedSelector(wc, selector) {
|
|
17851
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
17852
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
17853
|
-
const beforeUrl2 = wc.getURL();
|
|
17854
|
-
const idxLabel = await wc.executeJavaScript(
|
|
17855
|
-
`window.__vessel?.getElementText?.(${idx}) || ""`
|
|
17856
|
-
);
|
|
17857
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
|
|
17858
|
-
return `Blocked: "${idxLabel}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17859
|
-
}
|
|
17860
|
-
const result = await wc.executeJavaScript(
|
|
17861
|
-
`window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
|
|
17862
|
-
);
|
|
17863
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17864
|
-
if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17865
|
-
recordCartClick(beforeUrl2, idxLabel);
|
|
17866
|
-
}
|
|
17867
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17868
|
-
const afterUrl2 = wc.getURL();
|
|
17869
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17870
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17871
|
-
if (!overlayHint2 && typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
|
|
17872
|
-
await sleep(1200);
|
|
17873
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17874
|
-
}
|
|
17875
|
-
if (!overlayHint2) {
|
|
17876
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17877
|
-
if (hrefMatch) {
|
|
17878
|
-
try {
|
|
17879
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17880
|
-
await waitForLoad(wc, 8e3);
|
|
17881
|
-
const hrefUrl = wc.getURL();
|
|
17882
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17883
|
-
} catch {
|
|
17884
|
-
}
|
|
17885
|
-
}
|
|
17886
|
-
return result;
|
|
17887
|
-
}
|
|
17888
|
-
const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
|
|
17889
|
-
const actionsSuffix = dialogActions ? `
|
|
17890
|
-
${dialogActions}
|
|
17891
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17892
|
-
return `${result}
|
|
17893
|
-
${overlayHint2}${actionsSuffix}`;
|
|
17894
|
-
}
|
|
17895
|
-
if (selector.includes(" >>> ")) {
|
|
17896
|
-
const beforeUrl2 = wc.getURL();
|
|
17897
|
-
const shadowLabel = await wc.executeJavaScript(`
|
|
17898
|
-
(function() {
|
|
17899
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17900
|
-
return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
|
|
17901
|
-
})()
|
|
17902
|
-
`);
|
|
17903
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
|
|
17904
|
-
return `Blocked: "${shadowLabel}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17905
|
-
}
|
|
17906
|
-
const result = await wc.executeJavaScript(`
|
|
17907
|
-
(function() {
|
|
17908
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
17909
|
-
if (!el || !document.contains(el)) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
17910
|
-
if (el instanceof HTMLElement) { el.focus(); el.click(); }
|
|
17911
|
-
var anchor = el instanceof HTMLAnchorElement ? el : el.closest('a[href]');
|
|
17912
|
-
var href = anchor instanceof HTMLAnchorElement ? anchor.href : null;
|
|
17913
|
-
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase()) + (href ? "\\nhref: " + href : "");
|
|
17914
|
-
})()
|
|
17915
|
-
`);
|
|
17916
|
-
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
17917
|
-
if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17918
|
-
recordCartClick(beforeUrl2, shadowLabel);
|
|
17919
|
-
}
|
|
17920
|
-
await waitForPotentialNavigation$1(wc, beforeUrl2);
|
|
17921
|
-
const afterUrl2 = wc.getURL();
|
|
17922
|
-
if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
|
|
17923
|
-
let overlayHint2 = await detectPostClickOverlay(wc);
|
|
17924
|
-
if (!overlayHint2 && typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
|
|
17925
|
-
await sleep(1200);
|
|
17926
|
-
overlayHint2 = await detectPostClickOverlay(wc);
|
|
17927
|
-
}
|
|
17928
|
-
if (!overlayHint2) {
|
|
17929
|
-
const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
|
|
17930
|
-
if (hrefMatch) {
|
|
17931
|
-
try {
|
|
17932
|
-
await loadPermittedUrl(wc, hrefMatch[1]);
|
|
17933
|
-
await waitForLoad(wc, 8e3);
|
|
17934
|
-
const hrefUrl = wc.getURL();
|
|
17935
|
-
if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
|
|
17936
|
-
} catch {
|
|
17937
|
-
}
|
|
17938
|
-
}
|
|
17939
|
-
return result;
|
|
17940
|
-
}
|
|
17941
|
-
const dialogActions2 = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
|
|
17942
|
-
const actionsSuffix2 = dialogActions2 ? `
|
|
17943
|
-
${dialogActions2}
|
|
17944
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17945
|
-
return `${result}
|
|
17946
|
-
${overlayHint2}${actionsSuffix2}`;
|
|
17947
|
-
}
|
|
17948
|
-
const beforeUrl = wc.getURL();
|
|
17949
|
-
const elInfo = await describeElementForClick(wc, selector);
|
|
17950
|
-
if ("error" in elInfo) return `Error: ${elInfo.error}`;
|
|
17951
|
-
const cartMatch = isAddToCartText(elInfo.text);
|
|
17952
|
-
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
17953
|
-
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
17954
|
-
}
|
|
17955
|
-
if (!cartMatch) {
|
|
17956
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17957
|
-
if (dialogActions) {
|
|
17958
|
-
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
17959
|
-
${dialogActions}
|
|
17960
|
-
Click one of these dialog actions instead.`;
|
|
17961
|
-
}
|
|
17962
|
-
}
|
|
17963
|
-
if (elInfo.href) {
|
|
17964
|
-
const validation = await validateLinkDestination(elInfo.href);
|
|
17965
|
-
if (validation.status === "dead") {
|
|
17966
|
-
return formatDeadLinkMessage(elInfo.text, validation);
|
|
17967
|
-
}
|
|
17968
|
-
}
|
|
17969
|
-
if (cartMatch) {
|
|
17970
|
-
recordCartClick(beforeUrl, elInfo.text);
|
|
17971
|
-
}
|
|
17972
|
-
const clickText = `Clicked: ${elInfo.text}`;
|
|
17973
|
-
const clickResult = await clickElement(wc, selector);
|
|
17974
|
-
if (clickResult.startsWith("Error:")) return clickResult;
|
|
17975
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
17976
|
-
const afterUrl = wc.getURL();
|
|
17977
|
-
if (afterUrl !== beforeUrl) {
|
|
17978
|
-
return `${clickText} -> ${afterUrl}`;
|
|
17979
|
-
}
|
|
17980
|
-
const overlayHint = await detectPostClickOverlay(wc);
|
|
17981
|
-
if (overlayHint) {
|
|
17982
|
-
const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
|
|
17983
|
-
const actionsSuffix = dialogActions ? `
|
|
17984
|
-
${dialogActions}
|
|
17985
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17986
|
-
return `${clickText} (${clickResult})
|
|
17987
|
-
${overlayHint}${actionsSuffix}`;
|
|
17988
|
-
}
|
|
17989
|
-
if (cartMatch) {
|
|
17990
|
-
await sleep(1200);
|
|
17991
|
-
const delayedOverlayHint = await detectPostClickOverlay(wc);
|
|
17992
|
-
if (delayedOverlayHint) {
|
|
17993
|
-
const dialogActions = await getCartDialogActions(wc);
|
|
17994
|
-
const actionsSuffix = dialogActions ? `
|
|
17995
|
-
${dialogActions}
|
|
17996
|
-
Click one of these dialog actions. Do NOT click any other element.` : "";
|
|
17997
|
-
return `${clickText} (${clickResult})
|
|
17998
|
-
${delayedOverlayHint}${actionsSuffix}`;
|
|
17999
|
-
}
|
|
18000
|
-
return `${clickText} (${clickResult})`;
|
|
18001
|
-
}
|
|
18002
|
-
const activationResult = await activateElement(wc, selector);
|
|
18003
|
-
if (!activationResult.startsWith("Error:")) {
|
|
18004
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18005
|
-
const fallbackUrl = wc.getURL();
|
|
18006
|
-
if (fallbackUrl !== beforeUrl) {
|
|
18007
|
-
return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
|
|
18008
|
-
}
|
|
18009
|
-
}
|
|
18010
|
-
const postActivationOverlayHint = await detectPostClickOverlay(wc);
|
|
18011
|
-
if (postActivationOverlayHint) {
|
|
18012
|
-
return `${clickText} (${clickResult})
|
|
18013
|
-
${postActivationOverlayHint}`;
|
|
18014
|
-
}
|
|
18015
|
-
return `${clickText} (${clickResult})`;
|
|
18016
|
-
}
|
|
18017
|
-
async function getCartDialogActions(wc) {
|
|
18018
|
-
const result = await wc.executeJavaScript(`
|
|
18019
|
-
(function() {
|
|
18020
|
-
function isVisible(el) {
|
|
18021
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
18022
|
-
const style = getComputedStyle(el);
|
|
18023
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
18024
|
-
const rect = el.getBoundingClientRect();
|
|
18025
|
-
return rect.width >= 20 && rect.height >= 10;
|
|
18026
|
-
}
|
|
18027
|
-
|
|
18028
|
-
function findDialogRoot() {
|
|
18029
|
-
const selectors = [
|
|
18030
|
-
'[data-test="basket-flyout"]',
|
|
18031
|
-
'[role="dialog"]',
|
|
18032
|
-
'dialog[open]',
|
|
18033
|
-
'[role="alertdialog"]',
|
|
18034
|
-
'[aria-modal="true"]',
|
|
18035
|
-
];
|
|
18036
|
-
for (const selector of selectors) {
|
|
18037
|
-
const nodes = document.querySelectorAll(selector);
|
|
18038
|
-
for (const node of nodes) {
|
|
18039
|
-
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
|
|
18040
|
-
const text = (node.textContent || "").slice(0, 800).toLowerCase();
|
|
18041
|
-
const cartSignals = [
|
|
18042
|
-
"added to cart", "added to bag", "added to basket",
|
|
18043
|
-
"item added", "your basket", "your cart", "your bag",
|
|
18044
|
-
"view basket", "view cart", "continue shopping",
|
|
18045
|
-
];
|
|
18046
|
-
if (cartSignals.some((signal) => text.includes(signal))) {
|
|
18047
|
-
return node;
|
|
18048
|
-
}
|
|
18049
|
-
}
|
|
18050
|
-
}
|
|
18051
|
-
return null;
|
|
18052
|
-
}
|
|
18053
|
-
|
|
18054
|
-
const dialog = findDialogRoot();
|
|
18055
|
-
if (!dialog) return { found: false, actions: [] };
|
|
18056
|
-
|
|
18057
|
-
const actions = [];
|
|
18058
|
-
dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
|
|
18059
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18060
|
-
const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
|
|
18061
|
-
if (!label || label.length < 2) return;
|
|
18062
|
-
const href = el.getAttribute("href") || "";
|
|
18063
|
-
const selector = el.id ? "#" + el.id
|
|
18064
|
-
: el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
|
|
18065
|
-
: el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
|
|
18066
|
-
: null;
|
|
18067
|
-
if (selector) {
|
|
18068
|
-
actions.push({ label: label, href: href, selector: selector });
|
|
18069
|
-
}
|
|
18070
|
-
});
|
|
18071
|
-
|
|
18072
|
-
return {
|
|
18073
|
-
found: true,
|
|
18074
|
-
actions: actions.map((action) =>
|
|
18075
|
-
'- "' + action.label + '"' +
|
|
18076
|
-
(action.href ? ' -> ' + action.href : "") +
|
|
18077
|
-
(action.selector ? ' (selector: ' + action.selector + ')' : "")
|
|
18078
|
-
),
|
|
18079
|
-
};
|
|
18080
|
-
})()
|
|
18081
|
-
`);
|
|
18082
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18083
|
-
return null;
|
|
18084
|
-
}
|
|
18085
|
-
if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
|
|
18086
|
-
return null;
|
|
18087
|
-
}
|
|
18088
|
-
return `Available dialog actions:
|
|
18089
|
-
${result.actions.join("\n")}`;
|
|
18090
|
-
}
|
|
18091
|
-
async function detectPostClickOverlay(wc) {
|
|
18092
|
-
const result = await wc.executeJavaScript(`
|
|
18093
|
-
(function() {
|
|
18094
|
-
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
18095
|
-
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
18096
|
-
var vpArea = Math.max(1, vw * vh);
|
|
18097
|
-
|
|
18098
|
-
function isVisible(el) {
|
|
18099
|
-
if (!(el instanceof HTMLElement)) return false;
|
|
18100
|
-
var style = getComputedStyle(el);
|
|
18101
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
18102
|
-
return el.getBoundingClientRect().width > 0;
|
|
18103
|
-
}
|
|
18104
|
-
|
|
18105
|
-
function hasFixedAncestor(el) {
|
|
18106
|
-
var current = el.parentElement;
|
|
18107
|
-
while (current && current !== document.body) {
|
|
18108
|
-
var position = getComputedStyle(current).position;
|
|
18109
|
-
if (position === "fixed" || position === "sticky") return true;
|
|
18110
|
-
current = current.parentElement;
|
|
18111
|
-
}
|
|
18112
|
-
return false;
|
|
18113
|
-
}
|
|
18114
|
-
|
|
18115
|
-
function effectiveZ(el) {
|
|
18116
|
-
var current = el;
|
|
18117
|
-
while (current && current !== document.body) {
|
|
18118
|
-
var z = parseInt(getComputedStyle(current).zIndex, 10);
|
|
18119
|
-
if (z > 0) return z;
|
|
18120
|
-
current = current.parentElement;
|
|
18121
|
-
}
|
|
18122
|
-
return 0;
|
|
18123
|
-
}
|
|
18124
|
-
|
|
18125
|
-
function touchesViewportEdge(rect) {
|
|
18126
|
-
return rect.left <= 24 || rect.top <= 24 ||
|
|
18127
|
-
rect.right >= vw - 24 || rect.bottom >= vh - 24;
|
|
18128
|
-
}
|
|
18129
|
-
|
|
18130
|
-
var cartPhrases = [
|
|
18131
|
-
"added to cart", "added to bag", "added to basket",
|
|
18132
|
-
"added to your cart", "added to your bag", "added to your basket",
|
|
18133
|
-
];
|
|
18134
|
-
var cartActions = [
|
|
18135
|
-
"view cart", "go to cart", "view basket", "go to basket",
|
|
18136
|
-
"continue shopping", "keep shopping", "checkout",
|
|
18137
|
-
];
|
|
18138
|
-
|
|
18139
|
-
var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
|
|
18140
|
-
var candidates = document.querySelectorAll(selectors);
|
|
18141
|
-
var hit = null;
|
|
18142
|
-
for (var i = 0; i < candidates.length; i++) {
|
|
18143
|
-
if (isVisible(candidates[i])) {
|
|
18144
|
-
hit = candidates[i];
|
|
18145
|
-
break;
|
|
18146
|
-
}
|
|
18147
|
-
}
|
|
18148
|
-
|
|
18149
|
-
if (!hit) {
|
|
18150
|
-
var elements = document.querySelectorAll("*");
|
|
18151
|
-
for (var j = 0; j < elements.length; j++) {
|
|
18152
|
-
var el = elements[j];
|
|
18153
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
|
|
18154
|
-
var style = getComputedStyle(el);
|
|
18155
|
-
var position = style.position;
|
|
18156
|
-
var isFixed = position === "fixed" || position === "sticky";
|
|
18157
|
-
var isAbsolute = position === "absolute";
|
|
18158
|
-
if (!isFixed && !isAbsolute) continue;
|
|
18159
|
-
if (isAbsolute && !hasFixedAncestor(el)) continue;
|
|
18160
|
-
if (effectiveZ(el) < 5) continue;
|
|
18161
|
-
var rect = el.getBoundingClientRect();
|
|
18162
|
-
var areaRatio = (rect.width * rect.height) / vpArea;
|
|
18163
|
-
if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
|
|
18164
|
-
hit = el;
|
|
18165
|
-
break;
|
|
18166
|
-
}
|
|
18167
|
-
}
|
|
18168
|
-
}
|
|
18169
|
-
|
|
18170
|
-
if (!hit) return { found: false, label: "", cartLike: false };
|
|
18171
|
-
var text = (hit.textContent || "").slice(0, 800).toLowerCase();
|
|
18172
|
-
var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
|
|
18173
|
-
return text.indexOf(signal) !== -1;
|
|
18174
|
-
});
|
|
18175
|
-
var heading = hit.querySelector("h1,h2,h3,h4");
|
|
18176
|
-
var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
|
|
18177
|
-
return { found: true, label: label, cartLike: cartLike };
|
|
18178
|
-
})()
|
|
18179
|
-
`);
|
|
18180
|
-
if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
|
|
18181
|
-
return null;
|
|
18182
|
-
}
|
|
18183
|
-
const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
|
|
18184
|
-
if ("cartLike" in result && result.cartLike) {
|
|
18185
|
-
return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
|
|
18186
|
-
}
|
|
18187
|
-
return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
|
|
18188
|
-
}
|
|
18189
|
-
async function dismissPopup(wc) {
|
|
18190
|
-
const before = await extractContent(wc);
|
|
18191
|
-
const initialBlocking = before.overlays.filter(
|
|
18192
|
-
(overlay) => overlay.blocksInteraction
|
|
18193
|
-
).length;
|
|
18194
|
-
if (initialBlocking > 0) {
|
|
18195
|
-
const overlayText = before.overlays.map(
|
|
18196
|
-
(o) => [o.label, o.text].filter(Boolean).join(" ")
|
|
18197
|
-
).join(" ").toLowerCase();
|
|
18198
|
-
const cartSignals = [
|
|
18199
|
-
"added to cart",
|
|
18200
|
-
"added to bag",
|
|
18201
|
-
"added to basket",
|
|
18202
|
-
"item added",
|
|
18203
|
-
"items in your basket",
|
|
18204
|
-
"items in your cart",
|
|
18205
|
-
"items in your bag",
|
|
18206
|
-
"your basket",
|
|
18207
|
-
"your cart",
|
|
18208
|
-
"your bag",
|
|
18209
|
-
"view basket",
|
|
18210
|
-
"view cart",
|
|
18211
|
-
"continue shopping"
|
|
18212
|
-
];
|
|
18213
|
-
if (cartSignals.some((s) => overlayText.includes(s))) {
|
|
18214
|
-
const continueResult = await wc.executeJavaScript(`
|
|
18215
|
-
(function() {
|
|
18216
|
-
var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
|
|
18217
|
-
if (!dialog) return "Error: dialog not found";
|
|
18218
|
-
var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
|
|
18219
|
-
var continueBtn = null;
|
|
18220
|
-
var viewCartBtn = null;
|
|
18221
|
-
for (var i = 0; i < buttons.length; i++) {
|
|
18222
|
-
var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
|
|
18223
|
-
if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
|
|
18224
|
-
if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
|
|
18225
|
-
}
|
|
18226
|
-
var target = continueBtn || viewCartBtn;
|
|
18227
|
-
if (!target) return "Error: no dialog action found";
|
|
18228
|
-
var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
|
|
18229
|
-
if (target.tagName === 'A' && target.href) {
|
|
18230
|
-
window.location.href = target.href;
|
|
18231
|
-
return "Clicked: " + actionLabel + " -> " + target.href;
|
|
18232
|
-
}
|
|
18233
|
-
target.click();
|
|
18234
|
-
return "Clicked: " + actionLabel;
|
|
18235
|
-
})()
|
|
18236
|
-
`);
|
|
18237
|
-
if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
|
|
18238
|
-
return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
|
|
18239
|
-
}
|
|
18240
|
-
return "Cannot dismiss: this is a cart confirmation dialog. Item is in your cart. Use read_page to see dialog actions (e.g. View Basket, Continue Shopping) and click one of them instead.";
|
|
18241
|
-
}
|
|
18242
|
-
}
|
|
18243
|
-
const initialDormant = before.dormantOverlays.length;
|
|
18244
|
-
const candidates = await wc.executeJavaScript(`
|
|
18245
|
-
(function() {
|
|
18246
|
-
function text(value) {
|
|
18247
|
-
const trimmed = value == null ? "" : String(value).trim();
|
|
18248
|
-
return trimmed || "";
|
|
18249
|
-
}
|
|
18250
|
-
|
|
18251
|
-
${selectorHelpersJS(["data-testid", "data-test", "aria-label", "name", "title"])}
|
|
18252
|
-
|
|
18253
|
-
function isVisible(el) {
|
|
18254
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
18255
|
-
const style = window.getComputedStyle(el);
|
|
18256
|
-
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
18257
|
-
return false;
|
|
18258
|
-
}
|
|
18259
|
-
if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
|
|
18260
|
-
return false;
|
|
18261
|
-
}
|
|
18262
|
-
const rect = el.getBoundingClientRect();
|
|
18263
|
-
return rect.width > 0 && rect.height > 0;
|
|
18264
|
-
}
|
|
18265
|
-
|
|
18266
|
-
function overlayRoots() {
|
|
18267
|
-
const nodes = [];
|
|
18268
|
-
document.querySelectorAll("dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']").forEach((el) => {
|
|
18269
|
-
if (isVisible(el)) nodes.push(el);
|
|
18270
|
-
});
|
|
18271
|
-
// Detect known consent manager containers
|
|
18272
|
-
document.querySelectorAll("#onetrust-consent-sdk, #onetrust-banner-sdk, [id*='onetrust'], [class*='onetrust'], #CybotCookiebotDialog, #truste-consent-track, [id*='cookie-banner'], [id*='consent-banner'], [class*='cookie-consent'], [class*='consent-banner'], [id*='gdpr'], [class*='gdpr']").forEach((el) => {
|
|
18273
|
-
if (el instanceof HTMLElement && isVisible(el)) nodes.push(el);
|
|
18274
|
-
});
|
|
18275
|
-
document.querySelectorAll("body *").forEach((el) => {
|
|
18276
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18277
|
-
const style = window.getComputedStyle(el);
|
|
18278
|
-
const rect = el.getBoundingClientRect();
|
|
18279
|
-
const zIndex = Number.parseInt(style.zIndex, 10);
|
|
18280
|
-
const coversCenter =
|
|
18281
|
-
rect.left <= (window.innerWidth || 0) / 2 &&
|
|
18282
|
-
rect.right >= (window.innerWidth || 0) / 2 &&
|
|
18283
|
-
rect.top <= (window.innerHeight || 0) / 2 &&
|
|
18284
|
-
rect.bottom >= (window.innerHeight || 0) / 2;
|
|
18285
|
-
if (
|
|
18286
|
-
(style.position === "fixed" || style.position === "sticky") &&
|
|
18287
|
-
Number.isFinite(zIndex) &&
|
|
18288
|
-
zIndex >= 10 &&
|
|
18289
|
-
coversCenter
|
|
18290
|
-
) {
|
|
18291
|
-
nodes.push(el);
|
|
18292
|
-
}
|
|
18293
|
-
});
|
|
18294
|
-
return Array.from(new Set(nodes));
|
|
18295
|
-
}
|
|
18296
|
-
|
|
18297
|
-
function scoreCandidate(el, rooted) {
|
|
18298
|
-
const label = text(
|
|
18299
|
-
el.getAttribute("aria-label") ||
|
|
18300
|
-
el.getAttribute("title") ||
|
|
18301
|
-
el.textContent ||
|
|
18302
|
-
el.getAttribute("value"),
|
|
18303
|
-
).toLowerCase();
|
|
18304
|
-
const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18305
|
-
const idText = text(el.id).toLowerCase();
|
|
18306
|
-
const combined = classText + " " + idText;
|
|
18307
|
-
let score = rooted ? 30 : 0;
|
|
18308
|
-
if (/^x$|^×$/.test(label)) score += 120;
|
|
18309
|
-
if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
|
|
18310
|
-
if (/close|dismiss|modal-close|overlay-close/.test(combined)) score += 90;
|
|
18311
|
-
if (/onetrust-close|onetrust-reject|cookie.*close|consent.*close|cookie.*reject|consent.*reject/.test(combined)) score += 110;
|
|
18312
|
-
if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
|
|
18313
|
-
if (el.getAttribute("aria-label")) score += 20;
|
|
18314
|
-
if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
|
|
18315
|
-
const rect = el.getBoundingClientRect();
|
|
18316
|
-
if (rect.top < 120) score += 10;
|
|
18317
|
-
if (rect.right > (window.innerWidth || 0) - 120) score += 15;
|
|
18318
|
-
return score;
|
|
18319
|
-
}
|
|
18320
|
-
|
|
18321
|
-
const selector = "button, [role='button'], a[href], input[type='button'], input[type='submit'], [aria-label], [title]";
|
|
18322
|
-
const results = [];
|
|
18323
|
-
const roots = overlayRoots();
|
|
18324
|
-
|
|
18325
|
-
function collect(container, rooted) {
|
|
18326
|
-
container.querySelectorAll(selector).forEach((el) => {
|
|
18327
|
-
if (!(el instanceof HTMLElement) || !isVisible(el)) return;
|
|
18328
|
-
const candidateSelector = selectorFor(el);
|
|
18329
|
-
if (!candidateSelector) return;
|
|
18330
|
-
var label = text(
|
|
18331
|
-
el.getAttribute("aria-label") ||
|
|
18332
|
-
el.getAttribute("title") ||
|
|
18333
|
-
el.textContent ||
|
|
18334
|
-
el.getAttribute("value"),
|
|
18335
|
-
);
|
|
18336
|
-
if (!label) {
|
|
18337
|
-
var idLower = (el.id || "").toLowerCase();
|
|
18338
|
-
var classLower = (typeof el.className === "string" ? el.className : "").toLowerCase();
|
|
18339
|
-
var combined = idLower + " " + classLower;
|
|
18340
|
-
if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(combined)) {
|
|
18341
|
-
label = idLower.includes("accept") ? "Accept cookies"
|
|
18342
|
-
: idLower.includes("reject") ? "Reject cookies"
|
|
18343
|
-
: idLower.includes("close") || classLower.includes("close") ? "Close"
|
|
18344
|
-
: "Consent button";
|
|
18345
|
-
} else {
|
|
18346
|
-
return;
|
|
18347
|
-
}
|
|
18348
|
-
}
|
|
18349
|
-
results.push({
|
|
18350
|
-
selector: candidateSelector,
|
|
18351
|
-
label: label.slice(0, 120),
|
|
18352
|
-
score: scoreCandidate(el, rooted),
|
|
18353
|
-
});
|
|
18354
|
-
});
|
|
18355
|
-
}
|
|
18356
|
-
|
|
18357
|
-
roots.forEach((root) => collect(root, true));
|
|
18358
|
-
if (results.length === 0) {
|
|
18359
|
-
collect(document, false);
|
|
18360
|
-
}
|
|
18361
|
-
|
|
18362
|
-
const seen = new Set();
|
|
18363
|
-
return results
|
|
18364
|
-
.filter((candidate) => {
|
|
18365
|
-
if (seen.has(candidate.selector)) return false;
|
|
18366
|
-
seen.add(candidate.selector);
|
|
18367
|
-
return candidate.score > 0;
|
|
18368
|
-
})
|
|
18369
|
-
.sort((a, b) => b.score - a.score)
|
|
18370
|
-
.slice(0, 8);
|
|
18371
|
-
})()
|
|
18372
|
-
`);
|
|
18373
|
-
if (Array.isArray(candidates)) {
|
|
18374
|
-
for (const candidate of candidates) {
|
|
18375
|
-
if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
|
|
18376
|
-
continue;
|
|
18377
|
-
}
|
|
18378
|
-
const result = await clickElement(wc, candidate.selector);
|
|
18379
|
-
if (result.startsWith("Error:")) continue;
|
|
18380
|
-
await sleep(250);
|
|
18381
|
-
const after = await extractContent(wc);
|
|
18382
|
-
const blocking = after.overlays.filter(
|
|
18383
|
-
(overlay) => overlay.blocksInteraction
|
|
18384
|
-
).length;
|
|
18385
|
-
if (blocking < initialBlocking || initialBlocking > 0 && blocking === 0) {
|
|
18386
|
-
const label = typeof candidate.label === "string" && candidate.label ? candidate.label : "popup control";
|
|
18387
|
-
return `Dismissed popup using "${label}"`;
|
|
18388
|
-
}
|
|
18389
|
-
}
|
|
18390
|
-
}
|
|
18391
|
-
wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
|
|
18392
|
-
await sleep(16);
|
|
18393
|
-
wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
|
|
18394
|
-
await sleep(200);
|
|
18395
|
-
const afterEscape = await extractContent(wc);
|
|
18396
|
-
const escapeBlocking = afterEscape.overlays.filter(
|
|
18397
|
-
(overlay) => overlay.blocksInteraction
|
|
18398
|
-
).length;
|
|
18399
|
-
if (escapeBlocking < initialBlocking || initialBlocking > 0 && escapeBlocking === 0) {
|
|
18400
|
-
return "Dismissed popup with Escape";
|
|
18401
|
-
}
|
|
18402
|
-
return initialBlocking > 0 ? "Could not dismiss the blocking popup automatically" : initialDormant > 0 ? `No active blocking popup detected. Found ${initialDormant} dormant consent/modal surface(s) in the DOM, likely geo-gated or inactive in this session.` : "No blocking popup detected";
|
|
18403
|
-
}
|
|
18404
|
-
function isDangerousAction(name) {
|
|
18405
|
-
return [
|
|
18406
|
-
"navigate",
|
|
18407
|
-
"click",
|
|
18408
|
-
"type",
|
|
18409
|
-
"select_option",
|
|
18410
|
-
"submit_form",
|
|
18411
|
-
"press_key",
|
|
18412
|
-
"create_tab",
|
|
18413
|
-
"switch_tab",
|
|
18414
|
-
"close_tab",
|
|
18415
|
-
"restore_checkpoint",
|
|
18416
|
-
"login",
|
|
18417
|
-
"fill_form",
|
|
18418
|
-
"search",
|
|
18419
|
-
"paginate"
|
|
18420
|
-
].includes(name);
|
|
18421
|
-
}
|
|
18422
|
-
function getTabByMatch(tabManager, match) {
|
|
18423
|
-
const lowered = match.toLowerCase();
|
|
18424
|
-
return tabManager.getAllStates().find(
|
|
18425
|
-
(tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
|
|
18426
|
-
) || null;
|
|
18427
|
-
}
|
|
18428
|
-
async function getPostActionState(tabManager, name) {
|
|
18429
|
-
const tab = tabManager.getActiveTab();
|
|
18430
|
-
if (!tab) return "";
|
|
18431
|
-
const wc = tab.view.webContents;
|
|
18432
|
-
const navActions = [
|
|
18433
|
-
"navigate",
|
|
18434
|
-
"go_back",
|
|
18435
|
-
"go_forward",
|
|
18436
|
-
"click",
|
|
18437
|
-
"submit_form",
|
|
18438
|
-
"reload",
|
|
18439
|
-
"press_key"
|
|
18440
|
-
];
|
|
18441
|
-
const interactActions = [
|
|
18442
|
-
"type",
|
|
18443
|
-
"type_text",
|
|
18444
|
-
"select_option",
|
|
18445
|
-
"hover",
|
|
18446
|
-
"focus"
|
|
18447
|
-
];
|
|
18448
|
-
const tabActions = ["create_tab", "switch_tab", "close_tab"];
|
|
18449
|
-
if (navActions.includes(name)) {
|
|
18450
|
-
let warning = "";
|
|
18451
|
-
try {
|
|
18452
|
-
const page = await extractContent(wc);
|
|
18453
|
-
const issue = getRecoverableAccessIssue(page);
|
|
18454
|
-
if (issue) {
|
|
18455
|
-
const blockedUrl = wc.getURL();
|
|
18456
|
-
const canRecover = [
|
|
18457
|
-
"navigate",
|
|
18458
|
-
"open_bookmark",
|
|
18459
|
-
"click",
|
|
18460
|
-
"submit_form",
|
|
18461
|
-
"reload",
|
|
18462
|
-
"press_key"
|
|
18463
|
-
].includes(name) && tab.canGoBack();
|
|
18464
|
-
if (canRecover && tab.goBack()) {
|
|
18465
|
-
await waitForLoad(wc);
|
|
18466
|
-
warning = `
|
|
18467
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
|
|
18468
|
-
} else {
|
|
18469
|
-
warning = `
|
|
18470
|
-
[warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
|
|
18471
|
-
}
|
|
18472
|
-
}
|
|
18473
|
-
} catch {
|
|
18474
|
-
}
|
|
18475
|
-
return `${warning}
|
|
18476
|
-
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
18477
|
-
}
|
|
18478
|
-
if (interactActions.includes(name)) {
|
|
18479
|
-
return `
|
|
18480
|
-
[state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
|
|
18481
|
-
}
|
|
18482
|
-
if (tabActions.includes(name)) {
|
|
18483
|
-
const activeId = tabManager.getActiveTabId();
|
|
18484
|
-
const active = getActiveTabSummary(tabManager);
|
|
18485
|
-
const count = tabManager.getAllStates().length;
|
|
18486
|
-
return `
|
|
18487
|
-
[state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
|
|
18488
|
-
}
|
|
18489
|
-
return "";
|
|
18490
|
-
}
|
|
18491
|
-
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
18492
|
-
try {
|
|
18493
|
-
const result = await runtime2.runControlledAction({
|
|
18494
|
-
source: "mcp",
|
|
18495
|
-
name,
|
|
18496
|
-
args,
|
|
18497
|
-
tabId: tabManager.getActiveTabId(),
|
|
18498
|
-
dangerous: isDangerousAction(name),
|
|
18499
|
-
executor
|
|
18500
|
-
});
|
|
18501
|
-
const stateInfo = await getPostActionState(tabManager, name);
|
|
18502
|
-
const flowCtx = runtime2.getFlowContext();
|
|
18503
|
-
return asTextResponse(result + stateInfo + flowCtx);
|
|
18504
|
-
} catch (error) {
|
|
18505
|
-
return asTextResponse(
|
|
18506
|
-
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
18507
|
-
);
|
|
18508
|
-
}
|
|
18509
|
-
}
|
|
18510
|
-
async function setElementValue(wc, selector, value) {
|
|
18511
|
-
if (selector.startsWith("__vessel_idx:")) {
|
|
18512
|
-
const idx = Number(selector.slice("__vessel_idx:".length));
|
|
18513
|
-
return wc.executeJavaScript(
|
|
18514
|
-
`window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
|
|
18515
|
-
);
|
|
18516
|
-
}
|
|
18517
|
-
if (selector.includes(" >>> ")) {
|
|
18518
|
-
return wc.executeJavaScript(`
|
|
18519
|
-
(function() {
|
|
18520
|
-
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
18521
|
-
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
18522
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
|
|
18523
|
-
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18524
|
-
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
18525
|
-
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
18526
|
-
el.focus();
|
|
18527
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
18528
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
18529
|
-
return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
|
|
18530
|
-
})()
|
|
18531
|
-
`);
|
|
18532
|
-
}
|
|
18533
|
-
return wc.executeJavaScript(`
|
|
18534
|
-
(function() {
|
|
18535
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18536
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18537
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18538
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18539
|
-
}
|
|
18540
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18541
|
-
return 'Error[disabled]: Input is disabled';
|
|
18542
|
-
}
|
|
18543
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18544
|
-
? HTMLTextAreaElement.prototype
|
|
18545
|
-
: HTMLInputElement.prototype;
|
|
18546
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18547
|
-
if (descriptor && descriptor.set) {
|
|
18548
|
-
descriptor.set.call(el, ${JSON.stringify(value)});
|
|
18549
|
-
} else {
|
|
18550
|
-
el.value = ${JSON.stringify(value)};
|
|
18551
|
-
}
|
|
18552
|
-
el.focus();
|
|
18553
|
-
el.dispatchEvent(new InputEvent('input', {
|
|
18554
|
-
bubbles: true,
|
|
18555
|
-
cancelable: true,
|
|
18556
|
-
data: ${JSON.stringify(value)},
|
|
18557
|
-
inputType: 'insertText',
|
|
18558
|
-
}));
|
|
18559
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18560
|
-
return 'Typed into: ' +
|
|
18561
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18562
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18563
|
-
})()
|
|
18564
|
-
`);
|
|
18565
|
-
}
|
|
18566
|
-
async function typeKeystroke(wc, selector, value) {
|
|
18567
|
-
return wc.executeJavaScript(`
|
|
18568
|
-
(async function() {
|
|
18569
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18570
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18571
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
18572
|
-
return 'Error[not-input]: Element is not a text input';
|
|
18573
|
-
}
|
|
18574
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18575
|
-
return 'Error[disabled]: Input is disabled';
|
|
18576
|
-
}
|
|
18577
|
-
el.focus();
|
|
18578
|
-
const prototype = el instanceof HTMLTextAreaElement
|
|
18579
|
-
? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
18580
|
-
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
18581
|
-
if (descriptor && descriptor.set) {
|
|
18582
|
-
descriptor.set.call(el, '');
|
|
18583
|
-
} else {
|
|
18584
|
-
el.value = '';
|
|
18585
|
-
}
|
|
18586
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: '', inputType: 'deleteContentBackward' }));
|
|
18587
|
-
const chars = ${JSON.stringify(value)}.split('');
|
|
18588
|
-
for (const ch of chars) {
|
|
18589
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true, cancelable: true }));
|
|
18590
|
-
el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true, cancelable: true }));
|
|
18591
|
-
if (descriptor && descriptor.set) {
|
|
18592
|
-
descriptor.set.call(el, el.value + ch);
|
|
18593
|
-
} else {
|
|
18594
|
-
el.value += ch;
|
|
18595
|
-
}
|
|
18596
|
-
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: ch, inputType: 'insertText' }));
|
|
18597
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true, cancelable: true }));
|
|
18598
|
-
}
|
|
18599
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18600
|
-
return 'Typed into: ' +
|
|
18601
|
-
(el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
|
|
18602
|
-
' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
|
|
18603
|
-
})()
|
|
18604
|
-
`);
|
|
18605
|
-
}
|
|
18606
|
-
async function hoverElement(wc, selector) {
|
|
18607
|
-
const pos = await wc.executeJavaScript(`
|
|
18608
|
-
(function() {
|
|
18609
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18610
|
-
if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
|
|
18611
|
-
if (el instanceof HTMLElement) {
|
|
18612
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
|
|
18613
|
-
}
|
|
18614
|
-
const rect = el.getBoundingClientRect();
|
|
18615
|
-
if (rect.width <= 0 || rect.height <= 0) return { error: 'Error[hidden]: Element has no visible area. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it then call read_page to refresh visible elements.' };
|
|
18616
|
-
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
|
|
18617
|
-
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
|
|
18618
|
-
const label = (el.textContent || el.tagName || 'Element').trim().slice(0, 80);
|
|
18619
|
-
return {
|
|
18620
|
-
x: Math.round(rect.left + rect.width / 2),
|
|
18621
|
-
y: Math.round(rect.top + rect.height / 2),
|
|
18622
|
-
label: label,
|
|
18623
|
-
};
|
|
18624
|
-
})()
|
|
18625
|
-
`);
|
|
18626
|
-
if (!pos || typeof pos !== "object") return "Error: Could not hover element";
|
|
18627
|
-
if ("error" in pos && typeof pos.error === "string") return pos.error;
|
|
18628
|
-
const x = typeof pos.x === "number" ? pos.x : null;
|
|
18629
|
-
const y = typeof pos.y === "number" ? pos.y : null;
|
|
18630
|
-
if (x == null || y == null)
|
|
18631
|
-
return "Error: Could not resolve hover coordinates";
|
|
18632
|
-
wc.sendInputEvent({ type: "mouseMove", x, y });
|
|
18633
|
-
const label = typeof pos.label === "string" ? pos.label : "element";
|
|
18634
|
-
return `Hovered: ${label}`;
|
|
18635
|
-
}
|
|
18636
|
-
async function focusElement(wc, selector) {
|
|
18637
|
-
return wc.executeJavaScript(`
|
|
18638
|
-
(function() {
|
|
18639
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
18640
|
-
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
18641
|
-
if (!(el instanceof HTMLElement)) return 'Error[not-interactive]: Element is not focusable';
|
|
18642
|
-
if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
|
|
18643
|
-
return 'Error[disabled]: Element is disabled';
|
|
18644
|
-
}
|
|
18645
|
-
el.focus({ preventScroll: false });
|
|
18646
|
-
return 'Focused: ' + (el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
18647
|
-
})()
|
|
18648
|
-
`);
|
|
18649
|
-
}
|
|
18650
|
-
async function selectOption(wc, index, selector, label, value) {
|
|
18651
|
-
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18652
|
-
if (!resolvedSelector)
|
|
18653
|
-
return "Error: No select element index or selector provided";
|
|
18654
|
-
return wc.executeJavaScript(`
|
|
18655
|
-
(function() {
|
|
18656
|
-
const el = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18657
|
-
if (!(el instanceof HTMLSelectElement)) {
|
|
18658
|
-
return 'Element is not a select dropdown';
|
|
18659
|
-
}
|
|
18660
|
-
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
18661
|
-
return 'Select is disabled';
|
|
18662
|
-
}
|
|
18663
|
-
const requestedLabel = ${JSON.stringify(label || "")}.trim().toLowerCase();
|
|
18664
|
-
const requestedValue = ${JSON.stringify(value || "")}.trim();
|
|
18665
|
-
const option = Array.from(el.options).find((item) => {
|
|
18666
|
-
const optionLabel = (item.textContent || '').trim().toLowerCase();
|
|
18667
|
-
return (requestedLabel && optionLabel === requestedLabel) ||
|
|
18668
|
-
(requestedValue && item.value === requestedValue);
|
|
18669
|
-
});
|
|
18670
|
-
if (!option) return 'Option not found';
|
|
18671
|
-
el.value = option.value;
|
|
18672
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
18673
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18674
|
-
return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
|
|
18675
|
-
})()
|
|
18676
|
-
`);
|
|
17782
|
+
return "";
|
|
18677
17783
|
}
|
|
18678
|
-
async function
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
if (!resolvedSelector) return "Error: No form found on the page";
|
|
18694
|
-
}
|
|
18695
|
-
const formInfo = await wc.executeJavaScript(`
|
|
18696
|
-
(function() {
|
|
18697
|
-
const target = document.querySelector(${JSON.stringify(resolvedSelector)});
|
|
18698
|
-
if (!target) return { error: 'Target not found' };
|
|
18699
|
-
// Find the form: nested, or linked via form="id" attribute
|
|
18700
|
-
var form = target instanceof HTMLFormElement ? target : target.closest('form');
|
|
18701
|
-
if (!form) {
|
|
18702
|
-
const formId = target.getAttribute('form');
|
|
18703
|
-
if (formId) {
|
|
18704
|
-
const linked = document.getElementById(formId);
|
|
18705
|
-
if (linked instanceof HTMLFormElement) form = linked;
|
|
18706
|
-
}
|
|
18707
|
-
}
|
|
18708
|
-
if (!form) return { error: 'No parent form found' };
|
|
18709
|
-
function isSubmitControl(el) {
|
|
18710
|
-
return (
|
|
18711
|
-
(el instanceof HTMLButtonElement &&
|
|
18712
|
-
((el.getAttribute('type') || '').trim().toLowerCase() === '' ||
|
|
18713
|
-
el.type === 'submit')) ||
|
|
18714
|
-
(el instanceof HTMLInputElement &&
|
|
18715
|
-
(el.type === 'submit' || el.type === 'image'))
|
|
18716
|
-
);
|
|
18717
|
-
}
|
|
18718
|
-
const submitter = isSubmitControl(target)
|
|
18719
|
-
? target
|
|
18720
|
-
: Array.from(document.querySelectorAll('button, input[type="submit"], input[type="image"]')).find(
|
|
18721
|
-
(candidate) => isSubmitControl(candidate) && candidate.form === form,
|
|
18722
|
-
);
|
|
18723
|
-
if (
|
|
18724
|
-
submitter instanceof HTMLElement &&
|
|
18725
|
-
(submitter.hasAttribute('disabled') ||
|
|
18726
|
-
submitter.getAttribute('aria-disabled') === 'true')
|
|
18727
|
-
) {
|
|
18728
|
-
return { error: 'Submit control is disabled' };
|
|
18729
|
-
}
|
|
18730
|
-
// Collect form data and determine method
|
|
18731
|
-
const submitterActionAttr =
|
|
18732
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18733
|
-
submitter instanceof HTMLInputElement
|
|
18734
|
-
? submitter.getAttribute('formaction')?.trim()
|
|
18735
|
-
: '') || '';
|
|
18736
|
-
const action = submitterActionAttr
|
|
18737
|
-
? new URL(submitterActionAttr, document.baseURI).toString()
|
|
18738
|
-
: form.action || window.location.href;
|
|
18739
|
-
const submitterMethodAttr =
|
|
18740
|
-
(submitter instanceof HTMLButtonElement ||
|
|
18741
|
-
submitter instanceof HTMLInputElement
|
|
18742
|
-
? submitter.getAttribute('formmethod')?.trim()
|
|
18743
|
-
: '') || '';
|
|
18744
|
-
const method = (
|
|
18745
|
-
submitterMethodAttr ||
|
|
18746
|
-
form.getAttribute('method') ||
|
|
18747
|
-
form.method ||
|
|
18748
|
-
'GET'
|
|
18749
|
-
).toUpperCase();
|
|
18750
|
-
let fd;
|
|
18751
|
-
try {
|
|
18752
|
-
fd = submitter instanceof HTMLElement
|
|
18753
|
-
? new FormData(form, submitter)
|
|
18754
|
-
: new FormData(form);
|
|
18755
|
-
} catch {
|
|
18756
|
-
fd = new FormData(form);
|
|
18757
|
-
}
|
|
18758
|
-
const params = new URLSearchParams();
|
|
18759
|
-
for (const [k, v] of fd.entries()) {
|
|
18760
|
-
if (typeof v === 'string') params.append(k, v);
|
|
18761
|
-
}
|
|
18762
|
-
// Use requestSubmit to fire JS submit handlers for all methods
|
|
18763
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18764
|
-
try {
|
|
18765
|
-
if (
|
|
18766
|
-
submitter instanceof HTMLButtonElement ||
|
|
18767
|
-
submitter instanceof HTMLInputElement
|
|
18768
|
-
) {
|
|
18769
|
-
form.requestSubmit(submitter);
|
|
18770
|
-
} else {
|
|
18771
|
-
form.requestSubmit();
|
|
18772
|
-
}
|
|
18773
|
-
} catch {
|
|
18774
|
-
form.requestSubmit();
|
|
18775
|
-
}
|
|
18776
|
-
return { submitted: true, method };
|
|
18777
|
-
}
|
|
18778
|
-
if (submitter instanceof HTMLElement && typeof submitter.click === 'function') {
|
|
18779
|
-
submitter.click();
|
|
18780
|
-
return { submitted: true, method };
|
|
18781
|
-
}
|
|
18782
|
-
// Last resort: form.submit() bypasses JS handlers but at least submits
|
|
18783
|
-
if (method === 'GET') {
|
|
18784
|
-
return { action, method, params: params.toString(), found: true };
|
|
18785
|
-
}
|
|
18786
|
-
form.submit();
|
|
18787
|
-
return { submitted: true, method };
|
|
18788
|
-
})()
|
|
18789
|
-
`);
|
|
18790
|
-
if (formInfo.error) return formInfo.error;
|
|
18791
|
-
if (formInfo.found && formInfo.method === "GET") {
|
|
18792
|
-
const url = new URL(formInfo.action);
|
|
18793
|
-
if (formInfo.params) {
|
|
18794
|
-
url.search = formInfo.params;
|
|
18795
|
-
}
|
|
18796
|
-
await loadPermittedUrl(wc, url.toString());
|
|
18797
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18798
|
-
const afterUrl = wc.getURL();
|
|
18799
|
-
return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
|
|
18800
|
-
}
|
|
18801
|
-
if (formInfo.submitted) {
|
|
18802
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
18803
|
-
const afterUrl = wc.getURL();
|
|
18804
|
-
return afterUrl !== beforeUrl ? `Submitted form via ${formInfo.method} -> ${afterUrl}` : `Submitted form via ${formInfo.method}`;
|
|
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));
|
|
18805
17799
|
}
|
|
18806
|
-
return "Submitted form";
|
|
18807
|
-
}
|
|
18808
|
-
async function pressKey(wc, key, index, selector) {
|
|
18809
|
-
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
18810
|
-
return wc.executeJavaScript(`
|
|
18811
|
-
(function() {
|
|
18812
|
-
const key = ${JSON.stringify(key)};
|
|
18813
|
-
const selector = ${JSON.stringify(resolvedSelector)};
|
|
18814
|
-
const target = selector ? document.querySelector(selector) : document.activeElement;
|
|
18815
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
18816
|
-
return selector ? 'Target not found' : 'No focused element';
|
|
18817
|
-
}
|
|
18818
|
-
target.focus();
|
|
18819
|
-
const eventInit = { key, bubbles: true, cancelable: true };
|
|
18820
|
-
target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
|
|
18821
|
-
target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
|
|
18822
|
-
const tag = target.tagName;
|
|
18823
|
-
const type = target instanceof HTMLInputElement ? target.type : '';
|
|
18824
|
-
if (key === 'Enter') {
|
|
18825
|
-
if (tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button'))) {
|
|
18826
|
-
target.click();
|
|
18827
|
-
} else if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
18828
|
-
const form = target.closest('form');
|
|
18829
|
-
if (form) {
|
|
18830
|
-
if (typeof form.requestSubmit === 'function') {
|
|
18831
|
-
form.requestSubmit();
|
|
18832
|
-
} else {
|
|
18833
|
-
const submitBtn = form.querySelector('[type="submit"]');
|
|
18834
|
-
if (submitBtn) submitBtn.click();
|
|
18835
|
-
else form.submit();
|
|
18836
|
-
}
|
|
18837
|
-
}
|
|
18838
|
-
}
|
|
18839
|
-
}
|
|
18840
|
-
target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
|
|
18841
|
-
return 'Pressed key: ' + key;
|
|
18842
|
-
})()
|
|
18843
|
-
`);
|
|
18844
17800
|
}
|
|
18845
|
-
async function
|
|
17801
|
+
async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
18846
17802
|
const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
|
|
18847
17803
|
const expectedText = (text || "").trim();
|
|
18848
17804
|
const expectedSelector = (selector || "").trim();
|
|
18849
|
-
|
|
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") {
|
|
18850
17814
|
return JSON.stringify({
|
|
18851
17815
|
matched: false,
|
|
18852
17816
|
error: "wait_for requires text or selector"
|
|
18853
17817
|
});
|
|
18854
17818
|
}
|
|
18855
|
-
if (
|
|
18856
|
-
|
|
17819
|
+
if (result.startsWith("Error: Invalid selector ")) {
|
|
17820
|
+
return JSON.stringify({
|
|
17821
|
+
matched: false,
|
|
17822
|
+
error: result.slice("Error: ".length)
|
|
17823
|
+
});
|
|
18857
17824
|
}
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
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(`
|
|
18861
17859
|
(function() {
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
} catch (e) {
|
|
18868
|
-
return 'invalid_selector:' + e.message;
|
|
18869
|
-
}
|
|
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;
|
|
18870
17865
|
}
|
|
18871
|
-
if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
|
|
18872
|
-
return '';
|
|
18873
17866
|
})()
|
|
18874
|
-
`)
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
value: expectedSelector,
|
|
18881
|
-
elapsed_ms: elapsedMs2
|
|
18882
|
-
});
|
|
18883
|
-
}
|
|
18884
|
-
if (result === "text") {
|
|
18885
|
-
return JSON.stringify({
|
|
18886
|
-
matched: true,
|
|
18887
|
-
type: "text",
|
|
18888
|
-
value: expectedText.slice(0, 80),
|
|
18889
|
-
elapsed_ms: elapsedMs2
|
|
18890
|
-
});
|
|
18891
|
-
}
|
|
18892
|
-
if (typeof result === "string" && result.startsWith("invalid_selector:")) {
|
|
18893
|
-
return JSON.stringify({
|
|
18894
|
-
matched: false,
|
|
18895
|
-
error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
|
|
18896
|
-
});
|
|
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;
|
|
18897
17873
|
}
|
|
18898
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
18899
17874
|
}
|
|
18900
|
-
|
|
18901
|
-
const diagnostic = expectedSelector ? await wc.executeJavaScript(`
|
|
18902
|
-
(function() {
|
|
18903
|
-
try {
|
|
18904
|
-
var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
|
|
18905
|
-
return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
|
|
18906
|
-
} catch (e) { return 'selector error: ' + e.message; }
|
|
18907
|
-
})()
|
|
18908
|
-
`) : null;
|
|
18909
|
-
return JSON.stringify({
|
|
18910
|
-
matched: false,
|
|
18911
|
-
type: expectedSelector ? "selector" : "text",
|
|
18912
|
-
value: expectedSelector ? expectedSelector : expectedText.slice(0, 80),
|
|
18913
|
-
elapsed_ms: elapsedMs,
|
|
18914
|
-
timeout_ms: effectiveTimeout,
|
|
18915
|
-
...diagnostic ? { diagnostic } : {}
|
|
18916
|
-
});
|
|
17875
|
+
return JSON.stringify(timeoutPayload);
|
|
18917
17876
|
}
|
|
18918
17877
|
function registerTools(server, tabManager, runtime2) {
|
|
18919
17878
|
server.registerPrompt(
|
|
@@ -18992,7 +17951,8 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
18992
17951
|
pageTitle = wc.getTitle();
|
|
18993
17952
|
const page = await extractContent(wc);
|
|
18994
17953
|
pageType = detectPageType(page);
|
|
18995
|
-
} catch {
|
|
17954
|
+
} catch (err) {
|
|
17955
|
+
logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
18996
17956
|
}
|
|
18997
17957
|
}
|
|
18998
17958
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -19044,7 +18004,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
19044
18004
|
},
|
|
19045
18005
|
async () => {
|
|
19046
18006
|
const activeTab = getActiveTabSummary(tabManager);
|
|
19047
|
-
if (!activeTab) return
|
|
18007
|
+
if (!activeTab) return asNoActiveTabResponse();
|
|
19048
18008
|
return asTextResponse(JSON.stringify(activeTab, null, 2));
|
|
19049
18009
|
}
|
|
19050
18010
|
);
|
|
@@ -19153,7 +18113,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19153
18113
|
},
|
|
19154
18114
|
async ({ mode }) => {
|
|
19155
18115
|
const tab = tabManager.getActiveTab();
|
|
19156
|
-
if (!tab) return
|
|
18116
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19157
18117
|
try {
|
|
19158
18118
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19159
18119
|
const effectiveMode = mode || "full";
|
|
@@ -19185,7 +18145,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19185
18145
|
},
|
|
19186
18146
|
async ({ mode }) => {
|
|
19187
18147
|
const tab = tabManager.getActiveTab();
|
|
19188
|
-
if (!tab) return
|
|
18148
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19189
18149
|
try {
|
|
19190
18150
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19191
18151
|
const effectiveMode = mode || "full";
|
|
@@ -19234,7 +18194,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19234
18194
|
},
|
|
19235
18195
|
async ({ url, postBody }) => {
|
|
19236
18196
|
const tab = tabManager.getActiveTab();
|
|
19237
|
-
if (!tab) return
|
|
18197
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19238
18198
|
const preCheck = await validateLinkDestination(url);
|
|
19239
18199
|
if (preCheck.status === "dead") {
|
|
19240
18200
|
return asTextResponse(
|
|
@@ -19276,7 +18236,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19276
18236
|
async ({ enabled, tabId, match, reload }) => {
|
|
19277
18237
|
const activeTab = tabManager.getActiveTab();
|
|
19278
18238
|
if (!activeTab && !tabId && !match) {
|
|
19279
|
-
return
|
|
18239
|
+
return asNoActiveTabResponse();
|
|
19280
18240
|
}
|
|
19281
18241
|
return withAction(
|
|
19282
18242
|
runtime2,
|
|
@@ -19319,7 +18279,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19319
18279
|
},
|
|
19320
18280
|
async ({ type }) => {
|
|
19321
18281
|
const tab = tabManager.getActiveTab();
|
|
19322
|
-
if (!tab) return
|
|
18282
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19323
18283
|
try {
|
|
19324
18284
|
const pageContent = await extractContent(tab.view.webContents);
|
|
19325
18285
|
const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
|
|
@@ -19367,7 +18327,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19367
18327
|
},
|
|
19368
18328
|
async () => {
|
|
19369
18329
|
const tab = tabManager.getActiveTab();
|
|
19370
|
-
if (!tab) return
|
|
18330
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19371
18331
|
return withAction(runtime2, tabManager, "go_back", {}, async () => {
|
|
19372
18332
|
if (!tab.canGoBack()) {
|
|
19373
18333
|
return "No previous page in history";
|
|
@@ -19388,7 +18348,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19388
18348
|
},
|
|
19389
18349
|
async () => {
|
|
19390
18350
|
const tab = tabManager.getActiveTab();
|
|
19391
|
-
if (!tab) return
|
|
18351
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19392
18352
|
return withAction(runtime2, tabManager, "go_forward", {}, async () => {
|
|
19393
18353
|
if (!tab.canGoForward()) {
|
|
19394
18354
|
return "No forward page in history";
|
|
@@ -19409,7 +18369,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19409
18369
|
},
|
|
19410
18370
|
async () => {
|
|
19411
18371
|
const tab = tabManager.getActiveTab();
|
|
19412
|
-
if (!tab) return
|
|
18372
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19413
18373
|
return withAction(runtime2, tabManager, "reload", {}, async () => {
|
|
19414
18374
|
tabManager.reloadTab(tabManager.getActiveTabId());
|
|
19415
18375
|
await waitForLoad(tab.view.webContents);
|
|
@@ -19429,7 +18389,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19429
18389
|
},
|
|
19430
18390
|
async ({ index, selector }) => {
|
|
19431
18391
|
const tab = tabManager.getActiveTab();
|
|
19432
|
-
if (!tab) return
|
|
18392
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19433
18393
|
return withAction(
|
|
19434
18394
|
runtime2,
|
|
19435
18395
|
tabManager,
|
|
@@ -19458,7 +18418,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19458
18418
|
},
|
|
19459
18419
|
async ({ index, selector }) => {
|
|
19460
18420
|
const tab = tabManager.getActiveTab();
|
|
19461
|
-
if (!tab) return
|
|
18421
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19462
18422
|
return withAction(
|
|
19463
18423
|
runtime2,
|
|
19464
18424
|
tabManager,
|
|
@@ -19487,7 +18447,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19487
18447
|
},
|
|
19488
18448
|
async ({ index, selector }) => {
|
|
19489
18449
|
const tab = tabManager.getActiveTab();
|
|
19490
|
-
if (!tab) return
|
|
18450
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19491
18451
|
return withAction(
|
|
19492
18452
|
runtime2,
|
|
19493
18453
|
tabManager,
|
|
@@ -19516,11 +18476,11 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19516
18476
|
},
|
|
19517
18477
|
async ({ index, selector }) => {
|
|
19518
18478
|
const tab = tabManager.getActiveTab();
|
|
19519
|
-
if (!tab) return
|
|
18479
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19520
18480
|
const wc = tab.view.webContents;
|
|
19521
18481
|
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
19522
18482
|
if (!resolvedSelector) {
|
|
19523
|
-
return
|
|
18483
|
+
return asErrorTextResponse("No index or selector provided");
|
|
19524
18484
|
}
|
|
19525
18485
|
const result = await wc.executeJavaScript(`
|
|
19526
18486
|
(function() {
|
|
@@ -19570,7 +18530,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19570
18530
|
);
|
|
19571
18531
|
}
|
|
19572
18532
|
if ("error" in result && typeof result.error === "string") {
|
|
19573
|
-
return
|
|
18533
|
+
return asErrorTextResponse(result.error);
|
|
19574
18534
|
}
|
|
19575
18535
|
const parts = [`<${result.tag}>`];
|
|
19576
18536
|
if ("role" in result && typeof result.role === "string" && result.role.trim()) {
|
|
@@ -19601,7 +18561,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19601
18561
|
},
|
|
19602
18562
|
async ({ index, selector, text, mode }) => {
|
|
19603
18563
|
const tab = tabManager.getActiveTab();
|
|
19604
|
-
if (!tab) return
|
|
18564
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19605
18565
|
return withAction(
|
|
19606
18566
|
runtime2,
|
|
19607
18567
|
tabManager,
|
|
@@ -19640,7 +18600,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19640
18600
|
},
|
|
19641
18601
|
async ({ index, selector, text, mode }) => {
|
|
19642
18602
|
const tab = tabManager.getActiveTab();
|
|
19643
|
-
if (!tab) return
|
|
18603
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19644
18604
|
return withAction(
|
|
19645
18605
|
runtime2,
|
|
19646
18606
|
tabManager,
|
|
@@ -19677,13 +18637,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19677
18637
|
},
|
|
19678
18638
|
async ({ index, selector, label, value }) => {
|
|
19679
18639
|
const tab = tabManager.getActiveTab();
|
|
19680
|
-
if (!tab) return
|
|
18640
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19681
18641
|
return withAction(
|
|
19682
18642
|
runtime2,
|
|
19683
18643
|
tabManager,
|
|
19684
18644
|
"select_option",
|
|
19685
18645
|
{ index, selector, label, value },
|
|
19686
|
-
async () =>
|
|
18646
|
+
async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
|
|
19687
18647
|
);
|
|
19688
18648
|
}
|
|
19689
18649
|
);
|
|
@@ -19699,23 +18659,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19699
18659
|
},
|
|
19700
18660
|
async ({ index, selector }) => {
|
|
19701
18661
|
const tab = tabManager.getActiveTab();
|
|
19702
|
-
if (!tab) return
|
|
18662
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19703
18663
|
return withAction(
|
|
19704
18664
|
runtime2,
|
|
19705
18665
|
tabManager,
|
|
19706
18666
|
"submit_form",
|
|
19707
18667
|
{ index, selector },
|
|
19708
|
-
async () =>
|
|
19709
|
-
const wc = tab.view.webContents;
|
|
19710
|
-
const beforeUrl = wc.getURL();
|
|
19711
|
-
const result = await submitForm(wc, index, selector);
|
|
19712
|
-
if (result.startsWith("Error") || result.startsWith("Target") || result.startsWith("No parent") || result.startsWith("Submit control")) {
|
|
19713
|
-
return result;
|
|
19714
|
-
}
|
|
19715
|
-
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
19716
|
-
const afterUrl = wc.getURL();
|
|
19717
|
-
return afterUrl !== beforeUrl ? `${result} -> ${afterUrl}` : result;
|
|
19718
|
-
}
|
|
18668
|
+
async () => submitFormDirect(tab.view.webContents, index, selector)
|
|
19719
18669
|
);
|
|
19720
18670
|
}
|
|
19721
18671
|
);
|
|
@@ -19732,7 +18682,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19732
18682
|
},
|
|
19733
18683
|
async ({ key, index, selector }) => {
|
|
19734
18684
|
const tab = tabManager.getActiveTab();
|
|
19735
|
-
if (!tab) return
|
|
18685
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19736
18686
|
return withAction(
|
|
19737
18687
|
runtime2,
|
|
19738
18688
|
tabManager,
|
|
@@ -19741,7 +18691,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19741
18691
|
async () => {
|
|
19742
18692
|
const wc = tab.view.webContents;
|
|
19743
18693
|
const beforeUrl = wc.getURL();
|
|
19744
|
-
const result = await
|
|
18694
|
+
const result = await pressKeyDirect(wc, key, index, selector);
|
|
19745
18695
|
if (key === "Enter") {
|
|
19746
18696
|
await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
|
|
19747
18697
|
const afterUrl = wc.getURL();
|
|
@@ -19768,7 +18718,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19768
18718
|
},
|
|
19769
18719
|
async ({ direction, amount }) => {
|
|
19770
18720
|
const tab = tabManager.getActiveTab();
|
|
19771
|
-
if (!tab) return
|
|
18721
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19772
18722
|
return withAction(
|
|
19773
18723
|
runtime2,
|
|
19774
18724
|
tabManager,
|
|
@@ -19791,7 +18741,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19791
18741
|
},
|
|
19792
18742
|
async () => {
|
|
19793
18743
|
const tab = tabManager.getActiveTab();
|
|
19794
|
-
if (!tab) return
|
|
18744
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19795
18745
|
return withAction(
|
|
19796
18746
|
runtime2,
|
|
19797
18747
|
tabManager,
|
|
@@ -19814,7 +18764,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19814
18764
|
},
|
|
19815
18765
|
async ({ strategy }) => {
|
|
19816
18766
|
const tab = tabManager.getActiveTab();
|
|
19817
|
-
if (!tab) return
|
|
18767
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19818
18768
|
return withAction(
|
|
19819
18769
|
runtime2,
|
|
19820
18770
|
tabManager,
|
|
@@ -19840,13 +18790,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19840
18790
|
},
|
|
19841
18791
|
async ({ text, selector, timeoutMs }) => {
|
|
19842
18792
|
const tab = tabManager.getActiveTab();
|
|
19843
|
-
if (!tab) return
|
|
18793
|
+
if (!tab) return asNoActiveTabResponse();
|
|
19844
18794
|
return withAction(
|
|
19845
18795
|
runtime2,
|
|
19846
18796
|
tabManager,
|
|
19847
18797
|
"wait_for",
|
|
19848
18798
|
{ text, selector, timeoutMs },
|
|
19849
|
-
async () =>
|
|
18799
|
+
async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
|
|
19850
18800
|
);
|
|
19851
18801
|
}
|
|
19852
18802
|
);
|
|
@@ -20027,7 +18977,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
20027
18977
|
},
|
|
20028
18978
|
async () => {
|
|
20029
18979
|
const tab = tabManager.getActiveTab();
|
|
20030
|
-
if (!tab) return
|
|
18980
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20031
18981
|
try {
|
|
20032
18982
|
const bounds = tab.view.getBounds();
|
|
20033
18983
|
if (bounds.width <= 0 || bounds.height <= 0) {
|
|
@@ -20096,7 +19046,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20096
19046
|
},
|
|
20097
19047
|
async ({ index, selector, text, label, durationMs, persist, color }) => {
|
|
20098
19048
|
const tab = tabManager.getActiveTab();
|
|
20099
|
-
if (!tab) return
|
|
19049
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20100
19050
|
const normalizedText = normalizeLooseString(text);
|
|
20101
19051
|
return withAction(
|
|
20102
19052
|
runtime2,
|
|
@@ -20146,7 +19096,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
20146
19096
|
},
|
|
20147
19097
|
async () => {
|
|
20148
19098
|
const tab = tabManager.getActiveTab();
|
|
20149
|
-
if (!tab) return
|
|
19099
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20150
19100
|
return withAction(
|
|
20151
19101
|
runtime2,
|
|
20152
19102
|
tabManager,
|
|
@@ -20285,8 +19235,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20285
19235
|
h.label,
|
|
20286
19236
|
void 0,
|
|
20287
19237
|
h.color
|
|
20288
|
-
).catch(
|
|
20289
|
-
|
|
19238
|
+
).catch(
|
|
19239
|
+
(err) => logger$7.warn("Failed to restore highlight after removal:", err)
|
|
19240
|
+
);
|
|
20290
19241
|
}
|
|
20291
19242
|
}
|
|
20292
19243
|
return asTextResponse(`Removed highlight ${id}`);
|
|
@@ -20426,7 +19377,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20426
19377
|
note,
|
|
20427
19378
|
{
|
|
20428
19379
|
onDuplicate: on_duplicate ?? "ask",
|
|
20429
|
-
extra:
|
|
19380
|
+
extra: getBookmarkMetadataFromArgs({
|
|
20430
19381
|
intent,
|
|
20431
19382
|
expected_content,
|
|
20432
19383
|
key_fields,
|
|
@@ -20570,7 +19521,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20570
19521
|
folderId: target.folderId,
|
|
20571
19522
|
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
|
|
20572
19523
|
note,
|
|
20573
|
-
...
|
|
19524
|
+
...getBookmarkMetadataFromArgs(args)
|
|
20574
19525
|
});
|
|
20575
19526
|
if (!updated) {
|
|
20576
19527
|
return `Bookmark ${existing.id} not found`;
|
|
@@ -20588,7 +19539,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20588
19539
|
note,
|
|
20589
19540
|
{
|
|
20590
19541
|
onDuplicate: "update",
|
|
20591
|
-
extra:
|
|
19542
|
+
extra: getBookmarkMetadataFromArgs(args)
|
|
20592
19543
|
}
|
|
20593
19544
|
);
|
|
20594
19545
|
const bookmark = result.bookmark;
|
|
@@ -20971,7 +19922,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
20971
19922
|
},
|
|
20972
19923
|
async ({ title, folder, summary, note, tags }) => {
|
|
20973
19924
|
const tab = tabManager.getActiveTab();
|
|
20974
|
-
if (!tab) return
|
|
19925
|
+
if (!tab) return asNoActiveTabResponse();
|
|
20975
19926
|
return withAction(
|
|
20976
19927
|
runtime2,
|
|
20977
19928
|
tabManager,
|
|
@@ -21115,7 +20066,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21115
20066
|
let page;
|
|
21116
20067
|
try {
|
|
21117
20068
|
page = await extractContent(wc);
|
|
21118
|
-
} catch {
|
|
20069
|
+
} catch (err) {
|
|
20070
|
+
logger$7.warn("Failed to extract page while generating suggestions:", err);
|
|
21119
20071
|
return asTextResponse(
|
|
21120
20072
|
"Could not read page. Try navigate to a working URL."
|
|
21121
20073
|
);
|
|
@@ -21222,7 +20174,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21222
20174
|
},
|
|
21223
20175
|
async ({ fields, submit }) => {
|
|
21224
20176
|
const tab = tabManager.getActiveTab();
|
|
21225
|
-
if (!tab) return
|
|
20177
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21226
20178
|
return withAction(
|
|
21227
20179
|
runtime2,
|
|
21228
20180
|
tabManager,
|
|
@@ -21236,7 +20188,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
21236
20188
|
const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
|
|
21237
20189
|
if (firstSel) {
|
|
21238
20190
|
const beforeUrl = wc.getURL();
|
|
21239
|
-
const submitResult = await
|
|
20191
|
+
const submitResult = await submitFormDirect(wc, void 0, firstSel);
|
|
21240
20192
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
21241
20193
|
const afterUrl = wc.getURL();
|
|
21242
20194
|
results.push(
|
|
@@ -21279,7 +20231,7 @@ ${results.join("\n")}`;
|
|
|
21279
20231
|
submit_selector
|
|
21280
20232
|
}) => {
|
|
21281
20233
|
const tab = tabManager.getActiveTab();
|
|
21282
|
-
if (!tab) return
|
|
20234
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21283
20235
|
return withAction(
|
|
21284
20236
|
runtime2,
|
|
21285
20237
|
tabManager,
|
|
@@ -21353,7 +20305,7 @@ ${steps.join("\n")}`;
|
|
|
21353
20305
|
},
|
|
21354
20306
|
async ({ query, selector }) => {
|
|
21355
20307
|
const tab = tabManager.getActiveTab();
|
|
21356
|
-
if (!tab) return
|
|
20308
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21357
20309
|
const qLower = query.toLowerCase().trim();
|
|
21358
20310
|
const buttonLabels = [
|
|
21359
20311
|
"add to cart",
|
|
@@ -21438,7 +20390,7 @@ ${steps.join("\n")}`;
|
|
|
21438
20390
|
},
|
|
21439
20391
|
async ({ direction, selector }) => {
|
|
21440
20392
|
const tab = tabManager.getActiveTab();
|
|
21441
|
-
if (!tab) return
|
|
20393
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21442
20394
|
return withAction(
|
|
21443
20395
|
runtime2,
|
|
21444
20396
|
tabManager,
|
|
@@ -21489,7 +20441,7 @@ ${steps.join("\n")}`;
|
|
|
21489
20441
|
},
|
|
21490
20442
|
async () => {
|
|
21491
20443
|
const tab = tabManager.getActiveTab();
|
|
21492
|
-
if (!tab) return
|
|
20444
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21493
20445
|
return withAction(
|
|
21494
20446
|
runtime2,
|
|
21495
20447
|
tabManager,
|
|
@@ -21547,7 +20499,7 @@ ${steps.join("\n")}`;
|
|
|
21547
20499
|
},
|
|
21548
20500
|
async ({ index, selector: rawSelector }) => {
|
|
21549
20501
|
const tab = tabManager.getActiveTab();
|
|
21550
|
-
if (!tab) return
|
|
20502
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21551
20503
|
return withAction(
|
|
21552
20504
|
runtime2,
|
|
21553
20505
|
tabManager,
|
|
@@ -21602,7 +20554,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21602
20554
|
},
|
|
21603
20555
|
async ({ index, selector: rawSelector, position }) => {
|
|
21604
20556
|
const tab = tabManager.getActiveTab();
|
|
21605
|
-
if (!tab) return
|
|
20557
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21606
20558
|
return withAction(
|
|
21607
20559
|
runtime2,
|
|
21608
20560
|
tabManager,
|
|
@@ -21660,7 +20612,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21660
20612
|
},
|
|
21661
20613
|
async ({ timeoutMs }) => {
|
|
21662
20614
|
const tab = tabManager.getActiveTab();
|
|
21663
|
-
if (!tab) return
|
|
20615
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21664
20616
|
return withAction(
|
|
21665
20617
|
runtime2,
|
|
21666
20618
|
tabManager,
|
|
@@ -21720,11 +20672,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21720
20672
|
let targetDomain = domain;
|
|
21721
20673
|
if (!targetDomain) {
|
|
21722
20674
|
const tab = tabManager.getActiveTab();
|
|
21723
|
-
if (!tab) return
|
|
20675
|
+
if (!tab) return asErrorTextResponse("No active tab and no domain specified");
|
|
21724
20676
|
try {
|
|
21725
20677
|
targetDomain = new URL(tab.state.url).hostname;
|
|
21726
|
-
} catch {
|
|
21727
|
-
|
|
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");
|
|
21728
20681
|
}
|
|
21729
20682
|
}
|
|
21730
20683
|
const matches = findEntriesForDomain(
|
|
@@ -21783,13 +20736,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21783
20736
|
submit_index
|
|
21784
20737
|
}) => {
|
|
21785
20738
|
const tab = tabManager.getActiveTab();
|
|
21786
|
-
if (!tab) return
|
|
20739
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21787
20740
|
const wc = tab.view.webContents;
|
|
21788
20741
|
let hostname;
|
|
21789
20742
|
try {
|
|
21790
20743
|
hostname = new URL(tab.state.url).hostname;
|
|
21791
|
-
} catch {
|
|
21792
|
-
|
|
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");
|
|
21793
20747
|
}
|
|
21794
20748
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21795
20749
|
if (matches.length === 0) {
|
|
@@ -21825,7 +20779,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21825
20779
|
}
|
|
21826
20780
|
const creds = getCredential(match.id);
|
|
21827
20781
|
if (!creds) {
|
|
21828
|
-
return
|
|
20782
|
+
return asErrorTextResponse("Credential not found in vault");
|
|
21829
20783
|
}
|
|
21830
20784
|
const results = [];
|
|
21831
20785
|
if (username_index != null) {
|
|
@@ -21876,13 +20830,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21876
20830
|
},
|
|
21877
20831
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
21878
20832
|
const tab = tabManager.getActiveTab();
|
|
21879
|
-
if (!tab) return
|
|
20833
|
+
if (!tab) return asNoActiveTabResponse();
|
|
21880
20834
|
const wc = tab.view.webContents;
|
|
21881
20835
|
let hostname;
|
|
21882
20836
|
try {
|
|
21883
20837
|
hostname = new URL(tab.state.url).hostname;
|
|
21884
|
-
} catch {
|
|
21885
|
-
|
|
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");
|
|
21886
20841
|
}
|
|
21887
20842
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
21888
20843
|
const match = credential_label ? matches.find(
|
|
@@ -22053,7 +21008,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22053
21008
|
await mcpServer.connect(transport);
|
|
22054
21009
|
await transport.handleRequest(req, res);
|
|
22055
21010
|
} catch (error) {
|
|
22056
|
-
|
|
21011
|
+
logger$7.error("Error handling request:", error);
|
|
22057
21012
|
if (!res.headersSent) {
|
|
22058
21013
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22059
21014
|
res.end(
|
|
@@ -22072,7 +21027,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22072
21027
|
};
|
|
22073
21028
|
server.once("error", (error) => {
|
|
22074
21029
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
22075
|
-
|
|
21030
|
+
logger$7.error("Server error:", error);
|
|
22076
21031
|
clearMcpAuthFile();
|
|
22077
21032
|
setMcpHealth({
|
|
22078
21033
|
configuredPort: port,
|
|
@@ -22084,14 +21039,12 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22084
21039
|
if (httpServer === server) {
|
|
22085
21040
|
httpServer = null;
|
|
22086
21041
|
}
|
|
22087
|
-
finish({
|
|
22088
|
-
ok: false,
|
|
21042
|
+
finish(errorResult(message, {
|
|
22089
21043
|
configuredPort: port,
|
|
22090
21044
|
activePort: null,
|
|
22091
21045
|
endpoint: null,
|
|
22092
|
-
authToken: null
|
|
22093
|
-
|
|
22094
|
-
});
|
|
21046
|
+
authToken: null
|
|
21047
|
+
}));
|
|
22095
21048
|
});
|
|
22096
21049
|
server.listen(port, "127.0.0.1", () => {
|
|
22097
21050
|
httpServer = server;
|
|
@@ -22106,7 +21059,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
22106
21059
|
message: `MCP server listening on ${endpoint}.`
|
|
22107
21060
|
});
|
|
22108
21061
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22109
|
-
|
|
21062
|
+
logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
22110
21063
|
}
|
|
22111
21064
|
if (mcpAuthToken) {
|
|
22112
21065
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -22145,7 +21098,7 @@ function stopMcpServer() {
|
|
|
22145
21098
|
message: "MCP server is stopped."
|
|
22146
21099
|
});
|
|
22147
21100
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
22148
|
-
|
|
21101
|
+
logger$7.info("Server stopped");
|
|
22149
21102
|
}
|
|
22150
21103
|
resolve();
|
|
22151
21104
|
});
|
|
@@ -22166,6 +21119,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
22166
21119
|
function isSafeAutomationKitId(id) {
|
|
22167
21120
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
22168
21121
|
}
|
|
21122
|
+
const logger$6 = createLogger("KitRegistry");
|
|
22169
21123
|
function getUserKitsDir() {
|
|
22170
21124
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
22171
21125
|
}
|
|
@@ -22203,10 +21157,10 @@ function getInstalledKits() {
|
|
|
22203
21157
|
if (isValidKit(parsed)) {
|
|
22204
21158
|
kits.push(parsed);
|
|
22205
21159
|
} else {
|
|
22206
|
-
|
|
21160
|
+
logger$6.warn(`Skipping invalid kit file: ${file}`);
|
|
22207
21161
|
}
|
|
22208
|
-
} catch {
|
|
22209
|
-
|
|
21162
|
+
} catch (err) {
|
|
21163
|
+
logger$6.warn(`Failed to read kit file: ${file}`, err);
|
|
22210
21164
|
}
|
|
22211
21165
|
}
|
|
22212
21166
|
return kits;
|
|
@@ -22218,69 +21172,67 @@ async function installKitFromFile() {
|
|
|
22218
21172
|
properties: ["openFile"]
|
|
22219
21173
|
});
|
|
22220
21174
|
if (canceled || filePaths.length === 0) {
|
|
22221
|
-
return
|
|
21175
|
+
return errorResult("canceled");
|
|
22222
21176
|
}
|
|
22223
21177
|
let raw;
|
|
22224
21178
|
try {
|
|
22225
21179
|
raw = fs$1.readFileSync(filePaths[0], "utf-8");
|
|
22226
21180
|
} catch {
|
|
22227
|
-
return
|
|
21181
|
+
return errorResult("Could not read the selected file.");
|
|
22228
21182
|
}
|
|
22229
21183
|
let parsed;
|
|
22230
21184
|
try {
|
|
22231
21185
|
parsed = JSON.parse(raw);
|
|
22232
21186
|
} catch {
|
|
22233
|
-
return
|
|
21187
|
+
return errorResult("File is not valid JSON.");
|
|
22234
21188
|
}
|
|
22235
21189
|
if (!isValidKit(parsed)) {
|
|
22236
|
-
return
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
};
|
|
21190
|
+
return errorResult(
|
|
21191
|
+
"File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
21192
|
+
);
|
|
22240
21193
|
}
|
|
22241
21194
|
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
22242
|
-
return
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
};
|
|
21195
|
+
return errorResult(
|
|
21196
|
+
`Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
21197
|
+
);
|
|
22246
21198
|
}
|
|
22247
21199
|
ensureKitsDir();
|
|
22248
21200
|
const dest = getKitFilePath(parsed.id);
|
|
22249
21201
|
if (!dest) {
|
|
22250
|
-
return
|
|
21202
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22251
21203
|
}
|
|
22252
21204
|
try {
|
|
22253
21205
|
fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
22254
21206
|
} catch {
|
|
22255
|
-
return
|
|
21207
|
+
return errorResult("Failed to save the kit file.");
|
|
22256
21208
|
}
|
|
22257
|
-
return {
|
|
21209
|
+
return okResult({ kit: parsed });
|
|
22258
21210
|
}
|
|
22259
21211
|
function uninstallKit(id, scheduledKitIds) {
|
|
22260
21212
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
22261
|
-
return
|
|
21213
|
+
return errorResult("Built-in kits cannot be removed.");
|
|
22262
21214
|
}
|
|
22263
21215
|
if (scheduledKitIds?.has(id)) {
|
|
22264
|
-
return
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
};
|
|
21216
|
+
return errorResult(
|
|
21217
|
+
"This kit has active scheduled jobs. Delete or reassign them first."
|
|
21218
|
+
);
|
|
22268
21219
|
}
|
|
22269
21220
|
ensureKitsDir();
|
|
22270
21221
|
const target = getKitFilePath(id);
|
|
22271
21222
|
if (!target) {
|
|
22272
|
-
return
|
|
21223
|
+
return errorResult("Kit id contains unsupported characters.");
|
|
22273
21224
|
}
|
|
22274
21225
|
if (!fs$1.existsSync(target)) {
|
|
22275
|
-
return
|
|
21226
|
+
return errorResult("Kit not found.");
|
|
22276
21227
|
}
|
|
22277
21228
|
try {
|
|
22278
21229
|
fs$1.unlinkSync(target);
|
|
22279
|
-
return
|
|
21230
|
+
return okResult();
|
|
22280
21231
|
} catch {
|
|
22281
|
-
return
|
|
21232
|
+
return errorResult("Failed to remove the kit file.");
|
|
22282
21233
|
}
|
|
22283
21234
|
}
|
|
21235
|
+
const logger$5 = createLogger("Scheduler");
|
|
22284
21236
|
let jobs = [];
|
|
22285
21237
|
let removeIdleListener = null;
|
|
22286
21238
|
let broadcastFn = null;
|
|
@@ -22305,7 +21257,7 @@ function saveJobs() {
|
|
|
22305
21257
|
try {
|
|
22306
21258
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
22307
21259
|
} catch (err) {
|
|
22308
|
-
|
|
21260
|
+
logger$5.warn("Failed to save jobs:", err);
|
|
22309
21261
|
}
|
|
22310
21262
|
}
|
|
22311
21263
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -22427,7 +21379,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22427
21379
|
};
|
|
22428
21380
|
startActivity();
|
|
22429
21381
|
if (!settings2.chatProvider) {
|
|
22430
|
-
|
|
21382
|
+
logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
22431
21383
|
appendActivity(
|
|
22432
21384
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
22433
21385
|
);
|
|
@@ -22435,7 +21387,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
22435
21387
|
return;
|
|
22436
21388
|
}
|
|
22437
21389
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
22438
|
-
|
|
21390
|
+
logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
22439
21391
|
}
|
|
22440
21392
|
try {
|
|
22441
21393
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -22488,7 +21440,7 @@ function tick(windowState, runtime2) {
|
|
|
22488
21440
|
saveJobs();
|
|
22489
21441
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
22490
21442
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
22491
|
-
|
|
21443
|
+
logger$5.warn("Unexpected error firing job:", err);
|
|
22492
21444
|
}).finally(fireNext);
|
|
22493
21445
|
};
|
|
22494
21446
|
fireNext();
|
|
@@ -23046,6 +21998,7 @@ function registerWindowControlHandlers(mainWindow) {
|
|
|
23046
21998
|
});
|
|
23047
21999
|
}
|
|
23048
22000
|
let activeChatProvider = null;
|
|
22001
|
+
const logger$4 = createLogger("IPC");
|
|
23049
22002
|
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
23050
22003
|
function registerIpcHandlers(windowState, runtime2) {
|
|
23051
22004
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
@@ -23122,7 +22075,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23122
22075
|
let parsed;
|
|
23123
22076
|
try {
|
|
23124
22077
|
parsed = new URL(rawUrl);
|
|
23125
|
-
} catch {
|
|
22078
|
+
} catch (err) {
|
|
22079
|
+
logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
|
|
23126
22080
|
return;
|
|
23127
22081
|
}
|
|
23128
22082
|
if (parsed.origin !== premiumApiOrigin) return;
|
|
@@ -23179,7 +22133,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23179
22133
|
if (wc.isDestroyed()) return 0;
|
|
23180
22134
|
try {
|
|
23181
22135
|
return await getHighlightCount(wc) ?? 0;
|
|
23182
|
-
} catch {
|
|
22136
|
+
} catch (err) {
|
|
22137
|
+
logger$4.warn("Failed to get active highlight count:", err);
|
|
23183
22138
|
return 0;
|
|
23184
22139
|
}
|
|
23185
22140
|
};
|
|
@@ -23287,13 +22242,13 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23287
22242
|
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
|
|
23288
22243
|
try {
|
|
23289
22244
|
if (!config || typeof config !== "object" || !("id" in config)) {
|
|
23290
|
-
return
|
|
22245
|
+
return errorResult("Invalid provider configuration", { models: [] });
|
|
23291
22246
|
}
|
|
23292
22247
|
return await fetchProviderModels(
|
|
23293
22248
|
config
|
|
23294
22249
|
);
|
|
23295
22250
|
} catch (err) {
|
|
23296
|
-
return {
|
|
22251
|
+
return errorResult(getErrorMessage(err), { models: [] });
|
|
23297
22252
|
}
|
|
23298
22253
|
});
|
|
23299
22254
|
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
|
|
@@ -23488,12 +22443,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23488
22443
|
const wc = activeTab.view.webContents;
|
|
23489
22444
|
const result = await captureSelectionHighlight(wc);
|
|
23490
22445
|
if (result.success && result.text) {
|
|
23491
|
-
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
23492
|
-
|
|
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
|
+
);
|
|
23493
22449
|
await emitHighlightCount();
|
|
23494
22450
|
}
|
|
23495
22451
|
return result;
|
|
23496
|
-
} catch {
|
|
22452
|
+
} catch (err) {
|
|
22453
|
+
logger$4.warn("Failed to capture highlight from active tab:", err);
|
|
23497
22454
|
return { success: false, message: "Could not capture selection" };
|
|
23498
22455
|
}
|
|
23499
22456
|
});
|
|
@@ -23517,7 +22474,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23517
22474
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
23518
22475
|
}
|
|
23519
22476
|
});
|
|
23520
|
-
} catch {
|
|
22477
|
+
} catch (err) {
|
|
22478
|
+
logger$4.warn("Failed to persist auto-highlight selection:", err);
|
|
23521
22479
|
}
|
|
23522
22480
|
});
|
|
23523
22481
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
@@ -23530,7 +22488,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23530
22488
|
if (wc.isDestroyed()) return false;
|
|
23531
22489
|
try {
|
|
23532
22490
|
return scrollToHighlight(wc, index);
|
|
23533
|
-
} catch {
|
|
22491
|
+
} catch (err) {
|
|
22492
|
+
logger$4.warn("Failed to scroll to highlight:", err);
|
|
23534
22493
|
return false;
|
|
23535
22494
|
}
|
|
23536
22495
|
});
|
|
@@ -23545,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23545
22504
|
await emitHighlightCount();
|
|
23546
22505
|
}
|
|
23547
22506
|
return removed;
|
|
23548
|
-
} catch {
|
|
22507
|
+
} catch (err) {
|
|
22508
|
+
logger$4.warn("Failed to remove highlight at index:", err);
|
|
23549
22509
|
return false;
|
|
23550
22510
|
}
|
|
23551
22511
|
});
|
|
@@ -23560,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23560
22520
|
await emitHighlightCount();
|
|
23561
22521
|
}
|
|
23562
22522
|
return cleared;
|
|
23563
|
-
} catch {
|
|
22523
|
+
} catch (err) {
|
|
22524
|
+
logger$4.warn("Failed to clear highlight elements:", err);
|
|
23564
22525
|
return false;
|
|
23565
22526
|
}
|
|
23566
22527
|
});
|
|
@@ -23644,7 +22605,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23644
22605
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
|
|
23645
22606
|
assertString(email, "email");
|
|
23646
22607
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23647
|
-
return
|
|
22608
|
+
return errorResult("Invalid email format");
|
|
23648
22609
|
}
|
|
23649
22610
|
trackPremiumFunnel("activation_attempted");
|
|
23650
22611
|
const result = await requestActivationCode(email);
|
|
@@ -23660,11 +22621,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23660
22621
|
assertString(code, "code");
|
|
23661
22622
|
assertString(challengeToken, "challengeToken");
|
|
23662
22623
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
|
|
23663
|
-
return {
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
error: "Invalid email format"
|
|
23667
|
-
};
|
|
22624
|
+
return errorResult("Invalid email format", {
|
|
22625
|
+
state: getPremiumState()
|
|
22626
|
+
});
|
|
23668
22627
|
}
|
|
23669
22628
|
trackPremiumFunnel("activation_attempted");
|
|
23670
22629
|
const result = await verifyActivationCode(email, code, challengeToken);
|
|
@@ -24099,6 +23058,7 @@ ${lines.join("\n")}
|
|
|
24099
23058
|
}
|
|
24100
23059
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
24101
23060
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
23061
|
+
const logger$3 = createLogger("Runtime");
|
|
24102
23062
|
function clone(value) {
|
|
24103
23063
|
return JSON.parse(JSON.stringify(value));
|
|
24104
23064
|
}
|
|
@@ -24488,9 +23448,7 @@ ${progress}
|
|
|
24488
23448
|
JSON.stringify(persisted, null, 2),
|
|
24489
23449
|
"utf-8"
|
|
24490
23450
|
)
|
|
24491
|
-
).catch(
|
|
24492
|
-
(err) => console.error("[Vessel] Failed to persist runtime state:", err)
|
|
24493
|
-
);
|
|
23451
|
+
).catch((err) => logger$3.error("Failed to persist runtime state:", err));
|
|
24494
23452
|
}
|
|
24495
23453
|
schedulePersist() {
|
|
24496
23454
|
this.persistDirty = true;
|
|
@@ -24755,6 +23713,7 @@ function installDownloadHandler(chromeView) {
|
|
|
24755
23713
|
});
|
|
24756
23714
|
});
|
|
24757
23715
|
}
|
|
23716
|
+
const logger$2 = createLogger("Shortcuts");
|
|
24758
23717
|
function registerHighlightShortcut(mainWindow, tabManager) {
|
|
24759
23718
|
const register = () => {
|
|
24760
23719
|
electron.globalShortcut.unregister("CommandOrControl+H");
|
|
@@ -24764,7 +23723,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
|
|
|
24764
23723
|
tabManager.captureHighlightFromActiveTab();
|
|
24765
23724
|
});
|
|
24766
23725
|
if (!success) {
|
|
24767
|
-
|
|
23726
|
+
logger$2.warn("Failed to register Ctrl+H shortcut");
|
|
24768
23727
|
}
|
|
24769
23728
|
};
|
|
24770
23729
|
register();
|
|
@@ -24833,6 +23792,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
24833
23792
|
});
|
|
24834
23793
|
}
|
|
24835
23794
|
}
|
|
23795
|
+
const logger$1 = createLogger("Splash");
|
|
24836
23796
|
function findIconBase64() {
|
|
24837
23797
|
const candidates = [
|
|
24838
23798
|
path$1.join(process.resourcesPath, "vessel-icon.png"),
|
|
@@ -24995,7 +23955,7 @@ function createSplashWindow() {
|
|
|
24995
23955
|
fs$1.writeFileSync(tmpPath, html, "utf-8");
|
|
24996
23956
|
void splash.loadFile(tmpPath);
|
|
24997
23957
|
} catch (err) {
|
|
24998
|
-
|
|
23958
|
+
logger$1.warn("Failed to write temp HTML, using fallback:", err);
|
|
24999
23959
|
void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
|
|
25000
23960
|
}
|
|
25001
23961
|
return splash;
|
|
@@ -25005,6 +23965,7 @@ function closeSplash(splash, delayMs = 0) {
|
|
|
25005
23965
|
if (!splash.isDestroyed()) splash.close();
|
|
25006
23966
|
}, delayMs);
|
|
25007
23967
|
}
|
|
23968
|
+
const logger = createLogger("Bootstrap");
|
|
25008
23969
|
let runtime = null;
|
|
25009
23970
|
function checkWritableUserData(userDataPath) {
|
|
25010
23971
|
const issues = [];
|
|
@@ -25124,7 +24085,7 @@ async function bootstrap() {
|
|
|
25124
24085
|
};
|
|
25125
24086
|
let didInitializeChromeRenderer = false;
|
|
25126
24087
|
const splashTimeout = setTimeout(() => {
|
|
25127
|
-
|
|
24088
|
+
logger.warn("Renderer did not finish loading before splash timeout");
|
|
25128
24089
|
revealMainWindow();
|
|
25129
24090
|
}, 8e3);
|
|
25130
24091
|
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
@@ -25175,8 +24136,8 @@ async function bootstrap() {
|
|
|
25175
24136
|
"did-fail-load",
|
|
25176
24137
|
(_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
|
25177
24138
|
if (!isMainFrame) return;
|
|
25178
|
-
|
|
25179
|
-
"
|
|
24139
|
+
logger.error(
|
|
24140
|
+
"Chrome renderer failed to load:",
|
|
25180
24141
|
errorCode,
|
|
25181
24142
|
errorDescription,
|
|
25182
24143
|
validatedURL
|
|
@@ -25187,21 +24148,21 @@ async function bootstrap() {
|
|
|
25187
24148
|
);
|
|
25188
24149
|
loadRenderers(chromeView, sidebarView, devtoolsPanelView);
|
|
25189
24150
|
startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
|
|
25190
|
-
|
|
24151
|
+
logger.error("MCP server failed to start:", err);
|
|
25191
24152
|
});
|
|
25192
24153
|
}
|
|
25193
24154
|
process.on("uncaughtException", (error) => {
|
|
25194
|
-
|
|
24155
|
+
logger.error("Uncaught exception:", error.message, error.stack);
|
|
25195
24156
|
electron.app.quit();
|
|
25196
24157
|
});
|
|
25197
24158
|
process.on("unhandledRejection", (reason) => {
|
|
25198
|
-
|
|
25199
|
-
"
|
|
24159
|
+
logger.error(
|
|
24160
|
+
"Unhandled rejection:",
|
|
25200
24161
|
reason instanceof Error ? reason.message : reason
|
|
25201
24162
|
);
|
|
25202
24163
|
});
|
|
25203
24164
|
electron.app.whenReady().then(bootstrap).catch((error) => {
|
|
25204
|
-
|
|
24165
|
+
logger.error("Failed to bootstrap application:", error);
|
|
25205
24166
|
electron.app.quit();
|
|
25206
24167
|
});
|
|
25207
24168
|
electron.app.on("window-all-closed", () => {
|