@quanta-intellect/vessel-browser 0.1.60 → 0.1.63

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
@@ -3903,10 +3903,8 @@ function startBackgroundRevalidation() {
3903
3903
  }, REVALIDATION_INTERVAL_MS);
3904
3904
  }
3905
3905
  function stopBackgroundRevalidation() {
3906
- if (revalidationTimer) {
3907
- clearInterval(revalidationTimer);
3908
- revalidationTimer = null;
3909
- }
3906
+ clearInterval(revalidationTimer);
3907
+ revalidationTimer = null;
3910
3908
  }
3911
3909
  function isPremiumActiveState(state2) {
3912
3910
  return state2.status === "active" || state2.status === "trialing";
@@ -3940,6 +3938,7 @@ let flushTimer = null;
3940
3938
  let sessionStartedAt = null;
3941
3939
  function isEnabled() {
3942
3940
  if (POSTHOG_API_KEY === "YOUR_POSTHOG_KEY_HERE") return false;
3941
+ if (process.env.VESSEL_DEV === "1") return false;
3943
3942
  return loadSettings().telemetryEnabled !== false;
3944
3943
  }
3945
3944
  function trackEvent(event, properties = {}) {
@@ -4011,8 +4010,8 @@ function stopTelemetry() {
4011
4010
  }
4012
4011
  if (flushTimer) {
4013
4012
  clearInterval(flushTimer);
4014
- flushTimer = null;
4015
4013
  }
4014
+ flushTimer = null;
4016
4015
  void flush();
4017
4016
  }
4018
4017
  async function flush() {
@@ -4113,6 +4112,270 @@ function selectorHelpersJS(attributes = DEFAULT_SELECTOR_ATTRIBUTES) {
4113
4112
  "}"
4114
4113
  ].join("\n");
4115
4114
  }
4115
+ function normalizeString(value) {
4116
+ return (value ?? "").trim().toLowerCase();
4117
+ }
4118
+ function mapInputType(el) {
4119
+ const inputType = el.inputType ?? el.type ?? "text";
4120
+ switch (inputType.toLowerCase()) {
4121
+ case "email":
4122
+ return "email";
4123
+ case "password":
4124
+ return "password";
4125
+ case "number":
4126
+ case "range":
4127
+ return "number";
4128
+ case "select-one":
4129
+ case "select":
4130
+ return "select";
4131
+ case "checkbox":
4132
+ case "radio":
4133
+ return "checkbox";
4134
+ case "date":
4135
+ case "datetime-local":
4136
+ case "time":
4137
+ case "month":
4138
+ case "week":
4139
+ return "date";
4140
+ case "file":
4141
+ return "file";
4142
+ default:
4143
+ return "text";
4144
+ }
4145
+ }
4146
+ function mapFormFields(forms, interactiveElements) {
4147
+ const fields = [];
4148
+ const formFieldSelectors = /* @__PURE__ */ new Set();
4149
+ for (const form of forms) {
4150
+ for (const el of form.fields ?? []) {
4151
+ formFieldSelectors.add(el.selector || el.name || el.label || String(el.index));
4152
+ }
4153
+ }
4154
+ for (const el of interactiveElements) {
4155
+ const key = el.selector || el.name || el.label || String(el.index);
4156
+ if (formFieldSelectors.has(key)) {
4157
+ fields.push({
4158
+ name: el.name || el.label || key,
4159
+ type: mapInputType(el),
4160
+ label: el.label,
4161
+ required: el.required,
4162
+ selector: el.selector || ""
4163
+ });
4164
+ }
4165
+ }
4166
+ return fields;
4167
+ }
4168
+ function mapActionButtons(interactiveElements) {
4169
+ const buttons = [];
4170
+ const seen = /* @__PURE__ */ new Set();
4171
+ for (const el of interactiveElements) {
4172
+ if (el.type !== "button" && el.type !== "submit" && el.type !== "reset") continue;
4173
+ const label = (el.label || el.textContent || "").trim();
4174
+ if (!label || seen.has(label)) continue;
4175
+ seen.add(label);
4176
+ let intent;
4177
+ const normalized = normalizeString(label);
4178
+ if (/\b(add to cart|buy now|add to bag|add to basket|shop now)\b/.test(normalized)) {
4179
+ intent = "addToCart";
4180
+ } else if (/\b(login|sign in|log in|signin|log-in)\b/.test(normalized)) {
4181
+ intent = "login";
4182
+ } else if (/\b(submit|send|continue|next|proceed|register|create account|sign up)\b/.test(normalized)) {
4183
+ intent = "submit";
4184
+ } else if (/\b(cancel|back|return|go back|close)\b/.test(normalized)) {
4185
+ intent = "cancel";
4186
+ } else if (/\b(download|export|save as)\b/.test(normalized)) {
4187
+ intent = "download";
4188
+ } else if (/\b(search|find|go|submit search)\b/.test(normalized)) {
4189
+ intent = "search";
4190
+ } else if (el.href || el.url) {
4191
+ intent = "navigate";
4192
+ }
4193
+ if (el.selector || el.name || el.label) {
4194
+ buttons.push({
4195
+ label,
4196
+ selector: el.selector || el.name || el.label,
4197
+ intent
4198
+ });
4199
+ }
4200
+ }
4201
+ return buttons;
4202
+ }
4203
+ function asStructuredObject(value) {
4204
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4205
+ }
4206
+ function stringifyStructuredScalar(value) {
4207
+ if (typeof value === "string") {
4208
+ const trimmed = value.trim();
4209
+ return trimmed || void 0;
4210
+ }
4211
+ if (typeof value === "number" && Number.isFinite(value)) {
4212
+ return String(value);
4213
+ }
4214
+ return void 0;
4215
+ }
4216
+ function firstStructuredString(...values) {
4217
+ for (const value of values) {
4218
+ const normalized = stringifyStructuredScalar(value);
4219
+ if (normalized) return normalized;
4220
+ }
4221
+ return void 0;
4222
+ }
4223
+ function getOfferPrice(offers) {
4224
+ if (Array.isArray(offers)) {
4225
+ for (const offer2 of offers) {
4226
+ const price = getOfferPrice(offer2);
4227
+ if (price) return price;
4228
+ }
4229
+ return void 0;
4230
+ }
4231
+ const offer = asStructuredObject(offers);
4232
+ return firstStructuredString(offer?.price);
4233
+ }
4234
+ function extractPrimaryEntity(pageType, structuredData, metaTags) {
4235
+ if (pageType === "product") {
4236
+ const product = structuredData?.find(
4237
+ (e) => e.types.some((t) => /^product$/i.test(t))
4238
+ );
4239
+ if (product) {
4240
+ const attrs = product.attributes ?? {};
4241
+ const aggregateRating = asStructuredObject(attrs.aggregateRating);
4242
+ return {
4243
+ type: "Product",
4244
+ nameField: firstStructuredString(attrs.name),
4245
+ priceField: firstStructuredString(attrs.price) ?? getOfferPrice(attrs.offers),
4246
+ imageField: firstStructuredString(
4247
+ attrs.image,
4248
+ Array.isArray(attrs.image) ? attrs.image[0] : void 0
4249
+ ),
4250
+ descriptionField: firstStructuredString(attrs.description),
4251
+ reviewsField: firstStructuredString(
4252
+ attrs.reviews,
4253
+ attrs.reviewCount,
4254
+ aggregateRating?.reviewCount,
4255
+ aggregateRating?.ratingCount
4256
+ ),
4257
+ ratingField: firstStructuredString(
4258
+ attrs.rating,
4259
+ attrs.ratingValue,
4260
+ aggregateRating?.ratingValue
4261
+ ),
4262
+ addToCartField: void 0
4263
+ };
4264
+ }
4265
+ }
4266
+ if (pageType === "article") {
4267
+ const article = structuredData?.find(
4268
+ (e) => e.types.some(
4269
+ (t) => /^(article|newsarticle|blogposting|webpage)$/i.test(t)
4270
+ )
4271
+ );
4272
+ if (article) {
4273
+ const attrs = article.attributes ?? {};
4274
+ return {
4275
+ type: article.types[0] ?? "Article",
4276
+ nameField: typeof attrs.headline === "string" ? attrs.headline : typeof attrs.name === "string" ? attrs.name : void 0,
4277
+ descriptionField: typeof attrs.articleBody === "string" ? attrs.articleBody : typeof attrs.description === "string" ? attrs.description : void 0
4278
+ };
4279
+ }
4280
+ }
4281
+ return void 0;
4282
+ }
4283
+ function inferPageSchema(page) {
4284
+ let pageType = "unknown";
4285
+ let confidence = 0.5;
4286
+ const structuredData = page.structuredData;
4287
+ const metaTags = page.metaTags;
4288
+ const url = page.url ?? "";
4289
+ const forms = page.forms ?? [];
4290
+ const interactiveElements = page.interactiveElements ?? [];
4291
+ const urlLower = normalizeString(url);
4292
+ const jsonLdTypes = [];
4293
+ for (const entity of structuredData ?? []) {
4294
+ jsonLdTypes.push(...entity.types);
4295
+ }
4296
+ const hasProduct = jsonLdTypes.some((t) => /^product$/i.test(t));
4297
+ const hasArticle = jsonLdTypes.some(
4298
+ (t) => /^(article|newsarticle|blogposting)$/i.test(t)
4299
+ );
4300
+ const hasEvent = jsonLdTypes.some((t) => /^event$/i.test(t));
4301
+ const hasSearchResults = jsonLdTypes.some((t) => /^searchresultspage$/i.test(t));
4302
+ if (hasProduct) {
4303
+ pageType = "product";
4304
+ confidence += 0.2;
4305
+ } else if (hasArticle) {
4306
+ pageType = "article";
4307
+ confidence += 0.2;
4308
+ } else if (hasEvent) {
4309
+ pageType = "form";
4310
+ confidence += 0.15;
4311
+ } else if (hasSearchResults) {
4312
+ pageType = "search";
4313
+ confidence += 0.2;
4314
+ }
4315
+ const ogType = metaTags?.["og:type"];
4316
+ if (ogType) {
4317
+ const normalized = normalizeString(ogType);
4318
+ if (/^product$/i.test(normalized) && pageType !== "product") {
4319
+ pageType = "product";
4320
+ confidence += 0.15;
4321
+ } else if (/^article|blog|news/i.test(normalized) && pageType !== "article") {
4322
+ pageType = "article";
4323
+ confidence += 0.15;
4324
+ }
4325
+ }
4326
+ if (/\/checkout|\/cart|\/payment|\/billing/i.test(urlLower) && pageType === "unknown") {
4327
+ pageType = "checkout";
4328
+ confidence += 0.1;
4329
+ } else if (/\/login|\/signin|\/auth/i.test(urlLower) && pageType === "unknown") {
4330
+ pageType = "login";
4331
+ confidence += 0.1;
4332
+ } else if (/\/dashboard|\/account|\/profile/i.test(urlLower) && pageType === "unknown") {
4333
+ pageType = "dashboard";
4334
+ confidence += 0.1;
4335
+ } else if (/\/search/i.test(urlLower) && pageType === "unknown") {
4336
+ pageType = "search";
4337
+ confidence += 0.1;
4338
+ }
4339
+ const hasFormWithSubmit = forms.some(
4340
+ (f) => f.fields.some(
4341
+ (el) => el.type === "submit" || normalizeString(el.inputType) === "submit" || normalizeString(el.name) === "submit"
4342
+ )
4343
+ );
4344
+ const hasPriceSelectors = interactiveElements.some(
4345
+ (el) => el.selector?.includes("price") || normalizeString(el.label).includes("price") || normalizeString(el.name).includes("price") || el.selector?.includes("cost") || el.selector?.includes("amount")
4346
+ );
4347
+ if (pageType === "unknown") {
4348
+ if (hasFormWithSubmit && forms.length > 0) {
4349
+ pageType = "form";
4350
+ confidence += 0.1;
4351
+ } else if (hasPriceSelectors) {
4352
+ pageType = "product";
4353
+ confidence += 0.1;
4354
+ }
4355
+ }
4356
+ if ((pageType === "checkout" || pageType === "unknown") && forms.length > 0) {
4357
+ if (hasFormWithSubmit) {
4358
+ if (pageType === "unknown") {
4359
+ pageType = "form";
4360
+ }
4361
+ confidence += 0.15;
4362
+ }
4363
+ }
4364
+ if (pageType === "unknown") {
4365
+ confidence = 0.5;
4366
+ }
4367
+ confidence = Math.min(0.95, confidence);
4368
+ const primaryEntity = extractPrimaryEntity(pageType, structuredData);
4369
+ const formFields = pageType === "form" || pageType === "checkout" || pageType === "login" ? mapFormFields(forms, interactiveElements) : void 0;
4370
+ const actionButtons = mapActionButtons(interactiveElements);
4371
+ return {
4372
+ pageType,
4373
+ primaryEntity,
4374
+ formFields,
4375
+ actionButtons,
4376
+ confidence
4377
+ };
4378
+ }
4116
4379
  const EMPTY_PAGE_CONTENT = {
4117
4380
  title: "",
4118
4381
  content: "",
@@ -4826,156 +5089,6 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
4826
5089
  };
4827
5090
  })()
4828
5091
  `;
4829
- const SAFE_EXTRACTION_SCRIPT = String.raw`
4830
- (function() {
4831
- function getCleanBodyText() {
4832
- var removed = [];
4833
- document
4834
- .querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]')
4835
- .forEach(function(label) {
4836
- var parent = label.parentNode;
4837
- if (!parent) return;
4838
- removed.push({ label: label, parent: parent, nextSibling: label.nextSibling });
4839
- parent.removeChild(label);
4840
- });
4841
- try {
4842
- return document.body?.innerText || document.documentElement?.innerText || "";
4843
- } finally {
4844
- for (var i = removed.length - 1; i >= 0; i--) {
4845
- var entry = removed[i];
4846
- entry.parent.insertBefore(entry.label, entry.nextSibling);
4847
- }
4848
- }
4849
- }
4850
-
4851
- function text(value) {
4852
- const trimmed = value == null ? "" : String(value).trim();
4853
- return trimmed || undefined;
4854
- }
4855
-
4856
- function labelFor(el) {
4857
- const aria = text(el.getAttribute && el.getAttribute("aria-label"));
4858
- if (aria) return aria;
4859
- const placeholder = text(el.getAttribute && el.getAttribute("placeholder"));
4860
- if (placeholder) return placeholder;
4861
- if (el.id) {
4862
- const directLabel = document.querySelector('label[for="' + String(el.id).replace(/["\\]/g, "\\$&") + '"]');
4863
- const labelText = text(directLabel && directLabel.textContent);
4864
- if (labelText) return labelText;
4865
- }
4866
- return text(el.textContent);
4867
- }
4868
-
4869
- let indexCounter = 0;
4870
- function nextIndex() {
4871
- indexCounter += 1;
4872
- return indexCounter;
4873
- }
4874
-
4875
- const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6"))
4876
- .map((el) => {
4877
- const headingText = text(el.textContent);
4878
- if (!headingText) return null;
4879
- return { level: Number.parseInt(el.tagName[1], 10), text: headingText };
4880
- })
4881
- .filter(Boolean);
4882
-
4883
- const navigation = Array.from(document.querySelectorAll("nav a[href], [role='navigation'] a[href], header nav a[href]"))
4884
- .map((el) => {
4885
- const href = text(el.href || el.getAttribute("href"));
4886
- const linkText = text(el.textContent);
4887
- if (!href || href.startsWith("#") || !linkText) return null;
4888
- return {
4889
- type: "link",
4890
- text: linkText.slice(0, 100),
4891
- href: href.slice(0, 500),
4892
- context: "nav",
4893
- index: nextIndex(),
4894
- visible: true,
4895
- disabled: false,
4896
- };
4897
- })
4898
- .filter(Boolean);
4899
-
4900
- const interactiveElements = [];
4901
- Array.from(document.querySelectorAll("button, [role='button'], input[type='submit'], input[type='button']"))
4902
- .forEach((el) => {
4903
- interactiveElements.push({
4904
- type: "button",
4905
- text: text(el.textContent || el.value || el.getAttribute("aria-label") || "Button")?.slice(0, 100),
4906
- index: nextIndex(),
4907
- visible: true,
4908
- disabled: !!(el.hasAttribute && (el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true")),
4909
- });
4910
- });
4911
-
4912
- Array.from(document.querySelectorAll("a[href]")).forEach((el) => {
4913
- const href = text(el.href || el.getAttribute("href"));
4914
- const linkText = text(el.textContent);
4915
- if (!href || href.startsWith("#") || !linkText) return;
4916
- interactiveElements.push({
4917
- type: "link",
4918
- text: linkText.slice(0, 100),
4919
- href: href.slice(0, 500),
4920
- index: nextIndex(),
4921
- visible: true,
4922
- disabled: false,
4923
- });
4924
- });
4925
-
4926
- Array.from(document.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']), select, textarea"))
4927
- .forEach((el) => {
4928
- const tag = el.tagName.toLowerCase();
4929
- var elType = (el.type || "").toLowerCase();
4930
- interactiveElements.push({
4931
- type: tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input",
4932
- label: labelFor(el)?.slice(0, 100),
4933
- inputType: text(el.getAttribute && el.getAttribute("type")),
4934
- placeholder: text(el.getAttribute && el.getAttribute("placeholder")),
4935
- value: shouldExposeFieldValue(el) ? text(el.value) : undefined,
4936
- options: tag === "select"
4937
- ? Array.from(el.options || []).map(function(option) { return { label: text(option.textContent || option.value) || option.value, value: option.value }; }).filter(function(o) { return o.label || o.value; }).slice(0, 25)
4938
- : undefined,
4939
- required: !!(el.hasAttribute && el.hasAttribute("required")) || undefined,
4940
- index: nextIndex(),
4941
- visible: true,
4942
- disabled: !!(el.hasAttribute && (el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true")),
4943
- name: el.name || undefined,
4944
- autocomplete: text(el.getAttribute && el.getAttribute("autocomplete")),
4945
- checked: (elType === "checkbox" || elType === "radio") ? !!el.checked : undefined,
4946
- });
4947
- });
4948
-
4949
- const forms = Array.from(document.querySelectorAll("form")).map((form) => ({
4950
- id: text(form.id),
4951
- action: text(form.getAttribute("action")),
4952
- method: text(form.getAttribute("method")),
4953
- fields: [],
4954
- }));
4955
-
4956
- return {
4957
- title: document.title || "",
4958
- content: getCleanBodyText(),
4959
- htmlContent: "",
4960
- byline: "",
4961
- excerpt: "",
4962
- url: window.location.href || "",
4963
- headings,
4964
- navigation,
4965
- interactiveElements,
4966
- forms,
4967
- viewport: {
4968
- width: window.innerWidth || document.documentElement?.clientWidth || 0,
4969
- height: window.innerHeight || document.documentElement?.clientHeight || 0,
4970
- scrollX: 0,
4971
- scrollY: 0,
4972
- },
4973
- overlays: [],
4974
- dormantOverlays: [],
4975
- landmarks: [],
4976
- };
4977
- })()
4978
- `;
4979
5092
  function delay(ms) {
4980
5093
  return new Promise((resolve) => setTimeout(resolve, ms));
4981
5094
  }
@@ -5012,7 +5125,7 @@ async function executeScript(webContents, script) {
5012
5125
  } catch {
5013
5126
  return null;
5014
5127
  } finally {
5015
- if (typeof timer !== "undefined" && timer) {
5128
+ if (timer) {
5016
5129
  clearTimeout(timer);
5017
5130
  }
5018
5131
  }
@@ -5087,10 +5200,17 @@ function mergePageContent(candidates, webContents) {
5087
5200
  headings: mergedBase.headings,
5088
5201
  metaTags: mergedBase.metaTags
5089
5202
  });
5203
+ const pageSchema = inferPageSchema({
5204
+ ...mergedBase,
5205
+ structuredData: normalizedStructuredData,
5206
+ title: mergedBase.title || webContents.getTitle() || "",
5207
+ url: mergedBase.url || webContents.getURL() || ""
5208
+ });
5090
5209
  return {
5091
5210
  ...mergedBase,
5092
5211
  structuredData: normalizedStructuredData,
5093
5212
  pageIssues,
5213
+ pageSchema,
5094
5214
  title: mergedBase.title || webContents.getTitle() || "",
5095
5215
  url: mergedBase.url || webContents.getURL() || ""
5096
5216
  };
@@ -5116,13 +5236,12 @@ async function estimateExtractionTimeout(webContents) {
5116
5236
  }
5117
5237
  async function extractContentInner(webContents) {
5118
5238
  await waitForDomReady(webContents);
5119
- const [preloadResult, directResult, safeResult] = await Promise.all([
5239
+ const [preloadResult, directResult] = await Promise.all([
5120
5240
  executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT),
5121
- executeScript(webContents, DIRECT_EXTRACTION_SCRIPT),
5122
- executeScript(webContents, SAFE_EXTRACTION_SCRIPT)
5241
+ executeScript(webContents, DIRECT_EXTRACTION_SCRIPT)
5123
5242
  ]);
5124
5243
  return mergePageContent(
5125
- [preloadResult, directResult, safeResult],
5244
+ [preloadResult, directResult],
5126
5245
  webContents
5127
5246
  );
5128
5247
  }
@@ -5179,7 +5298,8 @@ function normalizePageContent(value) {
5179
5298
  rdfa: Array.isArray(page.rdfa) ? page.rdfa : [],
5180
5299
  metaTags: page.metaTags && typeof page.metaTags === "object" && !Array.isArray(page.metaTags) ? page.metaTags : {},
5181
5300
  structuredData: Array.isArray(page.structuredData) ? page.structuredData : [],
5182
- pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : []
5301
+ pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : [],
5302
+ pageSchema: page.pageSchema
5183
5303
  };
5184
5304
  }
5185
5305
  const latestPageDiffs = /* @__PURE__ */ new Map();
@@ -5189,17 +5309,19 @@ const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
5189
5309
  const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
5190
5310
  const lastMutationActivityAt = /* @__PURE__ */ new Map();
5191
5311
  const destroyListenerAttached = /* @__PURE__ */ new WeakSet();
5312
+ function cleanupTimersForWcId(wcId) {
5313
+ const timer = pendingPageSnapshotTimers.get(wcId);
5314
+ if (timer) clearTimeout(timer);
5315
+ pendingPageSnapshotTimers.delete(wcId);
5316
+ pendingPageSnapshotDueAt.delete(wcId);
5317
+ lastMutationSnapshotAt.delete(wcId);
5318
+ lastMutationActivityAt.delete(wcId);
5319
+ }
5192
5320
  function attachDestroyCleanup(wc) {
5193
5321
  if (destroyListenerAttached.has(wc)) return;
5194
5322
  destroyListenerAttached.add(wc);
5195
5323
  wc.once("destroyed", () => {
5196
- const wcId = wc.id;
5197
- const timer = pendingPageSnapshotTimers.get(wcId);
5198
- if (timer) clearTimeout(timer);
5199
- pendingPageSnapshotTimers.delete(wcId);
5200
- pendingPageSnapshotDueAt.delete(wcId);
5201
- lastMutationSnapshotAt.delete(wcId);
5202
- lastMutationActivityAt.delete(wcId);
5324
+ cleanupTimersForWcId(wc.id);
5203
5325
  });
5204
5326
  }
5205
5327
  const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
@@ -5271,8 +5393,7 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
5271
5393
  const existing = pendingPageSnapshotTimers.get(wcId);
5272
5394
  if (existing) clearTimeout(existing);
5273
5395
  const timer = setTimeout(() => {
5274
- pendingPageSnapshotTimers.delete(wcId);
5275
- pendingPageSnapshotDueAt.delete(wcId);
5396
+ cleanupTimersForWcId(wcId);
5276
5397
  if (wc.isDestroyed()) return;
5277
5398
  lastMutationSnapshotAt.set(wcId, Date.now());
5278
5399
  void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
@@ -5596,9 +5717,9 @@ function resizeSidebarViews(state2) {
5596
5717
  const contentWidth = width - sidebarWidth;
5597
5718
  sidebarView.setBounds({
5598
5719
  x: width - sidebarWidth - resizeHandleOverlap,
5599
- y: 0,
5720
+ y: chromeHeight,
5600
5721
  width: sidebarWidth + resizeHandleOverlap,
5601
- height
5722
+ height: height - chromeHeight
5602
5723
  });
5603
5724
  if (uiState.devtoolsPanelOpen) {
5604
5725
  devtoolsPanelView.setBounds({
@@ -9761,7 +9882,17 @@ const TOOL_DEFINITIONS = [
9761
9882
  folderSummary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
9762
9883
  createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
9763
9884
  note: zod.z.string().optional().describe("Optional note about why the page was saved"),
9764
- onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder")
9885
+ onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder"),
9886
+ intent: zod.z.string().optional().describe(
9887
+ "Human-readable description of what this bookmark is for (e.g. 'expense reporting')"
9888
+ ),
9889
+ expectedContent: zod.z.string().optional().describe(
9890
+ "Brief description of the content the agent should expect to find here"
9891
+ ),
9892
+ keyFields: zod.z.array(zod.z.string()).optional().describe(
9893
+ "Important form field names for this page (e.g. ['receipt_id', 'date', 'amount'])"
9894
+ ),
9895
+ agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
9765
9896
  },
9766
9897
  tier: 1
9767
9898
  },
@@ -9780,7 +9911,11 @@ const TOOL_DEFINITIONS = [
9780
9911
  folderSummary: zod.z.string().optional().describe("Optional summary for new folder"),
9781
9912
  createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
9782
9913
  note: zod.z.string().optional().describe("Optional note"),
9783
- archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
9914
+ archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
9915
+ intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
9916
+ expectedContent: zod.z.string().optional().describe("Brief description of content the agent should expect"),
9917
+ keyFields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
9918
+ agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
9784
9919
  },
9785
9920
  tier: 2
9786
9921
  },
@@ -10352,7 +10487,9 @@ const FIELD_WEIGHTS = {
10352
10487
  note: 5,
10353
10488
  folder: 3,
10354
10489
  folderSummary: 2,
10355
- url: 1
10490
+ url: 1,
10491
+ intent: 4,
10492
+ expectedContent: 3
10356
10493
  };
10357
10494
  function normalizeBookmarkSearchText(value) {
10358
10495
  let normalized = value.toLowerCase();
@@ -10383,7 +10520,9 @@ function getBookmarkSearchMatch(args) {
10383
10520
  url: args.url,
10384
10521
  note: args.note,
10385
10522
  folder: args.folder,
10386
- folderSummary: args.folderSummary
10523
+ folderSummary: args.folderSummary,
10524
+ intent: args.intent,
10525
+ expectedContent: args.expectedContent
10387
10526
  };
10388
10527
  for (const field of Object.keys(values)) {
10389
10528
  if (!bookmarkFieldMatchesQuery(values[field], normalizedQuery, tokens)) {
@@ -10523,7 +10662,9 @@ function searchBookmarks(query) {
10523
10662
  url: bookmark.url,
10524
10663
  note: bookmark.note,
10525
10664
  folder: folder?.name,
10526
- folderSummary: folder?.summary
10665
+ folderSummary: folder?.summary,
10666
+ intent: bookmark.intent,
10667
+ expectedContent: bookmark.expectedContent
10527
10668
  });
10528
10669
  return {
10529
10670
  bookmark,
@@ -10603,6 +10744,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
10603
10744
  if (note !== void 0) {
10604
10745
  bookmark2.note = note.trim() || void 0;
10605
10746
  }
10747
+ if (options?.extra) {
10748
+ Object.assign(bookmark2, options.extra);
10749
+ }
10606
10750
  bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10607
10751
  save();
10608
10752
  emit();
@@ -10618,7 +10762,8 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
10618
10762
  title: normalizedTitle,
10619
10763
  note: note?.trim() || void 0,
10620
10764
  folderId: targetId,
10621
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
10765
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
10766
+ ...options?.extra
10622
10767
  };
10623
10768
  state$1.bookmarks.push(bookmark);
10624
10769
  save();
@@ -10654,6 +10799,21 @@ function updateBookmark(id, updates) {
10654
10799
  if (typeof updates.folderId === "string") {
10655
10800
  bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
10656
10801
  }
10802
+ if (typeof updates.intent === "string") {
10803
+ bookmark.intent = updates.intent.trim() || void 0;
10804
+ }
10805
+ if (typeof updates.expectedContent === "string") {
10806
+ bookmark.expectedContent = updates.expectedContent.trim() || void 0;
10807
+ }
10808
+ if (updates.keyFields !== void 0) {
10809
+ bookmark.keyFields = updates.keyFields;
10810
+ }
10811
+ if (updates.pageSchema !== void 0) {
10812
+ bookmark.pageSchema = updates.pageSchema;
10813
+ }
10814
+ if (updates.agentHints !== void 0) {
10815
+ bookmark.agentHints = updates.agentHints;
10816
+ }
10657
10817
  save();
10658
10818
  emit();
10659
10819
  return { ...bookmark };
@@ -11634,6 +11794,33 @@ function formatCompactToolResult(name, result) {
11634
11794
  return limitText(result, 18, 1400);
11635
11795
  }
11636
11796
  }
11797
+ function normalizeOptionalString(value) {
11798
+ if (typeof value !== "string") return void 0;
11799
+ const trimmed = value.trim();
11800
+ return trimmed || void 0;
11801
+ }
11802
+ function normalizeKeyFields(value) {
11803
+ if (!Array.isArray(value)) return void 0;
11804
+ const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
11805
+ return normalized.length > 0 ? normalized : void 0;
11806
+ }
11807
+ function normalizeAgentHints(value) {
11808
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
11809
+ return void 0;
11810
+ }
11811
+ const normalized = Object.fromEntries(
11812
+ Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
11813
+ );
11814
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
11815
+ }
11816
+ function normalizeBookmarkMetadata(input) {
11817
+ return {
11818
+ intent: normalizeOptionalString(input.intent),
11819
+ expectedContent: normalizeOptionalString(input.expectedContent),
11820
+ keyFields: normalizeKeyFields(input.keyFields),
11821
+ agentHints: normalizeAgentHints(input.agentHints)
11822
+ };
11823
+ }
11637
11824
  const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
11638
11825
  const HUGGING_FACE_MODEL_TASKS = [
11639
11826
  {
@@ -11894,6 +12081,14 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
11894
12081
  appliedFilters
11895
12082
  };
11896
12083
  }
12084
+ function getBookmarkMetadataFromArgs(args) {
12085
+ return normalizeBookmarkMetadata({
12086
+ intent: args.intent,
12087
+ expectedContent: args.expectedContent,
12088
+ keyFields: args.keyFields,
12089
+ agentHints: args.agentHints
12090
+ });
12091
+ }
11897
12092
  const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
11898
12093
  const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
11899
12094
  async function loadPermittedUrl$1(wc, url) {
@@ -15558,7 +15753,10 @@ ${truncated}`;
15558
15753
  source.title,
15559
15754
  target.folderId,
15560
15755
  note,
15561
- { onDuplicate }
15756
+ {
15757
+ onDuplicate,
15758
+ extra: getBookmarkMetadataFromArgs(args)
15759
+ }
15562
15760
  );
15563
15761
  if (result2.status === "conflict" && result2.existing) {
15564
15762
  return composeFolderAwareResponse$1(
@@ -15597,7 +15795,8 @@ ${truncated}`;
15597
15795
  const updated = updateBookmark(existing.id, {
15598
15796
  folderId: target.folderId,
15599
15797
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
15600
- note
15798
+ note,
15799
+ ...getBookmarkMetadataFromArgs(args)
15601
15800
  });
15602
15801
  if (!updated) {
15603
15802
  return `Bookmark ${existing.id} not found`;
@@ -15608,12 +15807,18 @@ ${truncated}`;
15608
15807
  );
15609
15808
  }
15610
15809
  if ("error" in source) return `Error: ${source.error}`;
15611
- const bookmark = saveBookmark(
15810
+ const result2 = saveBookmarkWithPolicy(
15612
15811
  source.url,
15613
15812
  source.title,
15614
15813
  target.folderId,
15615
- note
15814
+ note,
15815
+ {
15816
+ onDuplicate: "update",
15817
+ extra: getBookmarkMetadataFromArgs(args)
15818
+ }
15616
15819
  );
15820
+ const bookmark = result2.bookmark;
15821
+ if (!bookmark) return "Error: Bookmark save failed";
15617
15822
  return composeFolderAwareResponse$1(
15618
15823
  `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15619
15824
  target.createdFolder
@@ -17310,6 +17515,14 @@ function readAuditLog(limit = 100) {
17310
17515
  }
17311
17516
  let httpServer = null;
17312
17517
  let mcpAuthToken = null;
17518
+ function getBookmarkMetadataFromToolArgs(args) {
17519
+ return normalizeBookmarkMetadata({
17520
+ intent: args.intent,
17521
+ expectedContent: args.expected_content,
17522
+ keyFields: args.key_fields,
17523
+ agentHints: args.agent_hints
17524
+ });
17525
+ }
17313
17526
  const MCP_AUTH_FILENAME = "mcp-auth.json";
17314
17527
  function getMcpAuthFilePath() {
17315
17528
  const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
@@ -20137,7 +20350,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20137
20350
  note: zod.z.string().optional().describe("Optional note about why this was bookmarked"),
20138
20351
  on_duplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe(
20139
20352
  'How to handle an existing bookmark with the same URL in the same folder: "ask" (default), "update", or "duplicate"'
20140
- )
20353
+ ),
20354
+ intent: zod.z.string().optional().describe(
20355
+ "Human-readable description of what this bookmark is for"
20356
+ ),
20357
+ expected_content: zod.z.string().optional().describe(
20358
+ "Brief description of the content the agent should expect to find here"
20359
+ ),
20360
+ key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
20361
+ agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
20141
20362
  }
20142
20363
  },
20143
20364
  async ({
@@ -20150,7 +20371,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20150
20371
  folder_summary,
20151
20372
  create_folder_if_missing,
20152
20373
  note,
20153
- on_duplicate
20374
+ on_duplicate,
20375
+ intent,
20376
+ expected_content,
20377
+ key_fields,
20378
+ agent_hints
20154
20379
  }) => {
20155
20380
  return withAction(
20156
20381
  runtime2,
@@ -20165,7 +20390,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20165
20390
  folder_name,
20166
20391
  folder_summary,
20167
20392
  create_folder_if_missing,
20168
- note
20393
+ note,
20394
+ intent,
20395
+ expected_content,
20396
+ key_fields,
20397
+ agent_hints
20169
20398
  },
20170
20399
  async () => {
20171
20400
  const currentTab = tabManager.getActiveTab();
@@ -20195,7 +20424,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20195
20424
  source.title,
20196
20425
  target.folderId,
20197
20426
  note,
20198
- { onDuplicate: on_duplicate ?? "ask" }
20427
+ {
20428
+ onDuplicate: on_duplicate ?? "ask",
20429
+ extra: getBookmarkMetadataFromToolArgs({
20430
+ intent,
20431
+ expected_content,
20432
+ key_fields,
20433
+ agent_hints
20434
+ })
20435
+ }
20199
20436
  );
20200
20437
  if (result.status === "conflict" && result.existing) {
20201
20438
  return composeFolderAwareResponse(
@@ -20292,7 +20529,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20292
20529
  folder_summary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
20293
20530
  create_folder_if_missing: zod.z.boolean().optional().describe("Create folder_name automatically when it does not exist"),
20294
20531
  note: zod.z.string().optional().describe("Optional note to attach or update on the bookmark"),
20295
- archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
20532
+ archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
20533
+ intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
20534
+ expected_content: zod.z.string().optional().describe("Brief description of content the agent should expect"),
20535
+ key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
20536
+ agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
20296
20537
  }
20297
20538
  },
20298
20539
  async (args) => {
@@ -20328,7 +20569,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20328
20569
  const updated = updateBookmark(existing.id, {
20329
20570
  folderId: target.folderId,
20330
20571
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
20331
- note
20572
+ note,
20573
+ ...getBookmarkMetadataFromToolArgs(args)
20332
20574
  });
20333
20575
  if (!updated) {
20334
20576
  return `Bookmark ${existing.id} not found`;
@@ -20339,12 +20581,18 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20339
20581
  );
20340
20582
  }
20341
20583
  if ("error" in source) return `Error: ${source.error}`;
20342
- const bookmark = saveBookmark(
20584
+ const result = saveBookmarkWithPolicy(
20343
20585
  source.url,
20344
20586
  source.title,
20345
20587
  target.folderId,
20346
- note
20588
+ note,
20589
+ {
20590
+ onDuplicate: "update",
20591
+ extra: getBookmarkMetadataFromToolArgs(args)
20592
+ }
20347
20593
  );
20594
+ const bookmark = result.bookmark;
20595
+ if (!bookmark) return "Error: Bookmark save failed";
20348
20596
  return composeFolderAwareResponse(
20349
20597
  `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
20350
20598
  target.createdFolder
@@ -22798,7 +23046,7 @@ function registerWindowControlHandlers(mainWindow) {
22798
23046
  });
22799
23047
  }
22800
23048
  let activeChatProvider = null;
22801
- const VALID_APPROVAL_MODES = /* @__PURE__ */ new Set(["auto", "confirm-dangerous", "manual"]);
23049
+ const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
22802
23050
  function registerIpcHandlers(windowState, runtime2) {
22803
23051
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
22804
23052
  let sidebarResizeRecoveryTimer = null;
@@ -22807,10 +23055,9 @@ function registerIpcHandlers(windowState, runtime2) {
22807
23055
  let pendingRuntimeState = null;
22808
23056
  const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
22809
23057
  const clearSidebarResizeRecoveryTimer = () => {
22810
- if (sidebarResizeRecoveryTimer) {
22811
- clearTimeout(sidebarResizeRecoveryTimer);
22812
- sidebarResizeRecoveryTimer = null;
22813
- }
23058
+ if (!sidebarResizeRecoveryTimer) return;
23059
+ clearTimeout(sidebarResizeRecoveryTimer);
23060
+ sidebarResizeRecoveryTimer = null;
22814
23061
  };
22815
23062
  const restoreSidebarLayoutAfterResize = () => {
22816
23063
  clearSidebarResizeRecoveryTimer();
@@ -23085,7 +23332,15 @@ function registerIpcHandlers(windowState, runtime2) {
23085
23332
  sidebarResizeActive = true;
23086
23333
  clearSidebarResizeRecoveryTimer();
23087
23334
  const [width, height] = windowState.mainWindow.getContentSize();
23088
- windowState.sidebarView.setBounds({ x: 0, y: 0, width, height });
23335
+ const chromeHeight = windowState.uiState.focusMode ? 0 : 110;
23336
+ const sidebarWidth = windowState.uiState.sidebarWidth;
23337
+ const resizeHandleOverlap = 6;
23338
+ windowState.sidebarView.setBounds({
23339
+ x: width - sidebarWidth - resizeHandleOverlap,
23340
+ y: chromeHeight,
23341
+ width: sidebarWidth + resizeHandleOverlap,
23342
+ height: height - chromeHeight
23343
+ });
23089
23344
  scheduleSidebarResizeRecovery();
23090
23345
  });
23091
23346
  electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (_, width) => {
@@ -23093,7 +23348,6 @@ function registerIpcHandlers(windowState, runtime2) {
23093
23348
  const clamped = Math.max(240, Math.min(800, Math.round(width)));
23094
23349
  windowState.uiState.sidebarWidth = clamped;
23095
23350
  resizeSidebarViews(windowState);
23096
- scheduleSidebarResizeRecovery();
23097
23351
  return clamped;
23098
23352
  });
23099
23353
  electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, () => {
@@ -23152,7 +23406,7 @@ function registerIpcHandlers(windowState, runtime2) {
23152
23406
  Channels.AGENT_SET_APPROVAL_MODE,
23153
23407
  (_, mode) => {
23154
23408
  assertString(mode, "mode");
23155
- if (!VALID_APPROVAL_MODES.has(mode)) {
23409
+ if (!VALID_APPROVAL_MODES.includes(mode)) {
23156
23410
  throw new Error(`Invalid approval mode: ${mode}`);
23157
23411
  }
23158
23412
  trackApprovalModeChanged(mode);
@@ -23192,9 +23446,23 @@ function registerIpcHandlers(windowState, runtime2) {
23192
23446
  );
23193
23447
  electron.ipcMain.handle(
23194
23448
  Channels.BOOKMARK_SAVE,
23195
- (_, url, title, folderId, note) => {
23449
+ (_, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
23196
23450
  trackBookmarkAction("save");
23197
- return saveBookmark(url, title, folderId, note);
23451
+ const result = saveBookmarkWithPolicy(url, title, folderId, note, {
23452
+ onDuplicate: "update",
23453
+ extra: {
23454
+ ...normalizeBookmarkMetadata({
23455
+ intent,
23456
+ expectedContent,
23457
+ keyFields,
23458
+ agentHints
23459
+ })
23460
+ }
23461
+ });
23462
+ if (!result.bookmark) {
23463
+ throw new Error("Bookmark save failed");
23464
+ }
23465
+ return result.bookmark;
23198
23466
  }
23199
23467
  );
23200
23468
  electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
@@ -23308,6 +23576,7 @@ function registerIpcHandlers(windowState, runtime2) {
23308
23576
  }
23309
23577
  }
23310
23578
  findWiredWcId = wc.id;
23579
+ if (wc.isDestroyed()) return;
23311
23580
  const listener = (_event, result) => {
23312
23581
  if (!chromeView.webContents.isDestroyed()) {
23313
23582
  chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
@@ -23315,8 +23584,9 @@ function registerIpcHandlers(windowState, runtime2) {
23315
23584
  };
23316
23585
  findResultListener = listener;
23317
23586
  wc.on("found-in-page", listener);
23587
+ const capturedWcId = wc.id;
23318
23588
  wc.once("destroyed", () => {
23319
- if (findWiredWcId === wc.id) {
23589
+ if (findWiredWcId === capturedWcId) {
23320
23590
  findWiredWcId = null;
23321
23591
  findResultListener = null;
23322
23592
  }
@@ -23424,9 +23694,20 @@ function registerIpcHandlers(windowState, runtime2) {
23424
23694
  sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
23425
23695
  return state2;
23426
23696
  });
23697
+ const PREMIUM_TRACKABLE_STEPS = [
23698
+ "chat_banner_viewed",
23699
+ "chat_banner_clicked",
23700
+ "settings_banner_viewed",
23701
+ "settings_banner_clicked",
23702
+ "welcome_banner_clicked",
23703
+ "premium_gate_seen",
23704
+ "premium_gate_clicked",
23705
+ "iteration_limit_seen",
23706
+ "iteration_limit_clicked"
23707
+ ];
23427
23708
  electron.ipcMain.handle(Channels.PREMIUM_TRACK_CONTEXT, (_, step) => {
23428
23709
  assertString(step, "step");
23429
- if (step === "chat_banner_viewed" || step === "chat_banner_clicked" || step === "settings_banner_viewed" || step === "settings_banner_clicked" || step === "welcome_banner_clicked" || step === "premium_gate_seen" || step === "premium_gate_clicked" || step === "iteration_limit_seen" || step === "iteration_limit_clicked") {
23710
+ if (PREMIUM_TRACKABLE_STEPS.includes(step)) {
23430
23711
  trackPremiumFunnel(step);
23431
23712
  }
23432
23713
  });