@quanta-intellect/vessel-browser 0.1.10 → 0.1.11

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.
@@ -2126,7 +2126,34 @@ function generateStableSelector(el) {
2126
2126
  let elementIndex = 0;
2127
2127
  const elementSelectors = {};
2128
2128
  let indexedElements = /* @__PURE__ */ new WeakMap();
2129
+ const indexedElementRefs = {};
2129
2130
  let activeOverlays = [];
2131
+ function deepQuerySelectorAll(selector, root = document) {
2132
+ const results = [];
2133
+ root.querySelectorAll(selector).forEach((el) => results.push(el));
2134
+ const MAX_SHADOW_HOSTS = 50;
2135
+ const MAX_DEPTH = 3;
2136
+ const shadowRoots = [];
2137
+ const findShadowRoots = (node, depth) => {
2138
+ if (depth > MAX_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS) return;
2139
+ const children = node.children;
2140
+ for (let i = 0; i < children.length && shadowRoots.length < MAX_SHADOW_HOSTS; i++) {
2141
+ const el = children[i];
2142
+ if (el.shadowRoot) {
2143
+ shadowRoots.push(el.shadowRoot);
2144
+ findShadowRoots(el.shadowRoot, depth + 1);
2145
+ }
2146
+ if (el.children.length > 0 && el.children.length < 200) {
2147
+ findShadowRoots(el, depth);
2148
+ }
2149
+ }
2150
+ };
2151
+ findShadowRoots(root, 0);
2152
+ for (const sr of shadowRoots) {
2153
+ sr.querySelectorAll(selector).forEach((el) => results.push(el));
2154
+ }
2155
+ return results;
2156
+ }
2130
2157
  function generateSelector(el) {
2131
2158
  return generateStableSelector(el);
2132
2159
  }
@@ -2135,6 +2162,7 @@ function assignIndex(el) {
2135
2162
  if (existing != null) return existing;
2136
2163
  elementIndex += 1;
2137
2164
  elementSelectors[elementIndex] = generateSelector(el);
2165
+ indexedElementRefs[elementIndex] = el;
2138
2166
  indexedElements.set(el, elementIndex);
2139
2167
  return elementIndex;
2140
2168
  }
@@ -2475,7 +2503,7 @@ function buildBaseMetadata(el) {
2475
2503
  };
2476
2504
  }
2477
2505
  function extractHeadings() {
2478
- return Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")).map((el) => {
2506
+ return deepQuerySelectorAll("h1, h2, h3, h4, h5, h6").map((el) => {
2479
2507
  const text = el.textContent?.trim() || "";
2480
2508
  if (!text) return null;
2481
2509
  return {
@@ -2486,10 +2514,10 @@ function extractHeadings() {
2486
2514
  }
2487
2515
  function extractNavigation() {
2488
2516
  const navigation = [];
2489
- document.querySelectorAll(
2517
+ deepQuerySelectorAll(
2490
2518
  'nav, [role="navigation"], header nav, [role="banner"] nav'
2491
2519
  ).forEach((nav) => {
2492
- nav.querySelectorAll("a[href]").forEach((link) => {
2520
+ deepQuerySelectorAll("a[href]", nav).forEach((link) => {
2493
2521
  const anchor = link;
2494
2522
  const text = anchor.textContent?.trim();
2495
2523
  if (!text || anchor.getAttribute("href")?.startsWith("#")) return;
@@ -2534,7 +2562,7 @@ function getFieldMetadata(el) {
2534
2562
  }
2535
2563
  function extractInteractiveElements() {
2536
2564
  const elements = [];
2537
- document.querySelectorAll(
2565
+ deepQuerySelectorAll(
2538
2566
  'button, [role="button"], input[type="submit"], input[type="button"]'
2539
2567
  ).forEach((btn) => {
2540
2568
  const input = btn;
@@ -2545,7 +2573,7 @@ function extractInteractiveElements() {
2545
2573
  ...buildBaseMetadata(btn)
2546
2574
  });
2547
2575
  });
2548
- document.querySelectorAll("a[href]").forEach((link) => {
2576
+ deepQuerySelectorAll("a[href]").forEach((link) => {
2549
2577
  const anchor = link;
2550
2578
  const text = anchor.textContent?.trim();
2551
2579
  if (!text || anchor.getAttribute("href")?.startsWith("#")) return;
@@ -2559,7 +2587,7 @@ function extractInteractiveElements() {
2559
2587
  context
2560
2588
  });
2561
2589
  });
2562
- document.querySelectorAll(
2590
+ deepQuerySelectorAll(
2563
2591
  'input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea'
2564
2592
  ).forEach((input) => {
2565
2593
  const element = input;
@@ -2587,7 +2615,8 @@ function extractForms() {
2587
2615
  }
2588
2616
  return el instanceof HTMLInputElement && (el.type === "submit" || el.type === "image") && el.form === form;
2589
2617
  }
2590
- document.querySelectorAll("form").forEach((form) => {
2618
+ deepQuerySelectorAll("form").forEach((formEl) => {
2619
+ const form = formEl;
2591
2620
  const fields = [];
2592
2621
  form.querySelectorAll(
2593
2622
  "input:not([type='hidden']):not([type='submit']):not([type='button']):not([type='image']), select, textarea"
@@ -2784,9 +2813,12 @@ function getVisiblePageText() {
2784
2813
  function vesselExtractContent() {
2785
2814
  const extractStructuredContent = (article) => {
2786
2815
  activeOverlays = detectOverlays();
2816
+ const readabilityText = article?.textContent || "";
2817
+ const visibleText = getVisiblePageText();
2818
+ const content = readabilityText.length > visibleText.length * 0.3 ? readabilityText : visibleText;
2787
2819
  return {
2788
2820
  title: article?.title || document.title,
2789
- content: article?.textContent || getVisiblePageText(),
2821
+ content,
2790
2822
  htmlContent: article?.content || "",
2791
2823
  byline: article?.byline || "",
2792
2824
  excerpt: article?.excerpt || "",
@@ -2813,6 +2845,9 @@ function vesselExtractContent() {
2813
2845
  Object.keys(elementSelectors).forEach(
2814
2846
  (key) => delete elementSelectors[key]
2815
2847
  );
2848
+ Object.keys(indexedElementRefs).forEach(
2849
+ (key) => delete indexedElementRefs[key]
2850
+ );
2816
2851
  const documentClone = document.cloneNode(true);
2817
2852
  const reader = new readabilityExports.Readability(documentClone);
2818
2853
  const article = reader.parse();
@@ -2825,9 +2860,42 @@ function vesselExtractContent() {
2825
2860
  function resolveElementSelector(index) {
2826
2861
  return elementSelectors[index] || null;
2827
2862
  }
2863
+ function interactByIndex(index, action, value) {
2864
+ const el = indexedElementRefs[index];
2865
+ if (!el || !(el instanceof HTMLElement)) {
2866
+ return "Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.";
2867
+ }
2868
+ if (action === "click") {
2869
+ el.focus();
2870
+ el.click();
2871
+ return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
2872
+ }
2873
+ if (action === "focus") {
2874
+ el.focus();
2875
+ return "Focused: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
2876
+ }
2877
+ if (action === "value" && value != null) {
2878
+ if (!(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement) && !(el instanceof HTMLSelectElement)) {
2879
+ return "Error[not-input]: Element is not a text input";
2880
+ }
2881
+ const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : el instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;
2882
+ const desc = Object.getOwnPropertyDescriptor(proto, "value");
2883
+ if (desc?.set) {
2884
+ desc.set.call(el, value);
2885
+ } else {
2886
+ el.value = value;
2887
+ }
2888
+ el.focus();
2889
+ el.dispatchEvent(new Event("input", { bubbles: true }));
2890
+ el.dispatchEvent(new Event("change", { bubbles: true }));
2891
+ return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
2892
+ }
2893
+ return "Error: Unknown action";
2894
+ }
2828
2895
  electron.contextBridge.exposeInMainWorld("__vessel", {
2829
2896
  extractContent: vesselExtractContent,
2830
2897
  getElementSelector: resolveElementSelector,
2898
+ interactByIndex,
2831
2899
  notifyHighlightSelection: (text) => {
2832
2900
  if (typeof text === "string" && text.trim()) {
2833
2901
  electron.ipcRenderer.send("vessel:highlight-selection", text.trim());