@quanta-intellect/vessel-browser 0.1.28 → 0.1.30
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/README.md +32 -7
- package/out/main/index.js +198 -84
- package/out/preload/content-script.js +19 -0
- package/out/preload/index.js +1 -1
- package/out/renderer/assets/{index-D38a5Gkn.js → index-DVD9XuhC.js} +298 -187
- package/out/renderer/assets/{index-DP2yMHwF.css → index-eS3ccAls.css} +79 -22
- package/out/renderer/index.html +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ https://github.com/user-attachments/assets/0a72b48a-873a-4eb0-b8f2-23e34d8472c4
|
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
|
32
|
-
Want the full agent toolkit from day one? [Start a
|
|
32
|
+
Want the full agent toolkit from day one? [Start a 7-Day Free Trial of Vessel Premium — $5.99/mo](https://vesselpremium.quantaintellect.com/checkout).
|
|
33
33
|
|
|
34
34
|
### Fastest Install Today
|
|
35
35
|
|
|
@@ -162,8 +162,10 @@ The installer:
|
|
|
162
162
|
- creates a `vessel-browser-status` helper in `~/.local/bin`
|
|
163
163
|
- creates a desktop entry for Linux app launchers
|
|
164
164
|
- writes `~/.config/vessel/vessel-settings.json` with MCP port `3100`
|
|
165
|
+
- writes `~/.config/vessel/mcp-stdio-snippet.json`
|
|
165
166
|
- writes `~/.config/vessel/mcp-http-snippet.json`
|
|
166
|
-
-
|
|
167
|
+
- installs a `vessel-browser-mcp` helper that can run as a stdio-to-HTTP proxy (`--stdio`) or print config snippets
|
|
168
|
+
- prints the exact recommended stdio MCP snippet to paste into your harness config
|
|
167
169
|
|
|
168
170
|
The packaged AppImage path:
|
|
169
171
|
|
|
@@ -204,7 +206,7 @@ Notes:
|
|
|
204
206
|
|
|
205
207
|
- `npm run dev` still launches the stock Electron binary, so Linux may continue showing the default Electron gear icon in development
|
|
206
208
|
- packaged builds created with `npm run dist` / `npm run dist:dir` use the Vessel app icon
|
|
207
|
-
- the tracked smoke test runs typecheck, build, and the Electron navigation regression harness
|
|
209
|
+
- the tracked smoke test runs typecheck, build, the MCP stdio proxy regression check, and the Electron navigation regression harness
|
|
208
210
|
- for headless CI, run the smoke test under `xvfb-run -a npm run smoke:test`
|
|
209
211
|
|
|
210
212
|
### Setting up Vessel for Hermes Agent or OpenClaw
|
|
@@ -214,7 +216,7 @@ Vessel is designed to act as the browser runtime that your external agent harnes
|
|
|
214
216
|
1. Launch Vessel
|
|
215
217
|
2. Open Settings (`Ctrl+,`) to confirm MCP status, copy the endpoint, or change the MCP port
|
|
216
218
|
3. Optional: set an Obsidian vault path or session preferences
|
|
217
|
-
4. Start Hermes Agent or OpenClaw and
|
|
219
|
+
4. Start Hermes Agent or OpenClaw and point it at Vessel — the easiest way is `vessel-browser-mcp --stdio` as the MCP command (auth is resolved automatically), or connect directly to `http://127.0.0.1:<mcpPort>/mcp` with the bearer token from `~/.config/vessel/mcp-auth.json`
|
|
218
220
|
5. Use the Supervisor panel in Vessel's sidebar to pause the agent, change approval mode, review pending approvals, checkpoint, or restore the browser session while the harness runs
|
|
219
221
|
6. Use the Bookmarks panel to organize saved pages into folders and expose those bookmarks back to the agent over MCP
|
|
220
222
|
|
|
@@ -316,7 +318,23 @@ The extraction output can distinguish:
|
|
|
316
318
|
- active blocking overlays
|
|
317
319
|
- dormant consent/modal UI present in the DOM but not active for the current session or region
|
|
318
320
|
|
|
319
|
-
|
|
321
|
+
Stdio proxy MCP config (recommended — resolves auth automatically):
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"mcpServers": {
|
|
326
|
+
"vessel": {
|
|
327
|
+
"command": "vessel-browser-mcp",
|
|
328
|
+
"args": ["--stdio"]
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The stdio proxy reads the bearer token from `~/.config/vessel/mcp-auth.json` at connection time, so no manual token management is needed.
|
|
335
|
+
Vessel must already be running when your MCP client connects, and `~/.config/vessel/mcp-auth.json` must exist from install or first launch.
|
|
336
|
+
|
|
337
|
+
Generic HTTP MCP config (requires copying the token manually):
|
|
320
338
|
|
|
321
339
|
```json
|
|
322
340
|
{
|
|
@@ -346,8 +364,9 @@ mcp_servers:
|
|
|
346
364
|
|
|
347
365
|
## Configuration
|
|
348
366
|
|
|
349
|
-
The installer writes
|
|
367
|
+
The installer writes three snippets to:
|
|
350
368
|
|
|
369
|
+
- `~/.config/vessel/mcp-stdio-snippet.json`
|
|
351
370
|
- `~/.config/vessel/mcp-http-snippet.json`
|
|
352
371
|
- `~/.config/vessel/mcp-hermes-snippet.yaml`
|
|
353
372
|
|
|
@@ -360,9 +379,15 @@ vessel-browser-mcp
|
|
|
360
379
|
Helper examples:
|
|
361
380
|
|
|
362
381
|
```bash
|
|
363
|
-
#
|
|
382
|
+
# Run as stdio-to-HTTP proxy (for MCP client integration)
|
|
383
|
+
vessel-browser-mcp --stdio
|
|
384
|
+
|
|
385
|
+
# Recommended stdio MCP snippet
|
|
364
386
|
vessel-browser-mcp
|
|
365
387
|
|
|
388
|
+
# Generic JSON snippet with Authorization header
|
|
389
|
+
vessel-browser-mcp --format json
|
|
390
|
+
|
|
366
391
|
# Hermes-ready YAML snippet with Authorization header
|
|
367
392
|
vessel-browser-mcp --format hermes
|
|
368
393
|
|
package/out/main/index.js
CHANGED
|
@@ -5177,9 +5177,6 @@ class AnthropicProvider {
|
|
|
5177
5177
|
let iterationsUsed = 0;
|
|
5178
5178
|
for (let i = 0; i < maxIterations; i++) {
|
|
5179
5179
|
iterationsUsed = i + 1;
|
|
5180
|
-
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
5181
|
-
const sysTokenEstimate = systemPrompt.length;
|
|
5182
|
-
const streamStartTime = Date.now();
|
|
5183
5180
|
const stream = this.client.messages.stream(
|
|
5184
5181
|
{
|
|
5185
5182
|
model: this.model,
|
|
@@ -5496,8 +5493,6 @@ class OpenAICompatProvider {
|
|
|
5496
5493
|
let iterationsUsed = 0;
|
|
5497
5494
|
for (let i = 0; i < maxIterations; i++) {
|
|
5498
5495
|
iterationsUsed = i + 1;
|
|
5499
|
-
const msgTokenEstimate = JSON.stringify(messages).length;
|
|
5500
|
-
const streamStartTime = Date.now();
|
|
5501
5496
|
let textAccum = "";
|
|
5502
5497
|
const toolCallAccums = {};
|
|
5503
5498
|
let finishReason = null;
|
|
@@ -8085,20 +8080,21 @@ function pruneToolsForContext(tools, pageType, query = "") {
|
|
|
8085
8080
|
const ctx = pageType ?? "GENERAL";
|
|
8086
8081
|
const hints = CONTEXT_HINTS[ctx] ?? {};
|
|
8087
8082
|
const intents = inferIntent(query);
|
|
8088
|
-
const scored = tools.filter((tool) =>
|
|
8083
|
+
const scored = tools.filter((tool) => shouldIncludeTool(tool.name, ctx, intents)).map((tool) => ({
|
|
8089
8084
|
tool,
|
|
8090
8085
|
score: scoreForContext(tool.name, ctx)
|
|
8091
8086
|
}));
|
|
8092
8087
|
scored.sort((a, b) => a.score - b.score);
|
|
8093
8088
|
return scored.map(({ tool, score }) => {
|
|
8089
|
+
let description = tool.description ?? "";
|
|
8094
8090
|
const hint = hints[tool.name];
|
|
8095
8091
|
if (hint && score <= 20) {
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
}
|
|
8092
|
+
description = hint + description;
|
|
8093
|
+
}
|
|
8094
|
+
if (isToolGated(tool.name)) {
|
|
8095
|
+
description = `[Premium — requires Vessel Premium] ${description}`;
|
|
8100
8096
|
}
|
|
8101
|
-
return tool;
|
|
8097
|
+
return description !== tool.description ? { ...tool, description } : tool;
|
|
8102
8098
|
});
|
|
8103
8099
|
}
|
|
8104
8100
|
function trimText(value) {
|
|
@@ -8512,13 +8508,17 @@ function updateBookmark(id, updates) {
|
|
|
8512
8508
|
emit();
|
|
8513
8509
|
return { ...bookmark };
|
|
8514
8510
|
}
|
|
8515
|
-
function removeFolder(id) {
|
|
8511
|
+
function removeFolder(id, deleteContents = false) {
|
|
8516
8512
|
load();
|
|
8517
8513
|
const exists = state.folders.some((f) => f.id === id);
|
|
8518
8514
|
if (!exists) return false;
|
|
8519
|
-
|
|
8520
|
-
(b) => b.folderId
|
|
8521
|
-
|
|
8515
|
+
if (deleteContents) {
|
|
8516
|
+
state.bookmarks = state.bookmarks.filter((b) => b.folderId !== id);
|
|
8517
|
+
} else {
|
|
8518
|
+
state.bookmarks = state.bookmarks.map(
|
|
8519
|
+
(b) => b.folderId === id ? { ...b, folderId: UNSORTED_ID } : b
|
|
8520
|
+
);
|
|
8521
|
+
}
|
|
8522
8522
|
state.folders = state.folders.filter((f) => f.id !== id);
|
|
8523
8523
|
save();
|
|
8524
8524
|
emit();
|
|
@@ -9204,6 +9204,18 @@ async function executePageScript(wc, script, options) {
|
|
|
9204
9204
|
}
|
|
9205
9205
|
}
|
|
9206
9206
|
}
|
|
9207
|
+
async function waitForJsReady(wc, timeout = 8e3) {
|
|
9208
|
+
const start = Date.now();
|
|
9209
|
+
while (Date.now() - start < timeout) {
|
|
9210
|
+
const ready = await executePageScript(wc, "1", {
|
|
9211
|
+
timeoutMs: 250,
|
|
9212
|
+
userGesture: true,
|
|
9213
|
+
label: "js-ready probe"
|
|
9214
|
+
});
|
|
9215
|
+
if (ready === 1) return;
|
|
9216
|
+
await sleep$1(250);
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9207
9219
|
function waitForLoad$1(wc, timeout = 5e3) {
|
|
9208
9220
|
return new Promise((resolve) => {
|
|
9209
9221
|
let finished = false;
|
|
@@ -10492,7 +10504,89 @@ async function tryDismissConsentIframe(wc) {
|
|
|
10492
10504
|
}
|
|
10493
10505
|
return null;
|
|
10494
10506
|
}
|
|
10507
|
+
async function tryAcceptCookiesQuickly(wc) {
|
|
10508
|
+
const dismissed = await executePageScript(
|
|
10509
|
+
wc,
|
|
10510
|
+
`
|
|
10511
|
+
(function() {
|
|
10512
|
+
var selectors = [
|
|
10513
|
+
'#onetrust-accept-btn-handler',
|
|
10514
|
+
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
10515
|
+
'[data-cookiefirst-action="accept"]',
|
|
10516
|
+
'.cookie-consent-accept-all',
|
|
10517
|
+
'#accept-cookies',
|
|
10518
|
+
'.cc-accept',
|
|
10519
|
+
'.cc-btn.cc-allow',
|
|
10520
|
+
'[aria-label="Accept cookies"]',
|
|
10521
|
+
'[aria-label="Accept all cookies"]',
|
|
10522
|
+
'[data-testid="cookie-accept"]',
|
|
10523
|
+
'[data-testid="consent-accept"]',
|
|
10524
|
+
'[data-testid="accept-all"]',
|
|
10525
|
+
'button[class*="consent"][class*="accept"]',
|
|
10526
|
+
'button[class*="privacy"][class*="accept"]',
|
|
10527
|
+
'.fc-cta-consent',
|
|
10528
|
+
'#sp_choice_button_accept',
|
|
10529
|
+
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
10530
|
+
'[class*="truste"] [class*="accept"]',
|
|
10531
|
+
'[id*="consent-accept"]',
|
|
10532
|
+
'[class*="cmp-accept"]',
|
|
10533
|
+
];
|
|
10534
|
+
var textPatterns = [
|
|
10535
|
+
'accept all',
|
|
10536
|
+
'accept cookies',
|
|
10537
|
+
'allow all',
|
|
10538
|
+
'allow cookies',
|
|
10539
|
+
'agree',
|
|
10540
|
+
'got it',
|
|
10541
|
+
'ok',
|
|
10542
|
+
'i agree',
|
|
10543
|
+
'i accept',
|
|
10544
|
+
'consent',
|
|
10545
|
+
'continue',
|
|
10546
|
+
'accept and continue',
|
|
10547
|
+
'accept & continue'
|
|
10548
|
+
];
|
|
10549
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
10550
|
+
var el = document.querySelector(selectors[i]);
|
|
10551
|
+
if (el && el instanceof HTMLElement) {
|
|
10552
|
+
el.click();
|
|
10553
|
+
return "Dismissed cookie banner via: " + selectors[i];
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
var buttons = document.querySelectorAll('button, a[role="button"], [type="submit"]');
|
|
10557
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
10558
|
+
var btn = buttons[j];
|
|
10559
|
+
var text = (btn.textContent || '').trim().toLowerCase();
|
|
10560
|
+
for (var k = 0; k < textPatterns.length; k++) {
|
|
10561
|
+
if (text === textPatterns[k] || text.startsWith(textPatterns[k])) {
|
|
10562
|
+
btn.click();
|
|
10563
|
+
return "Dismissed cookie banner via text match: " + text;
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
}
|
|
10567
|
+
return null;
|
|
10568
|
+
})()
|
|
10569
|
+
`,
|
|
10570
|
+
{
|
|
10571
|
+
label: "accept cookies",
|
|
10572
|
+
timeoutMs: 1200
|
|
10573
|
+
}
|
|
10574
|
+
);
|
|
10575
|
+
if (dismissed) return dismissed;
|
|
10576
|
+
return tryDismissConsentIframe(wc);
|
|
10577
|
+
}
|
|
10495
10578
|
async function clearOverlays(wc, strategy = "auto") {
|
|
10579
|
+
const quickCookieResult = await tryAcceptCookiesQuickly(wc);
|
|
10580
|
+
if (quickCookieResult === PAGE_SCRIPT_TIMEOUT) {
|
|
10581
|
+
return pageBusyError("clear_overlays");
|
|
10582
|
+
}
|
|
10583
|
+
if (quickCookieResult) {
|
|
10584
|
+
return [
|
|
10585
|
+
quickCookieResult,
|
|
10586
|
+
"Stopped after a lightweight consent pass to keep the page responsive. Re-run only if the banner is still blocking the page."
|
|
10587
|
+
].join("\n");
|
|
10588
|
+
}
|
|
10589
|
+
await waitForJsReady(wc, 1500);
|
|
10496
10590
|
const steps = [];
|
|
10497
10591
|
let cleared = 0;
|
|
10498
10592
|
const maxIterations = 8;
|
|
@@ -10556,6 +10650,12 @@ Submitted modal: ${submitResult}`;
|
|
|
10556
10650
|
actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
|
|
10557
10651
|
}
|
|
10558
10652
|
steps.push(actionMessage);
|
|
10653
|
+
if (overlay.kind === "cookie_consent") {
|
|
10654
|
+
steps.push(
|
|
10655
|
+
"Stopped after a lightweight consent pass to keep the page responsive. Re-run only if the banner is still blocking the page."
|
|
10656
|
+
);
|
|
10657
|
+
return steps.join("\n");
|
|
10658
|
+
}
|
|
10559
10659
|
await sleep$1(250);
|
|
10560
10660
|
const after = await extractContent(wc);
|
|
10561
10661
|
const afterState = describeOverlayState(after);
|
|
@@ -12030,7 +12130,7 @@ async function executeAction(name, args, ctx) {
|
|
|
12030
12130
|
}
|
|
12031
12131
|
case "click": {
|
|
12032
12132
|
if (!wc) return "Error: No active tab";
|
|
12033
|
-
const selector = await resolveSelector$1(wc, args.index, args.selector);
|
|
12133
|
+
const selector = typeof args.selector === "string" && args.selector.trim() ? await resolveSelector$1(wc, void 0, args.selector) : typeof args.index === "number" ? `__vessel_idx:${args.index}` : await resolveSelector$1(wc, args.index, args.selector);
|
|
12034
12134
|
if (!selector) return "Error: No element index or selector provided";
|
|
12035
12135
|
return clickResolvedSelector$1(wc, selector);
|
|
12036
12136
|
}
|
|
@@ -12599,10 +12699,18 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
12599
12699
|
(el) => (el.text || "").toLowerCase() === "next" || el.text === "›" || el.text === "»"
|
|
12600
12700
|
);
|
|
12601
12701
|
const hasOverlays = page.overlays.some((o) => o.blocksInteraction);
|
|
12702
|
+
const hasCookieConsent = page.overlays.some(
|
|
12703
|
+
(overlay) => overlay.blocksInteraction && overlay.kind === "cookie_consent"
|
|
12704
|
+
);
|
|
12602
12705
|
if (hasOverlays) {
|
|
12603
12706
|
suggestions.push("BLOCKING OVERLAY detected — dismiss it first:");
|
|
12604
|
-
|
|
12605
|
-
|
|
12707
|
+
if (hasCookieConsent) {
|
|
12708
|
+
suggestions.push(" → accept_cookies for consent banners");
|
|
12709
|
+
suggestions.push(" → clear_overlays only if consent handling does not unblock the page");
|
|
12710
|
+
} else {
|
|
12711
|
+
suggestions.push(" → clear_overlays for stacked modals");
|
|
12712
|
+
suggestions.push(" → or dismiss_popup for a single popup");
|
|
12713
|
+
}
|
|
12606
12714
|
suggestions.push("");
|
|
12607
12715
|
}
|
|
12608
12716
|
if (hasPasswordField) {
|
|
@@ -12807,64 +12915,11 @@ ${steps.join("\n")}`;
|
|
|
12807
12915
|
}
|
|
12808
12916
|
case "accept_cookies": {
|
|
12809
12917
|
if (!wc) return "Error: No active tab";
|
|
12810
|
-
const dismissed = await
|
|
12811
|
-
wc,
|
|
12812
|
-
`
|
|
12813
|
-
(function() {
|
|
12814
|
-
// Common cookie consent selectors — OneTrust, CookieBot, GDPR banners
|
|
12815
|
-
var selectors = [
|
|
12816
|
-
'#onetrust-accept-btn-handler',
|
|
12817
|
-
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
12818
|
-
'[data-cookiefirst-action="accept"]',
|
|
12819
|
-
'.cookie-consent-accept-all',
|
|
12820
|
-
'#accept-cookies',
|
|
12821
|
-
'.cc-accept',
|
|
12822
|
-
'.cc-btn.cc-allow',
|
|
12823
|
-
'[aria-label="Accept cookies"]',
|
|
12824
|
-
'[aria-label="Accept all cookies"]',
|
|
12825
|
-
'[data-testid="cookie-accept"]',
|
|
12826
|
-
// CNN / WarnerMedia / common consent SDKs
|
|
12827
|
-
'[data-testid="consent-accept"]',
|
|
12828
|
-
'[data-testid="accept-all"]',
|
|
12829
|
-
'button[class*="consent"][class*="accept"]',
|
|
12830
|
-
'button[class*="privacy"][class*="accept"]',
|
|
12831
|
-
'.fc-cta-consent',
|
|
12832
|
-
'#sp_choice_button_accept',
|
|
12833
|
-
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
12834
|
-
'[class*="truste"] [class*="accept"]',
|
|
12835
|
-
'[id*="consent-accept"]',
|
|
12836
|
-
'[class*="cmp-accept"]',
|
|
12837
|
-
];
|
|
12838
|
-
// Also try text-matching on buttons
|
|
12839
|
-
var textPatterns = ['accept all', 'accept cookies', 'allow all', 'allow cookies', 'agree', 'got it', 'ok', 'i agree', 'i accept', 'consent', 'continue', 'accept and continue', 'accept & continue'];
|
|
12840
|
-
for (var i = 0; i < selectors.length; i++) {
|
|
12841
|
-
var el = document.querySelector(selectors[i]);
|
|
12842
|
-
if (el && el instanceof HTMLElement) { el.click(); return "Dismissed cookie banner via: " + selectors[i]; }
|
|
12843
|
-
}
|
|
12844
|
-
var buttons = document.querySelectorAll('button, a[role="button"], [type="submit"]');
|
|
12845
|
-
for (var j = 0; j < buttons.length; j++) {
|
|
12846
|
-
var btn = buttons[j];
|
|
12847
|
-
var text = (btn.textContent || '').trim().toLowerCase();
|
|
12848
|
-
for (var k = 0; k < textPatterns.length; k++) {
|
|
12849
|
-
if (text === textPatterns[k] || text.startsWith(textPatterns[k])) {
|
|
12850
|
-
btn.click();
|
|
12851
|
-
return "Dismissed cookie banner via text match: " + text;
|
|
12852
|
-
}
|
|
12853
|
-
}
|
|
12854
|
-
}
|
|
12855
|
-
return null;
|
|
12856
|
-
})()
|
|
12857
|
-
`,
|
|
12858
|
-
{
|
|
12859
|
-
label: "accept cookies"
|
|
12860
|
-
}
|
|
12861
|
-
);
|
|
12918
|
+
const dismissed = await tryAcceptCookiesQuickly(wc);
|
|
12862
12919
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
12863
12920
|
return pageBusyError("accept_cookies");
|
|
12864
12921
|
}
|
|
12865
12922
|
if (dismissed) return dismissed;
|
|
12866
|
-
const iframeResult = await tryDismissConsentIframe(wc);
|
|
12867
|
-
if (iframeResult) return iframeResult;
|
|
12868
12923
|
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
12869
12924
|
}
|
|
12870
12925
|
case "extract_table": {
|
|
@@ -15906,7 +15961,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
15906
15961
|
{ index, selector },
|
|
15907
15962
|
async () => {
|
|
15908
15963
|
const wc = tab.view.webContents;
|
|
15909
|
-
const resolvedSelector = await resolveSelector(wc, index, selector);
|
|
15964
|
+
const resolvedSelector = typeof selector === "string" && selector.trim() ? await resolveSelector(wc, void 0, selector) : typeof index === "number" ? `__vessel_idx:${index}` : await resolveSelector(wc, index, selector);
|
|
15910
15965
|
if (!resolvedSelector) {
|
|
15911
15966
|
return "Error: No index or selector provided";
|
|
15912
15967
|
}
|
|
@@ -17216,22 +17271,29 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
17216
17271
|
"folder_remove",
|
|
17217
17272
|
{
|
|
17218
17273
|
title: "Remove Bookmark Folder",
|
|
17219
|
-
description: "Remove a folder.
|
|
17274
|
+
description: "Remove a folder. By default bookmarks in it are moved to Unsorted. Set delete_contents to true to delete them with the folder.",
|
|
17220
17275
|
inputSchema: {
|
|
17221
|
-
folder_id: zod.z.string().describe("ID of the folder to remove")
|
|
17276
|
+
folder_id: zod.z.string().describe("ID of the folder to remove"),
|
|
17277
|
+
delete_contents: zod.z.boolean().optional().default(false).describe(
|
|
17278
|
+
"If true, delete all bookmarks in the folder. If false (default), move them to Unsorted."
|
|
17279
|
+
)
|
|
17222
17280
|
}
|
|
17223
17281
|
},
|
|
17224
|
-
async ({ folder_id }) => {
|
|
17282
|
+
async ({ folder_id, delete_contents }) => {
|
|
17225
17283
|
return withAction(
|
|
17226
17284
|
runtime2,
|
|
17227
17285
|
tabManager,
|
|
17228
17286
|
"remove_bookmark_folder",
|
|
17229
|
-
{ folder_id },
|
|
17287
|
+
{ folder_id, delete_contents },
|
|
17230
17288
|
async () => {
|
|
17231
|
-
const removed = removeFolder(
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
)
|
|
17289
|
+
const removed = removeFolder(
|
|
17290
|
+
folder_id,
|
|
17291
|
+
delete_contents
|
|
17292
|
+
);
|
|
17293
|
+
if (!removed) return `Folder ${folder_id} not found`;
|
|
17294
|
+
return composeFolderAwareResponse(
|
|
17295
|
+
delete_contents ? `Removed folder ${folder_id} and deleted its bookmarks.` : `Removed folder ${folder_id}. Bookmarks moved to Unsorted.`
|
|
17296
|
+
);
|
|
17235
17297
|
}
|
|
17236
17298
|
);
|
|
17237
17299
|
}
|
|
@@ -19072,7 +19134,53 @@ function assertNumber(value, name) {
|
|
|
19072
19134
|
const VALID_APPROVAL_MODES = /* @__PURE__ */ new Set(["auto", "confirm-dangerous", "manual"]);
|
|
19073
19135
|
function registerIpcHandlers(windowState, runtime2) {
|
|
19074
19136
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
19137
|
+
let sidebarResizeRecoveryTimer = null;
|
|
19138
|
+
let sidebarResizeActive = false;
|
|
19139
|
+
let runtimeUpdateTimer = null;
|
|
19140
|
+
let pendingRuntimeState = null;
|
|
19075
19141
|
const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
|
|
19142
|
+
const clearSidebarResizeRecoveryTimer = () => {
|
|
19143
|
+
if (sidebarResizeRecoveryTimer) {
|
|
19144
|
+
clearTimeout(sidebarResizeRecoveryTimer);
|
|
19145
|
+
sidebarResizeRecoveryTimer = null;
|
|
19146
|
+
}
|
|
19147
|
+
};
|
|
19148
|
+
const restoreSidebarLayoutAfterResize = () => {
|
|
19149
|
+
clearSidebarResizeRecoveryTimer();
|
|
19150
|
+
if (!sidebarResizeActive) return;
|
|
19151
|
+
sidebarResizeActive = false;
|
|
19152
|
+
layoutViews(windowState);
|
|
19153
|
+
};
|
|
19154
|
+
const scheduleSidebarResizeRecovery = () => {
|
|
19155
|
+
clearSidebarResizeRecoveryTimer();
|
|
19156
|
+
sidebarResizeRecoveryTimer = setTimeout(() => {
|
|
19157
|
+
restoreSidebarLayoutAfterResize();
|
|
19158
|
+
}, 1200);
|
|
19159
|
+
};
|
|
19160
|
+
const flushRuntimeUpdate = () => {
|
|
19161
|
+
runtimeUpdateTimer = null;
|
|
19162
|
+
if (!pendingRuntimeState) return;
|
|
19163
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
19164
|
+
chromeView.webContents.send(
|
|
19165
|
+
Channels.AGENT_RUNTIME_UPDATE,
|
|
19166
|
+
pendingRuntimeState
|
|
19167
|
+
);
|
|
19168
|
+
}
|
|
19169
|
+
if (!sidebarView.webContents.isDestroyed()) {
|
|
19170
|
+
sidebarView.webContents.send(
|
|
19171
|
+
Channels.AGENT_RUNTIME_UPDATE,
|
|
19172
|
+
pendingRuntimeState
|
|
19173
|
+
);
|
|
19174
|
+
}
|
|
19175
|
+
pendingRuntimeState = null;
|
|
19176
|
+
};
|
|
19177
|
+
const scheduleRuntimeUpdate = (state2) => {
|
|
19178
|
+
pendingRuntimeState = state2;
|
|
19179
|
+
if (runtimeUpdateTimer) return;
|
|
19180
|
+
runtimeUpdateTimer = setTimeout(() => {
|
|
19181
|
+
flushRuntimeUpdate();
|
|
19182
|
+
}, 32);
|
|
19183
|
+
};
|
|
19076
19184
|
const sendToRendererViews = (channel, ...args) => {
|
|
19077
19185
|
chromeView.webContents.send(channel, ...args);
|
|
19078
19186
|
sidebarView.webContents.send(channel, ...args);
|
|
@@ -19159,7 +19267,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
19159
19267
|
sendToRendererViews(Channels.HIGHLIGHT_COUNT_UPDATE, count);
|
|
19160
19268
|
};
|
|
19161
19269
|
runtime2.setUpdateListener((state2) => {
|
|
19162
|
-
|
|
19270
|
+
scheduleRuntimeUpdate(state2);
|
|
19163
19271
|
});
|
|
19164
19272
|
onRuntimeHealthChange((health) => {
|
|
19165
19273
|
sendToRendererViews(Channels.SETTINGS_HEALTH_UPDATE, health);
|
|
@@ -19287,17 +19395,23 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
19287
19395
|
};
|
|
19288
19396
|
});
|
|
19289
19397
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_START, () => {
|
|
19398
|
+
sidebarResizeActive = true;
|
|
19399
|
+
clearSidebarResizeRecoveryTimer();
|
|
19290
19400
|
const [width, height] = windowState.mainWindow.getContentSize();
|
|
19291
19401
|
windowState.sidebarView.setBounds({ x: 0, y: 0, width, height });
|
|
19402
|
+
scheduleSidebarResizeRecovery();
|
|
19292
19403
|
});
|
|
19293
19404
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (_, width) => {
|
|
19294
19405
|
assertNumber(width, "width");
|
|
19295
19406
|
const clamped = Math.max(240, Math.min(800, Math.round(width)));
|
|
19296
19407
|
windowState.uiState.sidebarWidth = clamped;
|
|
19297
19408
|
resizeSidebarViews(windowState);
|
|
19409
|
+
scheduleSidebarResizeRecovery();
|
|
19298
19410
|
return clamped;
|
|
19299
19411
|
});
|
|
19300
19412
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, () => {
|
|
19413
|
+
sidebarResizeActive = false;
|
|
19414
|
+
clearSidebarResizeRecoveryTimer();
|
|
19301
19415
|
setSetting("sidebarWidth", windowState.uiState.sidebarWidth);
|
|
19302
19416
|
layoutViews(windowState);
|
|
19303
19417
|
});
|
|
@@ -19389,9 +19503,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
19389
19503
|
trackBookmarkAction("remove");
|
|
19390
19504
|
return removeBookmark(id);
|
|
19391
19505
|
});
|
|
19392
|
-
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (_, id) => {
|
|
19506
|
+
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (_, id, deleteContents) => {
|
|
19393
19507
|
trackBookmarkAction("folder_remove");
|
|
19394
|
-
return removeFolder(id);
|
|
19508
|
+
return removeFolder(id, deleteContents ?? false);
|
|
19395
19509
|
});
|
|
19396
19510
|
electron.ipcMain.handle(
|
|
19397
19511
|
Channels.FOLDER_RENAME,
|
|
@@ -3195,6 +3195,25 @@ function interactByIndex(index, action, value) {
|
|
|
3195
3195
|
if (action === "click") {
|
|
3196
3196
|
el.focus();
|
|
3197
3197
|
el.click();
|
|
3198
|
+
if (el instanceof HTMLInputElement) {
|
|
3199
|
+
if (el.type === "checkbox") {
|
|
3200
|
+
const label = getInputLabel(el) || el.getAttribute("aria-label") || el.name || "checkbox";
|
|
3201
|
+
return `${el.checked ? "Checked" : "Unchecked"}: ${label}`;
|
|
3202
|
+
}
|
|
3203
|
+
if (el.type === "radio") {
|
|
3204
|
+
const label = getTrimmedText(el.value) || getInputLabel(el) || el.getAttribute("aria-label") || el.name || "radio";
|
|
3205
|
+
return `${el.checked ? "Selected" : "Clicked"}: ${label}`;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
const role = el.getAttribute("role");
|
|
3209
|
+
if (role === "checkbox" || role === "radio") {
|
|
3210
|
+
const label = getTrimmedText(el.getAttribute("aria-label")) || getTrimmedText(el.textContent) || el.tagName.toLowerCase();
|
|
3211
|
+
const ariaChecked = el.getAttribute("aria-checked");
|
|
3212
|
+
if (role === "checkbox") {
|
|
3213
|
+
return `${ariaChecked === "true" ? "Checked" : "Unchecked"}: ${label}`;
|
|
3214
|
+
}
|
|
3215
|
+
return `${ariaChecked === "true" ? "Selected" : "Clicked"}: ${label}`;
|
|
3216
|
+
}
|
|
3198
3217
|
return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
|
|
3199
3218
|
}
|
|
3200
3219
|
if (action === "focus") {
|
package/out/preload/index.js
CHANGED
|
@@ -238,7 +238,7 @@ const api = {
|
|
|
238
238
|
removeBookmark: (id) => electron.ipcRenderer.invoke(Channels.BOOKMARK_REMOVE, id),
|
|
239
239
|
createFolder: (name) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name),
|
|
240
240
|
createFolderWithSummary: (name, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name, summary),
|
|
241
|
-
removeFolder: (id) => electron.ipcRenderer.invoke(Channels.FOLDER_REMOVE, id),
|
|
241
|
+
removeFolder: (id, deleteContents) => electron.ipcRenderer.invoke(Channels.FOLDER_REMOVE, id, deleteContents),
|
|
242
242
|
renameFolder: (id, newName, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_RENAME, id, newName, summary),
|
|
243
243
|
onAddContextToChat: (cb) => {
|
|
244
244
|
const handler = (_, bookmarkId) => cb(bookmarkId);
|