@quanta-intellect/vessel-browser 0.1.84 → 0.1.88

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 CHANGED
@@ -182,6 +182,14 @@ The packaged AppImage path:
182
182
  - uses the packaged Vessel app icon and metadata
183
183
  - is the recommended path for early adopters who just want to run Vessel
184
184
 
185
+ Windows packaged releases:
186
+
187
+ - use the `Vessel-<version>-x64-setup.exe` NSIS installer
188
+ - can be installed over an existing Vessel install when upgrading
189
+ - preserve Vessel app data during the normal upgrade path
190
+
191
+ You do not need to uninstall Vessel before installing a newer Windows release. Uninstall first only if you are recovering from a broken install or intentionally removing local Vessel data.
192
+
185
193
  After install:
186
194
 
187
195
  ```bash
package/out/main/index.js CHANGED
@@ -616,7 +616,7 @@ class Tab {
616
616
  var el = document.elementFromPoint(${x}, ${y});
617
617
  while (el) {
618
618
  if (el.hasAttribute && el.hasAttribute('data-vessel-highlight')) {
619
- return el.textContent || '';
619
+ return el.getAttribute('data-vessel-highlight-text') || el.textContent || '';
620
620
  }
621
621
  el = el.parentElement;
622
622
  }
@@ -931,6 +931,7 @@ class Tab {
931
931
  mark.style.setProperty('background', 'rgba(240, 198, 54, 0.3)', 'important');
932
932
  mark.style.setProperty('border-bottom-color', '#f0c636', 'important');
933
933
  mark.setAttribute('data-vessel-highlight', 'true');
934
+ mark.setAttribute('data-vessel-highlight-text', text);
934
935
  range.surroundContents(mark);
935
936
  } else {
936
937
  // For cross-node selections, extract and wrap in a mark
@@ -939,6 +940,7 @@ class Tab {
939
940
  mark.style.setProperty('background', 'rgba(240, 198, 54, 0.3)', 'important');
940
941
  mark.style.setProperty('border-bottom-color', '#f0c636', 'important');
941
942
  mark.setAttribute('data-vessel-highlight', 'true');
943
+ mark.setAttribute('data-vessel-highlight-text', text);
942
944
  var contents = range.extractContents();
943
945
  mark.appendChild(contents);
944
946
  range.insertNode(mark);
@@ -1215,6 +1217,134 @@ for (var cr = 0; cr < contentRoots.length; cr++) {
1215
1217
  if (contentRoot) break;
1216
1218
  }`;
1217
1219
  const NAV_ANCESTORS_JS = `var NAV_ANCESTORS = 'nav, aside, footer, header, [role="navigation"], [role="complementary"], .sidebar, .navbox, .infobox, figcaption, .thumbcaption, .mw-jump-link';`;
1220
+ const TEXT_MATCH_HELPERS_JS = `
1221
+ function normalizeHighlightSearchText(value) {
1222
+ return String(value || '').replace(/\\s+/g, ' ').trim().toLowerCase();
1223
+ }
1224
+
1225
+ function isHighlightableTextNode(n) {
1226
+ var p = n.parentElement;
1227
+ if (!p) return false;
1228
+ if (SKIP_TAGS[p.tagName]) return false;
1229
+ if (p.closest('[data-vessel-highlight]')) return false;
1230
+ if (p.closest(NAV_ANCESTORS)) return false;
1231
+ var style = window.getComputedStyle(p);
1232
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
1233
+ if (p.offsetWidth === 0 && p.offsetHeight === 0) return false;
1234
+ return true;
1235
+ }
1236
+
1237
+ function collectTextRangeMatches(root, searchText, limit) {
1238
+ var normalizedSearch = normalizeHighlightSearchText(searchText);
1239
+ if (!root || !normalizedSearch) return [];
1240
+ var normalized = '';
1241
+ var map = [];
1242
+ var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
1243
+ acceptNode: function(n) {
1244
+ return isHighlightableTextNode(n)
1245
+ ? NodeFilter.FILTER_ACCEPT
1246
+ : NodeFilter.FILTER_REJECT;
1247
+ }
1248
+ });
1249
+ var n;
1250
+ while ((n = w.nextNode())) {
1251
+ var text = n.textContent || '';
1252
+ for (var i = 0; i < text.length; i++) {
1253
+ var ch = text.charAt(i);
1254
+ if (/\\s/.test(ch)) {
1255
+ if (normalized.length === 0 || normalized.charAt(normalized.length - 1) === ' ') {
1256
+ continue;
1257
+ }
1258
+ normalized += ' ';
1259
+ } else {
1260
+ normalized += ch.toLowerCase();
1261
+ }
1262
+ map.push({ node: n, offset: i });
1263
+ }
1264
+ }
1265
+ var matches = [];
1266
+ var from = 0;
1267
+ while (matches.length < limit) {
1268
+ var idx = normalized.indexOf(normalizedSearch, from);
1269
+ if (idx === -1) break;
1270
+ var start = map[idx];
1271
+ var end = map[idx + normalizedSearch.length - 1];
1272
+ if (start && end) {
1273
+ matches.push({
1274
+ startNode: start.node,
1275
+ startOffset: start.offset,
1276
+ endNode: end.node,
1277
+ endOffset: end.offset + 1,
1278
+ });
1279
+ }
1280
+ from = idx + Math.max(1, normalizedSearch.length);
1281
+ }
1282
+ return matches;
1283
+ }
1284
+
1285
+ function collectTextSegmentsForMatch(match) {
1286
+ var segments = [];
1287
+ var inRange = false;
1288
+ var w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
1289
+ acceptNode: function(n) {
1290
+ return isHighlightableTextNode(n) || n === match.startNode || n === match.endNode
1291
+ ? NodeFilter.FILTER_ACCEPT
1292
+ : NodeFilter.FILTER_REJECT;
1293
+ }
1294
+ });
1295
+ var n;
1296
+ while ((n = w.nextNode())) {
1297
+ if (n === match.startNode) inRange = true;
1298
+ if (!inRange) continue;
1299
+ segments.push({
1300
+ node: n,
1301
+ start: n === match.startNode ? match.startOffset : 0,
1302
+ end: n === match.endNode ? match.endOffset : (n.textContent || '').length,
1303
+ });
1304
+ if (n === match.endNode) break;
1305
+ }
1306
+ return segments;
1307
+ }
1308
+
1309
+ function markTextSegment(segment, solidColor, bgColor, fullText) {
1310
+ if (!segment.node || segment.end <= segment.start) return null;
1311
+ var parent = segment.node.parentNode;
1312
+ if (!parent) return null;
1313
+ var text = segment.node.textContent || '';
1314
+ var selected = text.slice(segment.start, segment.end);
1315
+ if (!selected) return null;
1316
+ var mark = document.createElement('mark');
1317
+ mark.className = '__vessel-highlight-text';
1318
+ mark.style.setProperty('background', bgColor, 'important');
1319
+ mark.style.setProperty('border-bottom-color', solidColor, 'important');
1320
+ mark.setAttribute('data-vessel-highlight', 'true');
1321
+ if (fullText) {
1322
+ mark.setAttribute('data-vessel-highlight-text', fullText);
1323
+ }
1324
+ mark.textContent = selected;
1325
+ if (segment.start > 0) {
1326
+ parent.insertBefore(document.createTextNode(text.slice(0, segment.start)), segment.node);
1327
+ }
1328
+ parent.insertBefore(mark, segment.node);
1329
+ if (segment.end < text.length) {
1330
+ parent.insertBefore(document.createTextNode(text.slice(segment.end)), segment.node);
1331
+ }
1332
+ parent.removeChild(segment.node);
1333
+ return mark;
1334
+ }
1335
+
1336
+ function applyTextRangeMatch(match, solidColor, bgColor, fullText) {
1337
+ var segments = collectTextSegmentsForMatch(match);
1338
+ var marks = [];
1339
+ for (var i = segments.length - 1; i >= 0; i--) {
1340
+ try {
1341
+ var mark = markTextSegment(segments[i], solidColor, bgColor, fullText);
1342
+ if (mark) marks.unshift(mark);
1343
+ } catch (_e) {}
1344
+ }
1345
+ return marks;
1346
+ }
1347
+ `;
1218
1348
  const HIGHLIGHT_COLORS = {
1219
1349
  yellow: {
1220
1350
  solid: "#f0c636",
@@ -1299,8 +1429,7 @@ const VESSEL_HIGHLIGHT_CSS = `
1299
1429
  opacity: 1;
1300
1430
  }
1301
1431
  `;
1302
- async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, color) {
1303
- const c = resolveColor(color);
1432
+ async function ensureHighlightStyles(wc) {
1304
1433
  await wc.executeJavaScript(`
1305
1434
  (function() {
1306
1435
  if (!document.getElementById('__vessel-highlight-styles')) {
@@ -1309,6 +1438,14 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
1309
1438
  s.textContent = ${JSON.stringify(VESSEL_HIGHLIGHT_CSS)};
1310
1439
  document.head.appendChild(s);
1311
1440
  }
1441
+ })()
1442
+ `);
1443
+ }
1444
+ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, color) {
1445
+ const c = resolveColor(color);
1446
+ await ensureHighlightStyles(wc);
1447
+ await wc.executeJavaScript(`
1448
+ (function() {
1312
1449
  if (!window.__vesselHighlightLabelManager) {
1313
1450
  var overlap = function(a, b) {
1314
1451
  return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
@@ -1438,7 +1575,6 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
1438
1575
  return wc.executeJavaScript(`
1439
1576
  (function() {
1440
1577
  var searchText = (${JSON.stringify(text)} || '').trim();
1441
- var foldedSearchText = searchText.toLowerCase();
1442
1578
  var solidColor = ${JSON.stringify(c.solid)};
1443
1579
  var bgColor = ${JSON.stringify(c.bg)};
1444
1580
  var labelBg = ${JSON.stringify(c.label)};
@@ -1450,60 +1586,22 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
1450
1586
  ${SKIP_TAGS_JS}
1451
1587
  ${CONTENT_ROOTS_JS}
1452
1588
  ${NAV_ANCESTORS_JS}
1453
-
1454
- function collectMatches(root, limit) {
1455
- var matches = [];
1456
- var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
1457
- acceptNode: function(n) {
1458
- var p = n.parentElement;
1459
- if (!p) return NodeFilter.FILTER_REJECT;
1460
- if (SKIP_TAGS[p.tagName]) return NodeFilter.FILTER_REJECT;
1461
- if (p.closest('[data-vessel-highlight]')) return NodeFilter.FILTER_REJECT;
1462
- if (p.closest(NAV_ANCESTORS)) return NodeFilter.FILTER_REJECT;
1463
- var style = window.getComputedStyle(p);
1464
- if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return NodeFilter.FILTER_REJECT;
1465
- if (p.offsetWidth === 0 && p.offsetHeight === 0) return NodeFilter.FILTER_REJECT;
1466
- return NodeFilter.FILTER_ACCEPT;
1467
- }
1468
- });
1469
- var n;
1470
- while ((n = w.nextNode())) {
1471
- var haystack = n.textContent || '';
1472
- var idx = haystack.indexOf(searchText);
1473
- if (idx === -1 && foldedSearchText) {
1474
- idx = haystack.toLowerCase().indexOf(foldedSearchText);
1475
- }
1476
- if (idx !== -1) {
1477
- matches.push({ node: n, idx: idx });
1478
- if (matches.length >= limit) break;
1479
- }
1480
- }
1481
- return matches;
1482
- }
1589
+ ${TEXT_MATCH_HELPERS_JS}
1483
1590
 
1484
1591
  // First try: match inside main content area (skip nav/sidebar/captions)
1485
- var textNodes = contentRoot ? collectMatches(contentRoot, 20) : [];
1592
+ var textMatches = contentRoot ? collectTextRangeMatches(contentRoot, searchText, 20) : [];
1486
1593
  // Fallback: if no matches in content area, search the whole body
1487
- if (textNodes.length === 0) {
1488
- textNodes = collectMatches(document.body, 20);
1594
+ if (textMatches.length === 0) {
1595
+ textMatches = collectTextRangeMatches(document.body, searchText, 20);
1489
1596
  }
1490
1597
  var count = 0;
1491
1598
  var firstMark = null;
1492
- for (var i = 0; i < textNodes.length; i++) {
1493
- var match = textNodes[i];
1494
- try {
1495
- var range = document.createRange();
1496
- range.setStart(match.node, match.idx);
1497
- range.setEnd(match.node, match.idx + searchText.length);
1498
- var mark = document.createElement('mark');
1499
- mark.className = '__vessel-highlight-text';
1500
- mark.style.setProperty('background', bgColor, 'important');
1501
- mark.style.setProperty('border-bottom-color', solidColor, 'important');
1502
- mark.setAttribute('data-vessel-highlight', 'true');
1503
- range.surroundContents(mark);
1504
- if (!firstMark) firstMark = mark;
1599
+ for (var i = textMatches.length - 1; i >= 0; i--) {
1600
+ var marks = applyTextRangeMatch(textMatches[i], solidColor, bgColor, searchText);
1601
+ if (marks.length > 0) {
1602
+ firstMark = marks[0];
1505
1603
  count++;
1506
- } catch (_e) {}
1604
+ }
1507
1605
  }
1508
1606
  if (count === 0) return 'Text not found: ' + searchText.slice(0, 80);
1509
1607
  if (firstMark) firstMark.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -1552,72 +1650,26 @@ async function highlightBatchOnPage(wc, entries) {
1552
1650
  color: resolveColor(e.color)
1553
1651
  }));
1554
1652
  if (serialized.length === 0) return;
1653
+ await ensureHighlightStyles(wc);
1555
1654
  await wc.executeJavaScript(`
1556
1655
  (function() {
1557
- if (!document.getElementById('__vessel-highlight-styles')) {
1558
- var s = document.createElement('style');
1559
- s.id = '__vessel-highlight-styles';
1560
- s.textContent = ${JSON.stringify(VESSEL_HIGHLIGHT_CSS)};
1561
- document.head.appendChild(s);
1562
- }
1563
1656
  var entries = ${JSON.stringify(serialized)};
1564
1657
  ${SKIP_TAGS_JS}
1565
1658
  ${CONTENT_ROOTS_JS}
1566
1659
  ${NAV_ANCESTORS_JS}
1567
-
1568
- function collectMatches(root, searchText, foldedSearchText, limit) {
1569
- var matches = [];
1570
- var w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
1571
- acceptNode: function(n) {
1572
- var p = n.parentElement;
1573
- if (!p) return NodeFilter.FILTER_REJECT;
1574
- if (SKIP_TAGS[p.tagName]) return NodeFilter.FILTER_REJECT;
1575
- if (p.closest('[data-vessel-highlight]')) return NodeFilter.FILTER_REJECT;
1576
- if (p.closest(NAV_ANCESTORS)) return NodeFilter.FILTER_REJECT;
1577
- var style = window.getComputedStyle(p);
1578
- if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return NodeFilter.FILTER_REJECT;
1579
- if (p.offsetWidth === 0 && p.offsetHeight === 0) return NodeFilter.FILTER_REJECT;
1580
- return NodeFilter.FILTER_ACCEPT;
1581
- }
1582
- });
1583
- var n;
1584
- while ((n = w.nextNode())) {
1585
- var haystack = n.textContent || '';
1586
- var idx = haystack.indexOf(searchText);
1587
- if (idx === -1 && foldedSearchText) {
1588
- idx = haystack.toLowerCase().indexOf(foldedSearchText);
1589
- }
1590
- if (idx !== -1) {
1591
- matches.push({ node: n, idx: idx });
1592
- if (matches.length >= limit) break;
1593
- }
1594
- }
1595
- return matches;
1596
- }
1660
+ ${TEXT_MATCH_HELPERS_JS}
1597
1661
 
1598
1662
  for (var e = 0; e < entries.length; e++) {
1599
1663
  var entry = entries[e];
1600
1664
  var c = entry.color;
1601
1665
  if (entry.text) {
1602
1666
  var searchText = (entry.text || '').trim();
1603
- var foldedSearchText = searchText.toLowerCase();
1604
- var textNodes = contentRoot ? collectMatches(contentRoot, searchText, foldedSearchText, 20) : [];
1605
- if (textNodes.length === 0) {
1606
- textNodes = collectMatches(document.body, searchText, foldedSearchText, 20);
1667
+ var textMatches = contentRoot ? collectTextRangeMatches(contentRoot, searchText, 20) : [];
1668
+ if (textMatches.length === 0) {
1669
+ textMatches = collectTextRangeMatches(document.body, searchText, 20);
1607
1670
  }
1608
- for (var i = 0; i < textNodes.length; i++) {
1609
- try {
1610
- var match = textNodes[i];
1611
- var range = document.createRange();
1612
- range.setStart(match.node, match.idx);
1613
- range.setEnd(match.node, match.idx + searchText.length);
1614
- var mark = document.createElement('mark');
1615
- mark.className = '__vessel-highlight-text';
1616
- mark.style.setProperty('background', c.bg, 'important');
1617
- mark.style.setProperty('border-bottom-color', c.solid, 'important');
1618
- mark.setAttribute('data-vessel-highlight', 'true');
1619
- range.surroundContents(mark);
1620
- } catch (_e) {}
1671
+ for (var i = textMatches.length - 1; i >= 0; i--) {
1672
+ applyTextRangeMatch(textMatches[i], c.solid, c.bg, searchText);
1621
1673
  }
1622
1674
  } else if (entry.selector) {
1623
1675
  try {
@@ -1704,6 +1756,8 @@ async function clearAllHighlightElements(wc) {
1704
1756
  el.style.removeProperty('outline');
1705
1757
  el.style.removeProperty('outline-offset');
1706
1758
  });
1759
+ var style = document.getElementById('__vessel-highlight-styles');
1760
+ if (style) style.remove();
1707
1761
  return true;
1708
1762
  })()
1709
1763
  `);
@@ -3210,7 +3264,8 @@ class TabManager {
3210
3264
  `(function() {
3211
3265
  var marks = document.querySelectorAll('mark.__vessel-highlight-text[data-vessel-highlight]');
3212
3266
  marks.forEach(function(m) {
3213
- if (m.textContent === ${JSON.stringify(text)}) {
3267
+ var highlightText = m.getAttribute('data-vessel-highlight-text') || m.textContent;
3268
+ if (highlightText === ${JSON.stringify(text)}) {
3214
3269
  var parent = m.parentNode;
3215
3270
  while (m.firstChild) parent.insertBefore(m.firstChild, m);
3216
3271
  m.remove();
@@ -12311,7 +12366,10 @@ async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
12311
12366
  const pageHighlights = Array.from(
12312
12367
  document.querySelectorAll("mark.__vessel-highlight-text[data-vessel-highlight]")
12313
12368
  ).map((mark) => {
12314
- const text = mark.textContent?.trim() || "";
12369
+ const text =
12370
+ mark.getAttribute("data-vessel-highlight-text")?.trim() ||
12371
+ mark.textContent?.trim() ||
12372
+ "";
12315
12373
  const style = window.getComputedStyle(mark);
12316
12374
  return {
12317
12375
  text,
@@ -12347,20 +12405,28 @@ function formatLiveSelectionSection(snapshot) {
12347
12405
  const sections = [];
12348
12406
  if (snapshot.activeSelection) {
12349
12407
  const preview = snapshot.activeSelection.length > 400 ? `${snapshot.activeSelection.slice(0, 397)}...` : snapshot.activeSelection;
12350
- sections.push(`## Active User Selection
12351
- "${preview}"`);
12408
+ sections.push(
12409
+ `## Active User Selection
12410
+ The user currently has this text selected on screen:
12411
+ - "${preview}"`
12412
+ );
12352
12413
  }
12353
- const unsavedHighlights = snapshot.pageHighlights.filter(
12354
- (highlight) => !highlight.persisted
12355
- );
12356
- if (unsavedHighlights.length > 0) {
12357
- const lines = unsavedHighlights.map((highlight) => {
12414
+ if (snapshot.pageHighlights.length > 0) {
12415
+ const lines = snapshot.pageHighlights.map((highlight) => {
12358
12416
  const preview = highlight.text.length > 180 ? `${highlight.text.slice(0, 177)}...` : highlight.text;
12359
- const color = highlight.color ? ` (${highlight.color})` : "";
12360
- return `- "${preview}"${color}`;
12417
+ const details = [
12418
+ highlight.persisted ? "saved" : "visible only",
12419
+ highlight.color ? `color: ${highlight.color}` : ""
12420
+ ].filter(Boolean);
12421
+ return `- "${preview}"${details.length ? ` (${details.join(", ")})` : ""}`;
12361
12422
  });
12362
- sections.push(`## Unsaved Visible Highlights
12363
- ${lines.join("\n")}`);
12423
+ sections.push(
12424
+ [
12425
+ "## Visible Highlights On Screen",
12426
+ "These are the highlighted passages currently visible in the page. Treat this as authoritative before searching or inspecting:",
12427
+ lines.join("\n")
12428
+ ].join("\n")
12429
+ );
12364
12430
  }
12365
12431
  return sections.length > 0 ? sections.join("\n\n") : null;
12366
12432
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quanta-intellect/vessel-browser",
3
3
  "mcpName": "io.github.unmodeled-tyler/vessel-browser",
4
- "version": "0.1.84",
4
+ "version": "0.1.88",
5
5
  "description": "AI-native web browser runtime for autonomous agents with human supervision",
6
6
  "main": "./out/main/index.js",
7
7
  "bin": {