@quanta-intellect/vessel-browser 0.1.84 → 0.1.86

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",
@@ -1438,7 +1568,6 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
1438
1568
  return wc.executeJavaScript(`
1439
1569
  (function() {
1440
1570
  var searchText = (${JSON.stringify(text)} || '').trim();
1441
- var foldedSearchText = searchText.toLowerCase();
1442
1571
  var solidColor = ${JSON.stringify(c.solid)};
1443
1572
  var bgColor = ${JSON.stringify(c.bg)};
1444
1573
  var labelBg = ${JSON.stringify(c.label)};
@@ -1450,60 +1579,22 @@ async function highlightOnPage(wc, resolvedSelector, text, label, durationMs, co
1450
1579
  ${SKIP_TAGS_JS}
1451
1580
  ${CONTENT_ROOTS_JS}
1452
1581
  ${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
- }
1582
+ ${TEXT_MATCH_HELPERS_JS}
1483
1583
 
1484
1584
  // First try: match inside main content area (skip nav/sidebar/captions)
1485
- var textNodes = contentRoot ? collectMatches(contentRoot, 20) : [];
1585
+ var textMatches = contentRoot ? collectTextRangeMatches(contentRoot, searchText, 20) : [];
1486
1586
  // Fallback: if no matches in content area, search the whole body
1487
- if (textNodes.length === 0) {
1488
- textNodes = collectMatches(document.body, 20);
1587
+ if (textMatches.length === 0) {
1588
+ textMatches = collectTextRangeMatches(document.body, searchText, 20);
1489
1589
  }
1490
1590
  var count = 0;
1491
1591
  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;
1592
+ for (var i = textMatches.length - 1; i >= 0; i--) {
1593
+ var marks = applyTextRangeMatch(textMatches[i], solidColor, bgColor, searchText);
1594
+ if (marks.length > 0) {
1595
+ firstMark = marks[0];
1505
1596
  count++;
1506
- } catch (_e) {}
1597
+ }
1507
1598
  }
1508
1599
  if (count === 0) return 'Text not found: ' + searchText.slice(0, 80);
1509
1600
  if (firstMark) firstMark.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -1564,60 +1655,19 @@ async function highlightBatchOnPage(wc, entries) {
1564
1655
  ${SKIP_TAGS_JS}
1565
1656
  ${CONTENT_ROOTS_JS}
1566
1657
  ${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
- }
1658
+ ${TEXT_MATCH_HELPERS_JS}
1597
1659
 
1598
1660
  for (var e = 0; e < entries.length; e++) {
1599
1661
  var entry = entries[e];
1600
1662
  var c = entry.color;
1601
1663
  if (entry.text) {
1602
1664
  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);
1665
+ var textMatches = contentRoot ? collectTextRangeMatches(contentRoot, searchText, 20) : [];
1666
+ if (textMatches.length === 0) {
1667
+ textMatches = collectTextRangeMatches(document.body, searchText, 20);
1607
1668
  }
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) {}
1669
+ for (var i = textMatches.length - 1; i >= 0; i--) {
1670
+ applyTextRangeMatch(textMatches[i], c.solid, c.bg, searchText);
1621
1671
  }
1622
1672
  } else if (entry.selector) {
1623
1673
  try {
@@ -3210,7 +3260,8 @@ class TabManager {
3210
3260
  `(function() {
3211
3261
  var marks = document.querySelectorAll('mark.__vessel-highlight-text[data-vessel-highlight]');
3212
3262
  marks.forEach(function(m) {
3213
- if (m.textContent === ${JSON.stringify(text)}) {
3263
+ var highlightText = m.getAttribute('data-vessel-highlight-text') || m.textContent;
3264
+ if (highlightText === ${JSON.stringify(text)}) {
3214
3265
  var parent = m.parentNode;
3215
3266
  while (m.firstChild) parent.insertBefore(m.firstChild, m);
3216
3267
  m.remove();
@@ -12311,7 +12362,10 @@ async function captureLiveHighlightSnapshot(wc, savedHighlights = []) {
12311
12362
  const pageHighlights = Array.from(
12312
12363
  document.querySelectorAll("mark.__vessel-highlight-text[data-vessel-highlight]")
12313
12364
  ).map((mark) => {
12314
- const text = mark.textContent?.trim() || "";
12365
+ const text =
12366
+ mark.getAttribute("data-vessel-highlight-text")?.trim() ||
12367
+ mark.textContent?.trim() ||
12368
+ "";
12315
12369
  const style = window.getComputedStyle(mark);
12316
12370
  return {
12317
12371
  text,
@@ -12347,20 +12401,28 @@ function formatLiveSelectionSection(snapshot) {
12347
12401
  const sections = [];
12348
12402
  if (snapshot.activeSelection) {
12349
12403
  const preview = snapshot.activeSelection.length > 400 ? `${snapshot.activeSelection.slice(0, 397)}...` : snapshot.activeSelection;
12350
- sections.push(`## Active User Selection
12351
- "${preview}"`);
12404
+ sections.push(
12405
+ `## Active User Selection
12406
+ The user currently has this text selected on screen:
12407
+ - "${preview}"`
12408
+ );
12352
12409
  }
12353
- const unsavedHighlights = snapshot.pageHighlights.filter(
12354
- (highlight) => !highlight.persisted
12355
- );
12356
- if (unsavedHighlights.length > 0) {
12357
- const lines = unsavedHighlights.map((highlight) => {
12410
+ if (snapshot.pageHighlights.length > 0) {
12411
+ const lines = snapshot.pageHighlights.map((highlight) => {
12358
12412
  const preview = highlight.text.length > 180 ? `${highlight.text.slice(0, 177)}...` : highlight.text;
12359
- const color = highlight.color ? ` (${highlight.color})` : "";
12360
- return `- "${preview}"${color}`;
12413
+ const details = [
12414
+ highlight.persisted ? "saved" : "visible only",
12415
+ highlight.color ? `color: ${highlight.color}` : ""
12416
+ ].filter(Boolean);
12417
+ return `- "${preview}"${details.length ? ` (${details.join(", ")})` : ""}`;
12361
12418
  });
12362
- sections.push(`## Unsaved Visible Highlights
12363
- ${lines.join("\n")}`);
12419
+ sections.push(
12420
+ [
12421
+ "## Visible Highlights On Screen",
12422
+ "These are the highlighted passages currently visible in the page. Treat this as authoritative before searching or inspecting:",
12423
+ lines.join("\n")
12424
+ ].join("\n")
12425
+ );
12364
12426
  }
12365
12427
  return sections.length > 0 ? sections.join("\n\n") : null;
12366
12428
  }
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.86",
5
5
  "description": "AI-native web browser runtime for autonomous agents with human supervision",
6
6
  "main": "./out/main/index.js",
7
7
  "bin": {