@quanta-intellect/vessel-browser 0.1.60 → 0.1.61
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 +2 -0
- package/out/main/index.js +364 -194
- package/out/preload/index.js +11 -1
- package/out/renderer/assets/{index-NKk_lpQh.js → index-Di64dPm5.js} +80 -60
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,8 @@ Today, Vessel provides the browser shell, page visibility, and supervisory surfa
|
|
|
93
93
|
- **Chat Assistant** — built-in conversational AI in the sidebar Chat tab; supports Anthropic, OpenAI, Ollama, llama.cpp, Mistral, xAI, Google Gemini, OpenRouter, and any OpenAI-compatible endpoint; reads the current page automatically; has full access to the same browser tools as external agents; multi-turn session history; configure provider, model, and API key in Settings
|
|
94
94
|
- **Automation Kits** (Premium) — parameterized workflow templates in the sidebar Automate tab; fill in a short form and the built-in agent executes the workflow autonomously; bundled kits include Research & Collect (multi-source research with bookmark saving) and Price Scout (cross-retailer price comparison); designed for a future kit marketplace
|
|
95
95
|
- **Dev Tools Panel** (`F12`) — inspect console output, network requests, and MCP/agent activity in a resizable panel at the bottom of the window; export logs by category and date range as JSON
|
|
96
|
+
- **Agent-Meaningful Bookmarks** — bookmarks carry structured context the agent can read and act on: `intent` (what the page is for), `expectedContent` (what to expect on the page), `keyFields` (important form fields), `agentHints` (arbitrary directives), and a stored `pageSchema`; all fields are searchable
|
|
97
|
+
- **Page Schema Inference** — Vessel automatically infers a typed schema for every page: `pageType` (article, product, form, search, checkout, login, dashboard), `primaryEntity` (structured fields for products and articles), `formFields` (with names, types, labels, selectors), and `actionButtons` (with inferred intents: submit, addToCart, login, etc.); schema is attached to every content extraction result
|
|
96
98
|
- **Bookmarks for Agents** — save pages into folders, attach one-line folder summaries, and search bookmarks over MCP instead of dumping the entire library
|
|
97
99
|
- **Named Session Persistence** — save cookies, localStorage, and current tab layout under a reusable name, then reload it after a restart
|
|
98
100
|
- **Page Highlights** — agents can visually highlight text or elements on any page with labeled, color-coded markers that persist across navigation; highlight count and navigation controls appear in the sidebar; cleared explicitly or via tool call
|
package/out/main/index.js
CHANGED
|
@@ -3903,10 +3903,8 @@ function startBackgroundRevalidation() {
|
|
|
3903
3903
|
}, REVALIDATION_INTERVAL_MS);
|
|
3904
3904
|
}
|
|
3905
3905
|
function stopBackgroundRevalidation() {
|
|
3906
|
-
|
|
3907
|
-
|
|
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";
|
|
@@ -4011,8 +4009,8 @@ function stopTelemetry() {
|
|
|
4011
4009
|
}
|
|
4012
4010
|
if (flushTimer) {
|
|
4013
4011
|
clearInterval(flushTimer);
|
|
4014
|
-
flushTimer = null;
|
|
4015
4012
|
}
|
|
4013
|
+
flushTimer = null;
|
|
4016
4014
|
void flush();
|
|
4017
4015
|
}
|
|
4018
4016
|
async function flush() {
|
|
@@ -4113,6 +4111,226 @@ function selectorHelpersJS(attributes = DEFAULT_SELECTOR_ATTRIBUTES) {
|
|
|
4113
4111
|
"}"
|
|
4114
4112
|
].join("\n");
|
|
4115
4113
|
}
|
|
4114
|
+
function normalizeString(value) {
|
|
4115
|
+
return (value ?? "").trim().toLowerCase();
|
|
4116
|
+
}
|
|
4117
|
+
function mapInputType(el) {
|
|
4118
|
+
const inputType = el.inputType ?? el.type ?? "text";
|
|
4119
|
+
switch (inputType.toLowerCase()) {
|
|
4120
|
+
case "email":
|
|
4121
|
+
return "email";
|
|
4122
|
+
case "password":
|
|
4123
|
+
return "password";
|
|
4124
|
+
case "number":
|
|
4125
|
+
case "range":
|
|
4126
|
+
return "number";
|
|
4127
|
+
case "select-one":
|
|
4128
|
+
case "select":
|
|
4129
|
+
return "select";
|
|
4130
|
+
case "checkbox":
|
|
4131
|
+
case "radio":
|
|
4132
|
+
return "checkbox";
|
|
4133
|
+
case "date":
|
|
4134
|
+
case "datetime-local":
|
|
4135
|
+
case "time":
|
|
4136
|
+
case "month":
|
|
4137
|
+
case "week":
|
|
4138
|
+
return "date";
|
|
4139
|
+
case "file":
|
|
4140
|
+
return "file";
|
|
4141
|
+
default:
|
|
4142
|
+
return "text";
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
function mapFormFields(forms, interactiveElements) {
|
|
4146
|
+
const fields = [];
|
|
4147
|
+
const formFieldSelectors = /* @__PURE__ */ new Set();
|
|
4148
|
+
for (const form of forms) {
|
|
4149
|
+
for (const el of form.fields ?? []) {
|
|
4150
|
+
formFieldSelectors.add(el.selector || el.name || el.label || String(el.index));
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
for (const el of interactiveElements) {
|
|
4154
|
+
const key = el.selector || el.name || el.label || String(el.index);
|
|
4155
|
+
if (formFieldSelectors.has(key)) {
|
|
4156
|
+
fields.push({
|
|
4157
|
+
name: el.name || el.label || key,
|
|
4158
|
+
type: mapInputType(el),
|
|
4159
|
+
label: el.label,
|
|
4160
|
+
required: el.required,
|
|
4161
|
+
selector: el.selector || ""
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
return fields;
|
|
4166
|
+
}
|
|
4167
|
+
function mapActionButtons(interactiveElements) {
|
|
4168
|
+
const buttons = [];
|
|
4169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4170
|
+
for (const el of interactiveElements) {
|
|
4171
|
+
if (el.type !== "button" && el.type !== "submit" && el.type !== "reset") continue;
|
|
4172
|
+
const label = (el.label || el.textContent || "").trim();
|
|
4173
|
+
if (!label || seen.has(label)) continue;
|
|
4174
|
+
seen.add(label);
|
|
4175
|
+
let intent;
|
|
4176
|
+
const normalized = normalizeString(label);
|
|
4177
|
+
if (/\b(add to cart|buy now|add to bag|add to basket|shop now)\b/.test(normalized)) {
|
|
4178
|
+
intent = "addToCart";
|
|
4179
|
+
} else if (/\b(login|sign in|log in|signin|log-in)\b/.test(normalized)) {
|
|
4180
|
+
intent = "login";
|
|
4181
|
+
} else if (/\b(submit|send|continue|next|proceed|register|create account|sign up)\b/.test(normalized)) {
|
|
4182
|
+
intent = "submit";
|
|
4183
|
+
} else if (/\b(cancel|back|return|go back|close)\b/.test(normalized)) {
|
|
4184
|
+
intent = "cancel";
|
|
4185
|
+
} else if (/\b(download|export|save as)\b/.test(normalized)) {
|
|
4186
|
+
intent = "download";
|
|
4187
|
+
} else if (/\b(search|find|go|submit search)\b/.test(normalized)) {
|
|
4188
|
+
intent = "search";
|
|
4189
|
+
} else if (el.href || el.url) {
|
|
4190
|
+
intent = "navigate";
|
|
4191
|
+
}
|
|
4192
|
+
if (el.selector || el.name || el.label) {
|
|
4193
|
+
buttons.push({
|
|
4194
|
+
label,
|
|
4195
|
+
selector: el.selector || el.name || el.label,
|
|
4196
|
+
intent
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
return buttons;
|
|
4201
|
+
}
|
|
4202
|
+
function extractPrimaryEntity(pageType, structuredData, metaTags) {
|
|
4203
|
+
if (pageType === "product") {
|
|
4204
|
+
const product = structuredData?.find(
|
|
4205
|
+
(e) => e.types.some((t) => /^product$/i.test(t))
|
|
4206
|
+
);
|
|
4207
|
+
if (product) {
|
|
4208
|
+
const attrs = product.attributes ?? {};
|
|
4209
|
+
return {
|
|
4210
|
+
type: "Product",
|
|
4211
|
+
nameField: typeof attrs.name === "string" ? attrs.name : void 0,
|
|
4212
|
+
priceField: typeof attrs.price === "string" ? attrs.price : typeof attrs.offers === "object" && attrs.offers !== null ? String(attrs.offers["price"] ?? "") : void 0,
|
|
4213
|
+
imageField: typeof attrs.image === "string" ? attrs.image : Array.isArray(attrs.image) ? String(attrs.image[0]) : void 0,
|
|
4214
|
+
descriptionField: typeof attrs.description === "string" ? attrs.description : void 0,
|
|
4215
|
+
reviewsField: typeof attrs.reviews === "string" ? attrs.reviews : void 0,
|
|
4216
|
+
ratingField: typeof attrs.rating === "string" ? attrs.rating : void 0,
|
|
4217
|
+
addToCartField: void 0
|
|
4218
|
+
};
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (pageType === "article") {
|
|
4222
|
+
const article = structuredData?.find(
|
|
4223
|
+
(e) => e.types.some(
|
|
4224
|
+
(t) => /^(article|newsarticle|blogposting|webpage)$/i.test(t)
|
|
4225
|
+
)
|
|
4226
|
+
);
|
|
4227
|
+
if (article) {
|
|
4228
|
+
const attrs = article.attributes ?? {};
|
|
4229
|
+
return {
|
|
4230
|
+
type: article.types[0] ?? "Article",
|
|
4231
|
+
nameField: typeof attrs.headline === "string" ? attrs.headline : typeof attrs.name === "string" ? attrs.name : void 0,
|
|
4232
|
+
descriptionField: typeof attrs.articleBody === "string" ? attrs.articleBody : typeof attrs.description === "string" ? attrs.description : void 0
|
|
4233
|
+
};
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
return void 0;
|
|
4237
|
+
}
|
|
4238
|
+
function inferPageSchema(page) {
|
|
4239
|
+
let pageType = "unknown";
|
|
4240
|
+
let confidence = 0.5;
|
|
4241
|
+
const structuredData = page.structuredData;
|
|
4242
|
+
const metaTags = page.metaTags;
|
|
4243
|
+
const url = page.url ?? "";
|
|
4244
|
+
const forms = page.forms ?? [];
|
|
4245
|
+
const interactiveElements = page.interactiveElements ?? [];
|
|
4246
|
+
const urlLower = normalizeString(url);
|
|
4247
|
+
const jsonLdTypes = [];
|
|
4248
|
+
for (const entity of structuredData ?? []) {
|
|
4249
|
+
jsonLdTypes.push(...entity.types);
|
|
4250
|
+
}
|
|
4251
|
+
const hasProduct = jsonLdTypes.some((t) => /^product$/i.test(t));
|
|
4252
|
+
const hasArticle = jsonLdTypes.some(
|
|
4253
|
+
(t) => /^(article|newsarticle|blogposting)$/i.test(t)
|
|
4254
|
+
);
|
|
4255
|
+
const hasEvent = jsonLdTypes.some((t) => /^event$/i.test(t));
|
|
4256
|
+
const hasSearchResults = jsonLdTypes.some((t) => /^searchresultspage$/i.test(t));
|
|
4257
|
+
if (hasProduct) {
|
|
4258
|
+
pageType = "product";
|
|
4259
|
+
confidence += 0.2;
|
|
4260
|
+
} else if (hasArticle) {
|
|
4261
|
+
pageType = "article";
|
|
4262
|
+
confidence += 0.2;
|
|
4263
|
+
} else if (hasEvent) {
|
|
4264
|
+
pageType = "form";
|
|
4265
|
+
confidence += 0.15;
|
|
4266
|
+
} else if (hasSearchResults) {
|
|
4267
|
+
pageType = "search";
|
|
4268
|
+
confidence += 0.2;
|
|
4269
|
+
}
|
|
4270
|
+
const ogType = metaTags?.["og:type"];
|
|
4271
|
+
if (ogType) {
|
|
4272
|
+
const normalized = normalizeString(ogType);
|
|
4273
|
+
if (/^product$/i.test(normalized) && pageType !== "product") {
|
|
4274
|
+
pageType = "product";
|
|
4275
|
+
confidence += 0.15;
|
|
4276
|
+
} else if (/^article|blog|news/i.test(normalized) && pageType !== "article") {
|
|
4277
|
+
pageType = "article";
|
|
4278
|
+
confidence += 0.15;
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
if (/\/checkout|\/cart|\/payment|\/billing/i.test(urlLower) && pageType === "unknown") {
|
|
4282
|
+
pageType = "checkout";
|
|
4283
|
+
confidence += 0.1;
|
|
4284
|
+
} else if (/\/login|\/signin|\/auth/i.test(urlLower) && pageType === "unknown") {
|
|
4285
|
+
pageType = "login";
|
|
4286
|
+
confidence += 0.1;
|
|
4287
|
+
} else if (/\/dashboard|\/account|\/profile/i.test(urlLower) && pageType === "unknown") {
|
|
4288
|
+
pageType = "dashboard";
|
|
4289
|
+
confidence += 0.1;
|
|
4290
|
+
} else if (/\/search/i.test(urlLower) && pageType === "unknown") {
|
|
4291
|
+
pageType = "search";
|
|
4292
|
+
confidence += 0.1;
|
|
4293
|
+
}
|
|
4294
|
+
const hasFormWithSubmit = forms.some(
|
|
4295
|
+
(f) => f.fields.some(
|
|
4296
|
+
(el) => el.type === "submit" || normalizeString(el.inputType) === "submit" || normalizeString(el.name) === "submit"
|
|
4297
|
+
)
|
|
4298
|
+
);
|
|
4299
|
+
const hasPriceSelectors = interactiveElements.some(
|
|
4300
|
+
(el) => el.selector?.includes("price") || normalizeString(el.label).includes("price") || normalizeString(el.name).includes("price") || el.selector?.includes("cost") || el.selector?.includes("amount")
|
|
4301
|
+
);
|
|
4302
|
+
if (pageType === "unknown") {
|
|
4303
|
+
if (hasFormWithSubmit && forms.length > 0) {
|
|
4304
|
+
pageType = "form";
|
|
4305
|
+
confidence += 0.1;
|
|
4306
|
+
} else if (hasPriceSelectors) {
|
|
4307
|
+
pageType = "product";
|
|
4308
|
+
confidence += 0.1;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
if ((pageType === "checkout" || pageType === "unknown") && forms.length > 0) {
|
|
4312
|
+
if (hasFormWithSubmit) {
|
|
4313
|
+
if (pageType === "unknown") {
|
|
4314
|
+
pageType = "form";
|
|
4315
|
+
}
|
|
4316
|
+
confidence += 0.15;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
if (pageType === "unknown") {
|
|
4320
|
+
confidence = 0.5;
|
|
4321
|
+
}
|
|
4322
|
+
confidence = Math.min(0.95, confidence);
|
|
4323
|
+
const primaryEntity = extractPrimaryEntity(pageType, structuredData);
|
|
4324
|
+
const formFields = pageType === "form" || pageType === "checkout" || pageType === "login" ? mapFormFields(forms, interactiveElements) : void 0;
|
|
4325
|
+
const actionButtons = mapActionButtons(interactiveElements);
|
|
4326
|
+
return {
|
|
4327
|
+
pageType,
|
|
4328
|
+
primaryEntity,
|
|
4329
|
+
formFields,
|
|
4330
|
+
actionButtons,
|
|
4331
|
+
confidence
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4116
4334
|
const EMPTY_PAGE_CONTENT = {
|
|
4117
4335
|
title: "",
|
|
4118
4336
|
content: "",
|
|
@@ -4826,156 +5044,6 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
|
|
|
4826
5044
|
};
|
|
4827
5045
|
})()
|
|
4828
5046
|
`;
|
|
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
5047
|
function delay(ms) {
|
|
4980
5048
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4981
5049
|
}
|
|
@@ -5012,7 +5080,7 @@ async function executeScript(webContents, script) {
|
|
|
5012
5080
|
} catch {
|
|
5013
5081
|
return null;
|
|
5014
5082
|
} finally {
|
|
5015
|
-
if (
|
|
5083
|
+
if (timer) {
|
|
5016
5084
|
clearTimeout(timer);
|
|
5017
5085
|
}
|
|
5018
5086
|
}
|
|
@@ -5087,10 +5155,17 @@ function mergePageContent(candidates, webContents) {
|
|
|
5087
5155
|
headings: mergedBase.headings,
|
|
5088
5156
|
metaTags: mergedBase.metaTags
|
|
5089
5157
|
});
|
|
5158
|
+
const pageSchema = inferPageSchema({
|
|
5159
|
+
...mergedBase,
|
|
5160
|
+
structuredData: normalizedStructuredData,
|
|
5161
|
+
title: mergedBase.title || webContents.getTitle() || "",
|
|
5162
|
+
url: mergedBase.url || webContents.getURL() || ""
|
|
5163
|
+
});
|
|
5090
5164
|
return {
|
|
5091
5165
|
...mergedBase,
|
|
5092
5166
|
structuredData: normalizedStructuredData,
|
|
5093
5167
|
pageIssues,
|
|
5168
|
+
pageSchema,
|
|
5094
5169
|
title: mergedBase.title || webContents.getTitle() || "",
|
|
5095
5170
|
url: mergedBase.url || webContents.getURL() || ""
|
|
5096
5171
|
};
|
|
@@ -5116,13 +5191,12 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5116
5191
|
}
|
|
5117
5192
|
async function extractContentInner(webContents) {
|
|
5118
5193
|
await waitForDomReady(webContents);
|
|
5119
|
-
const [preloadResult, directResult
|
|
5194
|
+
const [preloadResult, directResult] = await Promise.all([
|
|
5120
5195
|
executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT),
|
|
5121
|
-
executeScript(webContents, DIRECT_EXTRACTION_SCRIPT)
|
|
5122
|
-
executeScript(webContents, SAFE_EXTRACTION_SCRIPT)
|
|
5196
|
+
executeScript(webContents, DIRECT_EXTRACTION_SCRIPT)
|
|
5123
5197
|
]);
|
|
5124
5198
|
return mergePageContent(
|
|
5125
|
-
[preloadResult, directResult
|
|
5199
|
+
[preloadResult, directResult],
|
|
5126
5200
|
webContents
|
|
5127
5201
|
);
|
|
5128
5202
|
}
|
|
@@ -5179,7 +5253,8 @@ function normalizePageContent(value) {
|
|
|
5179
5253
|
rdfa: Array.isArray(page.rdfa) ? page.rdfa : [],
|
|
5180
5254
|
metaTags: page.metaTags && typeof page.metaTags === "object" && !Array.isArray(page.metaTags) ? page.metaTags : {},
|
|
5181
5255
|
structuredData: Array.isArray(page.structuredData) ? page.structuredData : [],
|
|
5182
|
-
pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : []
|
|
5256
|
+
pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : [],
|
|
5257
|
+
pageSchema: page.pageSchema
|
|
5183
5258
|
};
|
|
5184
5259
|
}
|
|
5185
5260
|
const latestPageDiffs = /* @__PURE__ */ new Map();
|
|
@@ -5189,17 +5264,19 @@ const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
|
|
|
5189
5264
|
const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
|
|
5190
5265
|
const lastMutationActivityAt = /* @__PURE__ */ new Map();
|
|
5191
5266
|
const destroyListenerAttached = /* @__PURE__ */ new WeakSet();
|
|
5267
|
+
function cleanupTimersForWcId(wcId) {
|
|
5268
|
+
const timer = pendingPageSnapshotTimers.get(wcId);
|
|
5269
|
+
if (timer) clearTimeout(timer);
|
|
5270
|
+
pendingPageSnapshotTimers.delete(wcId);
|
|
5271
|
+
pendingPageSnapshotDueAt.delete(wcId);
|
|
5272
|
+
lastMutationSnapshotAt.delete(wcId);
|
|
5273
|
+
lastMutationActivityAt.delete(wcId);
|
|
5274
|
+
}
|
|
5192
5275
|
function attachDestroyCleanup(wc) {
|
|
5193
5276
|
if (destroyListenerAttached.has(wc)) return;
|
|
5194
5277
|
destroyListenerAttached.add(wc);
|
|
5195
5278
|
wc.once("destroyed", () => {
|
|
5196
|
-
|
|
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);
|
|
5279
|
+
cleanupTimersForWcId(wc.id);
|
|
5203
5280
|
});
|
|
5204
5281
|
}
|
|
5205
5282
|
const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
@@ -5271,8 +5348,7 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
|
5271
5348
|
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
5272
5349
|
if (existing) clearTimeout(existing);
|
|
5273
5350
|
const timer = setTimeout(() => {
|
|
5274
|
-
|
|
5275
|
-
pendingPageSnapshotDueAt.delete(wcId);
|
|
5351
|
+
cleanupTimersForWcId(wcId);
|
|
5276
5352
|
if (wc.isDestroyed()) return;
|
|
5277
5353
|
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
5278
5354
|
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
@@ -5596,9 +5672,9 @@ function resizeSidebarViews(state2) {
|
|
|
5596
5672
|
const contentWidth = width - sidebarWidth;
|
|
5597
5673
|
sidebarView.setBounds({
|
|
5598
5674
|
x: width - sidebarWidth - resizeHandleOverlap,
|
|
5599
|
-
y:
|
|
5675
|
+
y: chromeHeight,
|
|
5600
5676
|
width: sidebarWidth + resizeHandleOverlap,
|
|
5601
|
-
height
|
|
5677
|
+
height: height - chromeHeight
|
|
5602
5678
|
});
|
|
5603
5679
|
if (uiState.devtoolsPanelOpen) {
|
|
5604
5680
|
devtoolsPanelView.setBounds({
|
|
@@ -9761,7 +9837,17 @@ const TOOL_DEFINITIONS = [
|
|
|
9761
9837
|
folderSummary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
|
|
9762
9838
|
createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
|
|
9763
9839
|
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")
|
|
9840
|
+
onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder"),
|
|
9841
|
+
intent: zod.z.string().optional().describe(
|
|
9842
|
+
"Human-readable description of what this bookmark is for (e.g. 'expense reporting')"
|
|
9843
|
+
),
|
|
9844
|
+
expectedContent: zod.z.string().optional().describe(
|
|
9845
|
+
"Brief description of the content the agent should expect to find here"
|
|
9846
|
+
),
|
|
9847
|
+
keyFields: zod.z.array(zod.z.string()).optional().describe(
|
|
9848
|
+
"Important form field names for this page (e.g. ['receipt_id', 'date', 'amount'])"
|
|
9849
|
+
),
|
|
9850
|
+
agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
|
|
9765
9851
|
},
|
|
9766
9852
|
tier: 1
|
|
9767
9853
|
},
|
|
@@ -9780,7 +9866,11 @@ const TOOL_DEFINITIONS = [
|
|
|
9780
9866
|
folderSummary: zod.z.string().optional().describe("Optional summary for new folder"),
|
|
9781
9867
|
createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
|
|
9782
9868
|
note: zod.z.string().optional().describe("Optional note"),
|
|
9783
|
-
archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
|
|
9869
|
+
archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
|
|
9870
|
+
intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
|
|
9871
|
+
expectedContent: zod.z.string().optional().describe("Brief description of content the agent should expect"),
|
|
9872
|
+
keyFields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
|
|
9873
|
+
agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
|
|
9784
9874
|
},
|
|
9785
9875
|
tier: 2
|
|
9786
9876
|
},
|
|
@@ -10352,7 +10442,9 @@ const FIELD_WEIGHTS = {
|
|
|
10352
10442
|
note: 5,
|
|
10353
10443
|
folder: 3,
|
|
10354
10444
|
folderSummary: 2,
|
|
10355
|
-
url: 1
|
|
10445
|
+
url: 1,
|
|
10446
|
+
intent: 4,
|
|
10447
|
+
expectedContent: 3
|
|
10356
10448
|
};
|
|
10357
10449
|
function normalizeBookmarkSearchText(value) {
|
|
10358
10450
|
let normalized = value.toLowerCase();
|
|
@@ -10383,7 +10475,9 @@ function getBookmarkSearchMatch(args) {
|
|
|
10383
10475
|
url: args.url,
|
|
10384
10476
|
note: args.note,
|
|
10385
10477
|
folder: args.folder,
|
|
10386
|
-
folderSummary: args.folderSummary
|
|
10478
|
+
folderSummary: args.folderSummary,
|
|
10479
|
+
intent: args.intent,
|
|
10480
|
+
expectedContent: args.expectedContent
|
|
10387
10481
|
};
|
|
10388
10482
|
for (const field of Object.keys(values)) {
|
|
10389
10483
|
if (!bookmarkFieldMatchesQuery(values[field], normalizedQuery, tokens)) {
|
|
@@ -10523,7 +10617,9 @@ function searchBookmarks(query) {
|
|
|
10523
10617
|
url: bookmark.url,
|
|
10524
10618
|
note: bookmark.note,
|
|
10525
10619
|
folder: folder?.name,
|
|
10526
|
-
folderSummary: folder?.summary
|
|
10620
|
+
folderSummary: folder?.summary,
|
|
10621
|
+
intent: bookmark.intent,
|
|
10622
|
+
expectedContent: bookmark.expectedContent
|
|
10527
10623
|
});
|
|
10528
10624
|
return {
|
|
10529
10625
|
bookmark,
|
|
@@ -10603,6 +10699,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10603
10699
|
if (note !== void 0) {
|
|
10604
10700
|
bookmark2.note = note.trim() || void 0;
|
|
10605
10701
|
}
|
|
10702
|
+
if (options?.extra) {
|
|
10703
|
+
Object.assign(bookmark2, options.extra);
|
|
10704
|
+
}
|
|
10606
10705
|
bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10607
10706
|
save();
|
|
10608
10707
|
emit();
|
|
@@ -10618,7 +10717,8 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10618
10717
|
title: normalizedTitle,
|
|
10619
10718
|
note: note?.trim() || void 0,
|
|
10620
10719
|
folderId: targetId,
|
|
10621
|
-
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10720
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10721
|
+
...options?.extra
|
|
10622
10722
|
};
|
|
10623
10723
|
state$1.bookmarks.push(bookmark);
|
|
10624
10724
|
save();
|
|
@@ -10654,6 +10754,21 @@ function updateBookmark(id, updates) {
|
|
|
10654
10754
|
if (typeof updates.folderId === "string") {
|
|
10655
10755
|
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10656
10756
|
}
|
|
10757
|
+
if (typeof updates.intent === "string") {
|
|
10758
|
+
bookmark.intent = updates.intent.trim() || void 0;
|
|
10759
|
+
}
|
|
10760
|
+
if (typeof updates.expectedContent === "string") {
|
|
10761
|
+
bookmark.expectedContent = updates.expectedContent.trim() || void 0;
|
|
10762
|
+
}
|
|
10763
|
+
if (updates.keyFields !== void 0) {
|
|
10764
|
+
bookmark.keyFields = updates.keyFields;
|
|
10765
|
+
}
|
|
10766
|
+
if (updates.pageSchema !== void 0) {
|
|
10767
|
+
bookmark.pageSchema = updates.pageSchema;
|
|
10768
|
+
}
|
|
10769
|
+
if (updates.agentHints !== void 0) {
|
|
10770
|
+
bookmark.agentHints = updates.agentHints;
|
|
10771
|
+
}
|
|
10657
10772
|
save();
|
|
10658
10773
|
emit();
|
|
10659
10774
|
return { ...bookmark };
|
|
@@ -15558,7 +15673,17 @@ ${truncated}`;
|
|
|
15558
15673
|
source.title,
|
|
15559
15674
|
target.folderId,
|
|
15560
15675
|
note,
|
|
15561
|
-
{
|
|
15676
|
+
{
|
|
15677
|
+
onDuplicate,
|
|
15678
|
+
extra: {
|
|
15679
|
+
intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
|
|
15680
|
+
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15681
|
+
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15682
|
+
(f) => typeof f === "string"
|
|
15683
|
+
) : void 0,
|
|
15684
|
+
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15562
15687
|
);
|
|
15563
15688
|
if (result2.status === "conflict" && result2.existing) {
|
|
15564
15689
|
return composeFolderAwareResponse$1(
|
|
@@ -15597,7 +15722,13 @@ ${truncated}`;
|
|
|
15597
15722
|
const updated = updateBookmark(existing.id, {
|
|
15598
15723
|
folderId: target.folderId,
|
|
15599
15724
|
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
|
|
15600
|
-
note
|
|
15725
|
+
note,
|
|
15726
|
+
intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
|
|
15727
|
+
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15728
|
+
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15729
|
+
(f) => typeof f === "string"
|
|
15730
|
+
) : void 0,
|
|
15731
|
+
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15601
15732
|
});
|
|
15602
15733
|
if (!updated) {
|
|
15603
15734
|
return `Bookmark ${existing.id} not found`;
|
|
@@ -15608,12 +15739,25 @@ ${truncated}`;
|
|
|
15608
15739
|
);
|
|
15609
15740
|
}
|
|
15610
15741
|
if ("error" in source) return `Error: ${source.error}`;
|
|
15611
|
-
const
|
|
15742
|
+
const result2 = saveBookmarkWithPolicy(
|
|
15612
15743
|
source.url,
|
|
15613
15744
|
source.title,
|
|
15614
15745
|
target.folderId,
|
|
15615
|
-
note
|
|
15746
|
+
note,
|
|
15747
|
+
{
|
|
15748
|
+
onDuplicate: "update",
|
|
15749
|
+
extra: {
|
|
15750
|
+
intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
|
|
15751
|
+
expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
|
|
15752
|
+
keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
|
|
15753
|
+
(f) => typeof f === "string"
|
|
15754
|
+
) : void 0,
|
|
15755
|
+
agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
|
|
15756
|
+
}
|
|
15757
|
+
}
|
|
15616
15758
|
);
|
|
15759
|
+
const bookmark = result2.bookmark;
|
|
15760
|
+
if (!bookmark) return "Error: Bookmark save failed";
|
|
15617
15761
|
return composeFolderAwareResponse$1(
|
|
15618
15762
|
`Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
|
|
15619
15763
|
target.createdFolder
|
|
@@ -22798,7 +22942,7 @@ function registerWindowControlHandlers(mainWindow) {
|
|
|
22798
22942
|
});
|
|
22799
22943
|
}
|
|
22800
22944
|
let activeChatProvider = null;
|
|
22801
|
-
const VALID_APPROVAL_MODES =
|
|
22945
|
+
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
22802
22946
|
function registerIpcHandlers(windowState, runtime2) {
|
|
22803
22947
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
22804
22948
|
let sidebarResizeRecoveryTimer = null;
|
|
@@ -22807,10 +22951,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22807
22951
|
let pendingRuntimeState = null;
|
|
22808
22952
|
const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
|
|
22809
22953
|
const clearSidebarResizeRecoveryTimer = () => {
|
|
22810
|
-
if (sidebarResizeRecoveryTimer)
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
}
|
|
22954
|
+
if (!sidebarResizeRecoveryTimer) return;
|
|
22955
|
+
clearTimeout(sidebarResizeRecoveryTimer);
|
|
22956
|
+
sidebarResizeRecoveryTimer = null;
|
|
22814
22957
|
};
|
|
22815
22958
|
const restoreSidebarLayoutAfterResize = () => {
|
|
22816
22959
|
clearSidebarResizeRecoveryTimer();
|
|
@@ -23085,7 +23228,15 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23085
23228
|
sidebarResizeActive = true;
|
|
23086
23229
|
clearSidebarResizeRecoveryTimer();
|
|
23087
23230
|
const [width, height] = windowState.mainWindow.getContentSize();
|
|
23088
|
-
windowState.
|
|
23231
|
+
const chromeHeight = windowState.uiState.focusMode ? 0 : 110;
|
|
23232
|
+
const sidebarWidth = windowState.uiState.sidebarWidth;
|
|
23233
|
+
const resizeHandleOverlap = 6;
|
|
23234
|
+
windowState.sidebarView.setBounds({
|
|
23235
|
+
x: width - sidebarWidth - resizeHandleOverlap,
|
|
23236
|
+
y: chromeHeight,
|
|
23237
|
+
width: sidebarWidth + resizeHandleOverlap,
|
|
23238
|
+
height: height - chromeHeight
|
|
23239
|
+
});
|
|
23089
23240
|
scheduleSidebarResizeRecovery();
|
|
23090
23241
|
});
|
|
23091
23242
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (_, width) => {
|
|
@@ -23093,7 +23244,6 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23093
23244
|
const clamped = Math.max(240, Math.min(800, Math.round(width)));
|
|
23094
23245
|
windowState.uiState.sidebarWidth = clamped;
|
|
23095
23246
|
resizeSidebarViews(windowState);
|
|
23096
|
-
scheduleSidebarResizeRecovery();
|
|
23097
23247
|
return clamped;
|
|
23098
23248
|
});
|
|
23099
23249
|
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, () => {
|
|
@@ -23152,7 +23302,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23152
23302
|
Channels.AGENT_SET_APPROVAL_MODE,
|
|
23153
23303
|
(_, mode) => {
|
|
23154
23304
|
assertString(mode, "mode");
|
|
23155
|
-
if (!VALID_APPROVAL_MODES.
|
|
23305
|
+
if (!VALID_APPROVAL_MODES.includes(mode)) {
|
|
23156
23306
|
throw new Error(`Invalid approval mode: ${mode}`);
|
|
23157
23307
|
}
|
|
23158
23308
|
trackApprovalModeChanged(mode);
|
|
@@ -23192,9 +23342,16 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23192
23342
|
);
|
|
23193
23343
|
electron.ipcMain.handle(
|
|
23194
23344
|
Channels.BOOKMARK_SAVE,
|
|
23195
|
-
(_, url, title, folderId, note) => {
|
|
23345
|
+
(_, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
23196
23346
|
trackBookmarkAction("save");
|
|
23197
|
-
return
|
|
23347
|
+
return saveBookmarkWithPolicy(url, title, folderId, note, {
|
|
23348
|
+
extra: {
|
|
23349
|
+
intent: intent?.trim() || void 0,
|
|
23350
|
+
expectedContent: expectedContent?.trim() || void 0,
|
|
23351
|
+
keyFields,
|
|
23352
|
+
agentHints
|
|
23353
|
+
}
|
|
23354
|
+
});
|
|
23198
23355
|
}
|
|
23199
23356
|
);
|
|
23200
23357
|
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
|
|
@@ -23308,6 +23465,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23308
23465
|
}
|
|
23309
23466
|
}
|
|
23310
23467
|
findWiredWcId = wc.id;
|
|
23468
|
+
if (wc.isDestroyed()) return;
|
|
23311
23469
|
const listener = (_event, result) => {
|
|
23312
23470
|
if (!chromeView.webContents.isDestroyed()) {
|
|
23313
23471
|
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
@@ -23315,8 +23473,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23315
23473
|
};
|
|
23316
23474
|
findResultListener = listener;
|
|
23317
23475
|
wc.on("found-in-page", listener);
|
|
23476
|
+
const capturedWcId = wc.id;
|
|
23318
23477
|
wc.once("destroyed", () => {
|
|
23319
|
-
if (findWiredWcId ===
|
|
23478
|
+
if (findWiredWcId === capturedWcId) {
|
|
23320
23479
|
findWiredWcId = null;
|
|
23321
23480
|
findResultListener = null;
|
|
23322
23481
|
}
|
|
@@ -23424,9 +23583,20 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
23424
23583
|
sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
|
|
23425
23584
|
return state2;
|
|
23426
23585
|
});
|
|
23586
|
+
const PREMIUM_TRACKABLE_STEPS = [
|
|
23587
|
+
"chat_banner_viewed",
|
|
23588
|
+
"chat_banner_clicked",
|
|
23589
|
+
"settings_banner_viewed",
|
|
23590
|
+
"settings_banner_clicked",
|
|
23591
|
+
"welcome_banner_clicked",
|
|
23592
|
+
"premium_gate_seen",
|
|
23593
|
+
"premium_gate_clicked",
|
|
23594
|
+
"iteration_limit_seen",
|
|
23595
|
+
"iteration_limit_clicked"
|
|
23596
|
+
];
|
|
23427
23597
|
electron.ipcMain.handle(Channels.PREMIUM_TRACK_CONTEXT, (_, step) => {
|
|
23428
23598
|
assertString(step, "step");
|
|
23429
|
-
if (step
|
|
23599
|
+
if (PREMIUM_TRACKABLE_STEPS.includes(step)) {
|
|
23430
23600
|
trackPremiumFunnel(step);
|
|
23431
23601
|
}
|
|
23432
23602
|
});
|
package/out/preload/index.js
CHANGED
|
@@ -259,7 +259,17 @@ const api = {
|
|
|
259
259
|
},
|
|
260
260
|
bookmarks: {
|
|
261
261
|
get: () => electron.ipcRenderer.invoke(Channels.BOOKMARKS_GET),
|
|
262
|
-
saveBookmark: (url, title, folderId, note) => electron.ipcRenderer.invoke(
|
|
262
|
+
saveBookmark: (url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => electron.ipcRenderer.invoke(
|
|
263
|
+
Channels.BOOKMARK_SAVE,
|
|
264
|
+
url,
|
|
265
|
+
title,
|
|
266
|
+
folderId,
|
|
267
|
+
note,
|
|
268
|
+
intent,
|
|
269
|
+
expectedContent,
|
|
270
|
+
keyFields,
|
|
271
|
+
agentHints
|
|
272
|
+
),
|
|
263
273
|
removeBookmark: (id) => electron.ipcRenderer.invoke(Channels.BOOKMARK_REMOVE, id),
|
|
264
274
|
createFolder: (name) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name),
|
|
265
275
|
createFolderWithSummary: (name, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name, summary),
|
|
@@ -1491,32 +1491,34 @@ const [activeTabId, setActiveTabId] = createSignal("");
|
|
|
1491
1491
|
let initialized$4 = false;
|
|
1492
1492
|
let initPromise$3 = null;
|
|
1493
1493
|
let unsubscribeStateUpdate = null;
|
|
1494
|
+
async function doInit() {
|
|
1495
|
+
try {
|
|
1496
|
+
if (unsubscribeStateUpdate) {
|
|
1497
|
+
unsubscribeStateUpdate();
|
|
1498
|
+
unsubscribeStateUpdate = null;
|
|
1499
|
+
}
|
|
1500
|
+
unsubscribeStateUpdate = window.vessel.tabs.onStateUpdate(
|
|
1501
|
+
(newTabs, newActiveId) => {
|
|
1502
|
+
setTabs(newTabs);
|
|
1503
|
+
setActiveTabId(newActiveId);
|
|
1504
|
+
}
|
|
1505
|
+
);
|
|
1506
|
+
const initialState = await window.vessel.tabs.getState();
|
|
1507
|
+
setTabs(initialState.tabs);
|
|
1508
|
+
setActiveTabId(initialState.activeId);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
initialized$4 = false;
|
|
1511
|
+
console.error("Failed to initialize tabs store", error);
|
|
1512
|
+
throw error;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1494
1515
|
function init$4() {
|
|
1495
1516
|
if (initPromise$3) return initPromise$3;
|
|
1496
1517
|
if (initialized$4) return;
|
|
1497
1518
|
initialized$4 = true;
|
|
1498
|
-
initPromise$3 = (
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
unsubscribeStateUpdate();
|
|
1502
|
-
unsubscribeStateUpdate = null;
|
|
1503
|
-
}
|
|
1504
|
-
unsubscribeStateUpdate = window.vessel.tabs.onStateUpdate(
|
|
1505
|
-
(newTabs, newActiveId) => {
|
|
1506
|
-
setTabs(newTabs);
|
|
1507
|
-
setActiveTabId(newActiveId);
|
|
1508
|
-
}
|
|
1509
|
-
);
|
|
1510
|
-
const initialState = await window.vessel.tabs.getState();
|
|
1511
|
-
setTabs(initialState.tabs);
|
|
1512
|
-
setActiveTabId(initialState.activeId);
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
initialized$4 = false;
|
|
1515
|
-
console.error("Failed to initialize tabs store", error);
|
|
1516
|
-
} finally {
|
|
1517
|
-
initPromise$3 = null;
|
|
1518
|
-
}
|
|
1519
|
-
})();
|
|
1519
|
+
initPromise$3 = doInit().finally(() => {
|
|
1520
|
+
initPromise$3 = null;
|
|
1521
|
+
});
|
|
1520
1522
|
return initPromise$3;
|
|
1521
1523
|
}
|
|
1522
1524
|
const patchTab = (id, patch) => {
|
|
@@ -1867,29 +1869,8 @@ const [focusMode, setFocusMode] = createSignal(false);
|
|
|
1867
1869
|
const [commandBarOpen, setCommandBarOpen] = createSignal(false);
|
|
1868
1870
|
const [settingsOpen, setSettingsOpen] = createSignal(false);
|
|
1869
1871
|
const [devtoolsPanelOpen, setDevtoolsPanelOpen] = createSignal(false);
|
|
1870
|
-
let
|
|
1871
|
-
|
|
1872
|
-
async function flushResize() {
|
|
1873
|
-
if (resizeInFlight) {
|
|
1874
|
-
await resizeInFlight;
|
|
1875
|
-
if (pendingWidth !== null) {
|
|
1876
|
-
return flushResize();
|
|
1877
|
-
}
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
resizeInFlight = (async () => {
|
|
1881
|
-
while (pendingWidth !== null) {
|
|
1882
|
-
const nextWidth = pendingWidth;
|
|
1883
|
-
pendingWidth = null;
|
|
1884
|
-
await window.vessel.ui.resizeSidebar(nextWidth);
|
|
1885
|
-
}
|
|
1886
|
-
})();
|
|
1887
|
-
try {
|
|
1888
|
-
await resizeInFlight;
|
|
1889
|
-
} finally {
|
|
1890
|
-
resizeInFlight = null;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1872
|
+
let lastIpcTime = 0;
|
|
1873
|
+
const IPC_THROTTLE_MS = 8;
|
|
1893
1874
|
function useUI() {
|
|
1894
1875
|
return {
|
|
1895
1876
|
sidebarOpen,
|
|
@@ -1909,12 +1890,13 @@ function useUI() {
|
|
|
1909
1890
|
Math.min(MAX_SIDEBAR, Math.round(width))
|
|
1910
1891
|
);
|
|
1911
1892
|
setSidebarWidth(clamped);
|
|
1912
|
-
|
|
1913
|
-
|
|
1893
|
+
const now2 = performance.now();
|
|
1894
|
+
if (now2 - lastIpcTime >= IPC_THROTTLE_MS) {
|
|
1895
|
+
lastIpcTime = now2;
|
|
1896
|
+
void window.vessel.ui.resizeSidebar(clamped);
|
|
1897
|
+
}
|
|
1914
1898
|
},
|
|
1915
1899
|
commitResize: async () => {
|
|
1916
|
-
pendingWidth = sidebarWidth();
|
|
1917
|
-
await flushResize();
|
|
1918
1900
|
await window.vessel.ui.commitSidebarResize();
|
|
1919
1901
|
},
|
|
1920
1902
|
toggleFocusMode: async () => {
|
|
@@ -3533,7 +3515,16 @@ function useBookmarks() {
|
|
|
3533
3515
|
void init();
|
|
3534
3516
|
return {
|
|
3535
3517
|
bookmarksState,
|
|
3536
|
-
saveBookmark: (url, title, folderId, note) => window.vessel.bookmarks.saveBookmark(
|
|
3518
|
+
saveBookmark: (url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => window.vessel.bookmarks.saveBookmark(
|
|
3519
|
+
url,
|
|
3520
|
+
title,
|
|
3521
|
+
folderId,
|
|
3522
|
+
note,
|
|
3523
|
+
intent,
|
|
3524
|
+
expectedContent,
|
|
3525
|
+
keyFields,
|
|
3526
|
+
agentHints
|
|
3527
|
+
),
|
|
3537
3528
|
removeBookmark: (id) => window.vessel.bookmarks.removeBookmark(id),
|
|
3538
3529
|
createFolder: (name) => window.vessel.bookmarks.createFolder(name),
|
|
3539
3530
|
createFolderWithSummary: (name, summary) => window.vessel.bookmarks.createFolderWithSummary(name, summary),
|
|
@@ -4981,7 +4972,9 @@ const FIELD_WEIGHTS = {
|
|
|
4981
4972
|
note: 5,
|
|
4982
4973
|
folder: 3,
|
|
4983
4974
|
folderSummary: 2,
|
|
4984
|
-
url: 1
|
|
4975
|
+
url: 1,
|
|
4976
|
+
intent: 4,
|
|
4977
|
+
expectedContent: 3
|
|
4985
4978
|
};
|
|
4986
4979
|
function normalizeBookmarkSearchText(value) {
|
|
4987
4980
|
let normalized = value.toLowerCase();
|
|
@@ -5012,7 +5005,9 @@ function getBookmarkSearchMatch(args) {
|
|
|
5012
5005
|
url: args.url,
|
|
5013
5006
|
note: args.note,
|
|
5014
5007
|
folder: args.folder,
|
|
5015
|
-
folderSummary: args.folderSummary
|
|
5008
|
+
folderSummary: args.folderSummary,
|
|
5009
|
+
intent: args.intent,
|
|
5010
|
+
expectedContent: args.expectedContent
|
|
5016
5011
|
};
|
|
5017
5012
|
for (const field of Object.keys(values)) {
|
|
5018
5013
|
if (!bookmarkFieldMatchesQuery(values[field], normalizedQuery, tokens)) {
|
|
@@ -6671,6 +6666,18 @@ ${contextBlock}` : contextBlock);
|
|
|
6671
6666
|
const startX = e.screenX;
|
|
6672
6667
|
const startWidth = sidebarWidth2();
|
|
6673
6668
|
let finished = false;
|
|
6669
|
+
const state = {
|
|
6670
|
+
currentX: startX,
|
|
6671
|
+
rafId: null
|
|
6672
|
+
};
|
|
6673
|
+
const flushResizeUpdate = () => {
|
|
6674
|
+
state.rafId = null;
|
|
6675
|
+
if (finished) return;
|
|
6676
|
+
const totalDelta = startX - state.currentX;
|
|
6677
|
+
const targetWidth = startWidth + totalDelta;
|
|
6678
|
+
const newWidth = Math.max(240, Math.min(800, Math.round(targetWidth)));
|
|
6679
|
+
resizeSidebar(newWidth);
|
|
6680
|
+
};
|
|
6674
6681
|
const clearPointerTracking = () => {
|
|
6675
6682
|
window.removeEventListener("pointermove", onPointerMove);
|
|
6676
6683
|
window.removeEventListener("pointerup", onPointerUp);
|
|
@@ -6681,14 +6688,25 @@ ${contextBlock}` : contextBlock);
|
|
|
6681
6688
|
if (target.hasPointerCapture?.(e.pointerId)) {
|
|
6682
6689
|
target.releasePointerCapture(e.pointerId);
|
|
6683
6690
|
}
|
|
6691
|
+
if (state.rafId !== null) {
|
|
6692
|
+
cancelAnimationFrame(state.rafId);
|
|
6693
|
+
state.rafId = null;
|
|
6694
|
+
}
|
|
6684
6695
|
};
|
|
6685
6696
|
const onPointerMove = (ev) => {
|
|
6686
|
-
|
|
6687
|
-
|
|
6697
|
+
state.currentX = ev.screenX;
|
|
6698
|
+
if (state.rafId === null) {
|
|
6699
|
+
state.rafId = requestAnimationFrame(flushResizeUpdate);
|
|
6700
|
+
}
|
|
6688
6701
|
};
|
|
6689
6702
|
const finishResize = () => {
|
|
6690
6703
|
if (finished) return;
|
|
6691
6704
|
finished = true;
|
|
6705
|
+
if (state.rafId !== null) {
|
|
6706
|
+
cancelAnimationFrame(state.rafId);
|
|
6707
|
+
state.rafId = null;
|
|
6708
|
+
}
|
|
6709
|
+
flushResizeUpdate();
|
|
6692
6710
|
setIsDragging(false);
|
|
6693
6711
|
clearPointerTracking();
|
|
6694
6712
|
document.body.style.cursor = "";
|
|
@@ -10166,17 +10184,19 @@ const App = () => {
|
|
|
10166
10184
|
});
|
|
10167
10185
|
}
|
|
10168
10186
|
};
|
|
10169
|
-
const applyTheme =
|
|
10170
|
-
const s = await window.vessel.settings.get();
|
|
10171
|
-
const theme = s.theme ?? "dark";
|
|
10187
|
+
const applyTheme = (theme) => {
|
|
10172
10188
|
document.documentElement.setAttribute("data-theme", theme);
|
|
10173
10189
|
try {
|
|
10174
10190
|
localStorage.setItem("vessel:theme", theme);
|
|
10175
10191
|
} catch {
|
|
10176
10192
|
}
|
|
10177
10193
|
};
|
|
10194
|
+
const loadAndApplyTheme = async () => {
|
|
10195
|
+
const s = await window.vessel.settings.get();
|
|
10196
|
+
applyTheme(s.theme ?? "dark");
|
|
10197
|
+
};
|
|
10178
10198
|
onMount(() => {
|
|
10179
|
-
void
|
|
10199
|
+
void loadAndApplyTheme();
|
|
10180
10200
|
window.vessel.ui.rendererReady(view);
|
|
10181
10201
|
if (view !== "chrome") return;
|
|
10182
10202
|
const cleanupKeys = setupKeybindings({
|
|
@@ -10196,8 +10216,8 @@ const App = () => {
|
|
|
10196
10216
|
toggleKeyboardHelp: () => setKeyboardHelpOpen((v) => !v)
|
|
10197
10217
|
});
|
|
10198
10218
|
const cleanupCapture = window.vessel.highlights.onCaptureResult(showHighlightResult);
|
|
10199
|
-
const cleanupSettings = window.vessel.settings.onUpdate(() => {
|
|
10200
|
-
|
|
10219
|
+
const cleanupSettings = window.vessel.settings.onUpdate((settings) => {
|
|
10220
|
+
applyTheme(settings.theme ?? "dark");
|
|
10201
10221
|
});
|
|
10202
10222
|
onCleanup(() => {
|
|
10203
10223
|
cleanupKeys();
|
package/out/renderer/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:;" />
|
|
7
7
|
<title>Vessel</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-Di64dPm5.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-CBe7EN_l.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
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.
|
|
4
|
+
"version": "0.1.61",
|
|
5
5
|
"description": "AI-native web browser runtime for autonomous agents with human supervision",
|
|
6
6
|
"main": "./out/main/index.js",
|
|
7
7
|
"bin": {
|