@quanta-intellect/vessel-browser 0.1.103 → 0.1.114
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 +5 -5
- package/out/main/index.js +909 -576
- package/out/preload/content-script.js +20 -3
- package/out/preload/index.js +7 -0
- package/out/renderer/assets/{index-D3ABnKy4.js → index-CT4KWUML.js} +527 -357
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -1940,7 +1940,7 @@ function save$2() {
|
|
|
1940
1940
|
}
|
|
1941
1941
|
function emit$3() {
|
|
1942
1942
|
if (!state$4) return;
|
|
1943
|
-
const snapshot2 =
|
|
1943
|
+
const snapshot2 = listEntries$2();
|
|
1944
1944
|
for (const listener of listeners$1) {
|
|
1945
1945
|
listener(snapshot2);
|
|
1946
1946
|
}
|
|
@@ -1949,6 +1949,17 @@ function getState$1() {
|
|
|
1949
1949
|
load$3();
|
|
1950
1950
|
return { entries: [...state$4.entries] };
|
|
1951
1951
|
}
|
|
1952
|
+
function listEntries$2(offset = 0, limit = 200) {
|
|
1953
|
+
load$3();
|
|
1954
|
+
const safeOffset = Math.max(0, Math.floor(offset));
|
|
1955
|
+
const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
|
|
1956
|
+
return {
|
|
1957
|
+
entries: state$4.entries.slice(safeOffset, safeOffset + safeLimit),
|
|
1958
|
+
offset: safeOffset,
|
|
1959
|
+
limit: safeLimit,
|
|
1960
|
+
total: state$4.entries.length
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1952
1963
|
function subscribe$1(listener) {
|
|
1953
1964
|
listeners$1.add(listener);
|
|
1954
1965
|
return () => {
|
|
@@ -2851,6 +2862,7 @@ class TabManager {
|
|
|
2851
2862
|
pageLoadCallback = null;
|
|
2852
2863
|
securityStateCallback = null;
|
|
2853
2864
|
closedTabs = [];
|
|
2865
|
+
lastSessionSignature = "";
|
|
2854
2866
|
MAX_CLOSED_TABS = 20;
|
|
2855
2867
|
isPrivate;
|
|
2856
2868
|
sessionPartition;
|
|
@@ -2895,7 +2907,7 @@ class TabManager {
|
|
|
2895
2907
|
this.window.contentView.addChildView(tab.view);
|
|
2896
2908
|
if (background) {
|
|
2897
2909
|
tab.view.setBounds({ x: 0, y: 0, width: 0, height: 0 });
|
|
2898
|
-
this.broadcastState();
|
|
2910
|
+
this.broadcastState({ persistSession: true });
|
|
2899
2911
|
} else {
|
|
2900
2912
|
this.switchTab(id);
|
|
2901
2913
|
}
|
|
@@ -2910,7 +2922,7 @@ class TabManager {
|
|
|
2910
2922
|
}
|
|
2911
2923
|
}
|
|
2912
2924
|
this.activeTabId = id;
|
|
2913
|
-
this.broadcastState();
|
|
2925
|
+
this.broadcastState({ persistSession: true });
|
|
2914
2926
|
}
|
|
2915
2927
|
closeTab(id) {
|
|
2916
2928
|
const tab = this.tabs.get(id);
|
|
@@ -2942,7 +2954,7 @@ class TabManager {
|
|
|
2942
2954
|
this.createTab();
|
|
2943
2955
|
}
|
|
2944
2956
|
} else {
|
|
2945
|
-
this.broadcastState();
|
|
2957
|
+
this.broadcastState({ persistSession: true });
|
|
2946
2958
|
}
|
|
2947
2959
|
}
|
|
2948
2960
|
navigateTab(id, url, postBody) {
|
|
@@ -2989,7 +3001,7 @@ class TabManager {
|
|
|
2989
3001
|
} else {
|
|
2990
3002
|
this.order.splice(firstNonPinned, 0, id);
|
|
2991
3003
|
}
|
|
2992
|
-
this.broadcastState();
|
|
3004
|
+
this.broadcastState({ persistSession: true });
|
|
2993
3005
|
}
|
|
2994
3006
|
unpinTab(id) {
|
|
2995
3007
|
const tab = this.tabs.get(id);
|
|
@@ -3002,7 +3014,7 @@ class TabManager {
|
|
|
3002
3014
|
} else {
|
|
3003
3015
|
this.order.splice(firstNonPinned, 0, id);
|
|
3004
3016
|
}
|
|
3005
|
-
this.broadcastState();
|
|
3017
|
+
this.broadcastState({ persistSession: true });
|
|
3006
3018
|
}
|
|
3007
3019
|
createGroupFromTab(id, options) {
|
|
3008
3020
|
const tab = this.tabs.get(id);
|
|
@@ -3026,7 +3038,7 @@ class TabManager {
|
|
|
3026
3038
|
const previousGroupId = tab.state.groupId;
|
|
3027
3039
|
tab.setGroup(groupId);
|
|
3028
3040
|
this.removeGroupIfEmpty(previousGroupId);
|
|
3029
|
-
this.broadcastState();
|
|
3041
|
+
this.broadcastState({ persistSession: true });
|
|
3030
3042
|
}
|
|
3031
3043
|
removeTabFromGroup(id) {
|
|
3032
3044
|
const tab = this.tabs.get(id);
|
|
@@ -3034,20 +3046,20 @@ class TabManager {
|
|
|
3034
3046
|
const groupId = tab.state.groupId;
|
|
3035
3047
|
tab.setGroup(void 0);
|
|
3036
3048
|
this.removeGroupIfEmpty(groupId);
|
|
3037
|
-
this.broadcastState();
|
|
3049
|
+
this.broadcastState({ persistSession: true });
|
|
3038
3050
|
}
|
|
3039
3051
|
toggleGroupCollapsed(groupId) {
|
|
3040
3052
|
const group = this.tabGroups.get(groupId);
|
|
3041
3053
|
if (!group) return null;
|
|
3042
3054
|
group.collapsed = !group.collapsed;
|
|
3043
|
-
this.broadcastState();
|
|
3055
|
+
this.broadcastState({ persistSession: true });
|
|
3044
3056
|
return group.collapsed;
|
|
3045
3057
|
}
|
|
3046
3058
|
setGroupColor(groupId, color) {
|
|
3047
3059
|
const group = this.tabGroups.get(groupId);
|
|
3048
3060
|
if (!group || !TAB_GROUP_COLORS.includes(color)) return;
|
|
3049
3061
|
group.color = color;
|
|
3050
|
-
this.broadcastState();
|
|
3062
|
+
this.broadcastState({ persistSession: true });
|
|
3051
3063
|
}
|
|
3052
3064
|
toggleMuted(id) {
|
|
3053
3065
|
return this.tabs.get(id)?.toggleMuted() ?? null;
|
|
@@ -3207,7 +3219,7 @@ class TabManager {
|
|
|
3207
3219
|
this.order = [];
|
|
3208
3220
|
this.tabGroups.clear();
|
|
3209
3221
|
this.activeTabId = null;
|
|
3210
|
-
this.broadcastState();
|
|
3222
|
+
this.broadcastState({ persistSession: true });
|
|
3211
3223
|
}
|
|
3212
3224
|
lastReapply = /* @__PURE__ */ new Map();
|
|
3213
3225
|
reapplyHighlights(url, wc) {
|
|
@@ -3340,6 +3352,20 @@ class TabManager {
|
|
|
3340
3352
|
}
|
|
3341
3353
|
this.tabGroups.delete(groupId);
|
|
3342
3354
|
}
|
|
3355
|
+
getSessionSignature(states) {
|
|
3356
|
+
return JSON.stringify({
|
|
3357
|
+
activeTabId: this.activeTabId,
|
|
3358
|
+
tabs: states.map((state2) => ({
|
|
3359
|
+
id: state2.id,
|
|
3360
|
+
url: state2.url || "about:blank",
|
|
3361
|
+
adBlockingEnabled: state2.adBlockingEnabled,
|
|
3362
|
+
isPinned: state2.isPinned,
|
|
3363
|
+
groupId: state2.groupId,
|
|
3364
|
+
groupName: state2.groupName,
|
|
3365
|
+
groupColor: state2.groupColor
|
|
3366
|
+
}))
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3343
3369
|
async removeHighlightMarksForText(wc, text) {
|
|
3344
3370
|
await wc.executeJavaScript(
|
|
3345
3371
|
`(function() {
|
|
@@ -3358,9 +3384,12 @@ class TabManager {
|
|
|
3358
3384
|
(err) => logger$m.warn("Failed to remove highlight marks:", err)
|
|
3359
3385
|
);
|
|
3360
3386
|
}
|
|
3361
|
-
broadcastState() {
|
|
3387
|
+
broadcastState(meta = { persistSession: false }) {
|
|
3362
3388
|
const states = this.getAllStates();
|
|
3363
|
-
this.
|
|
3389
|
+
const sessionSignature = this.getSessionSignature(states);
|
|
3390
|
+
const persistSession = meta.persistSession || sessionSignature !== this.lastSessionSignature;
|
|
3391
|
+
this.lastSessionSignature = sessionSignature;
|
|
3392
|
+
this.onStateChange(states, this.activeTabId || "", { persistSession });
|
|
3364
3393
|
const activeTab = this.getActiveTab();
|
|
3365
3394
|
if (activeTab && this.activeTabId && this.securityStateCallback) {
|
|
3366
3395
|
this.securityStateCallback(this.activeTabId, activeTab.securityState);
|
|
@@ -3422,6 +3451,8 @@ const Channels = {
|
|
|
3422
3451
|
SETTINGS_HEALTH_GET: "settings:health:get",
|
|
3423
3452
|
SETTINGS_HEALTH_UPDATE: "settings:health:update",
|
|
3424
3453
|
MCP_REGENERATE_TOKEN: "mcp:regenerate-token",
|
|
3454
|
+
// Support
|
|
3455
|
+
SUPPORT_SUBMIT_FEEDBACK: "support:submit-feedback",
|
|
3425
3456
|
// Bookmarks
|
|
3426
3457
|
BOOKMARKS_GET: "bookmarks:get",
|
|
3427
3458
|
BOOKMARKS_UPDATE: "bookmarks:update",
|
|
@@ -3493,6 +3524,7 @@ const Channels = {
|
|
|
3493
3524
|
FIND_IN_PAGE_RESULT: "find:result",
|
|
3494
3525
|
// Browsing history
|
|
3495
3526
|
HISTORY_GET: "history:get",
|
|
3527
|
+
HISTORY_LIST: "history:list",
|
|
3496
3528
|
HISTORY_SEARCH: "history:search",
|
|
3497
3529
|
HISTORY_CLEAR: "history:clear",
|
|
3498
3530
|
HISTORY_UPDATE: "history:update",
|
|
@@ -3596,7 +3628,7 @@ const MAX_DIFF_BLOCKS = 500;
|
|
|
3596
3628
|
function normalizeText$2(value) {
|
|
3597
3629
|
return value.replace(/\s+/g, " ").trim();
|
|
3598
3630
|
}
|
|
3599
|
-
function truncateText(value, max = 180) {
|
|
3631
|
+
function truncateText$1(value, max = 180) {
|
|
3600
3632
|
const normalized = normalizeText$2(value);
|
|
3601
3633
|
if (normalized.length <= max) return normalized;
|
|
3602
3634
|
return `${normalized.slice(0, max - 3)}...`;
|
|
@@ -3770,10 +3802,10 @@ function diffSnapshots(oldSnap, currentContent, currentTitle, currentHeadings) {
|
|
|
3770
3802
|
addedBlocks.length,
|
|
3771
3803
|
removedBlocks.length
|
|
3772
3804
|
),
|
|
3773
|
-
before: changedPairs[0] ? truncateText(changedPairs[0].before) : removedBlocks[0] ? truncateText(removedBlocks[0]) : void 0,
|
|
3774
|
-
after: changedPairs[0] ? truncateText(changedPairs[0].after) : addedBlocks[0] ? truncateText(addedBlocks[0]) : void 0,
|
|
3775
|
-
addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item)),
|
|
3776
|
-
removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item))
|
|
3805
|
+
before: changedPairs[0] ? truncateText$1(changedPairs[0].before) : removedBlocks[0] ? truncateText$1(removedBlocks[0]) : void 0,
|
|
3806
|
+
after: changedPairs[0] ? truncateText$1(changedPairs[0].after) : addedBlocks[0] ? truncateText$1(addedBlocks[0]) : void 0,
|
|
3807
|
+
addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item)),
|
|
3808
|
+
removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item))
|
|
3777
3809
|
});
|
|
3778
3810
|
}
|
|
3779
3811
|
}
|
|
@@ -4591,18 +4623,28 @@ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4591
4623
|
"research_approve_objectives",
|
|
4592
4624
|
"research_export_report"
|
|
4593
4625
|
]);
|
|
4626
|
+
const PREMIUM_FEATURES = /* @__PURE__ */ new Set([
|
|
4627
|
+
"obsidian",
|
|
4628
|
+
"devtools",
|
|
4629
|
+
"unlimited_iterations",
|
|
4630
|
+
"vault",
|
|
4631
|
+
"human_vault",
|
|
4632
|
+
"automation_kits",
|
|
4633
|
+
"research"
|
|
4634
|
+
]);
|
|
4594
4635
|
function isPremium() {
|
|
4595
4636
|
const { premium } = loadSettings();
|
|
4596
|
-
if (premium.status
|
|
4637
|
+
if (premium.status !== "active" && premium.status !== "trialing") {
|
|
4638
|
+
return false;
|
|
4639
|
+
}
|
|
4640
|
+
if (!premium.validatedAt) {
|
|
4597
4641
|
return true;
|
|
4598
4642
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
return true;
|
|
4603
|
-
}
|
|
4643
|
+
const lastValidated = new Date(premium.validatedAt).getTime();
|
|
4644
|
+
if (!Number.isFinite(lastValidated)) {
|
|
4645
|
+
return false;
|
|
4604
4646
|
}
|
|
4605
|
-
return
|
|
4647
|
+
return Date.now() - lastValidated < OFFLINE_GRACE_PERIOD_MS;
|
|
4606
4648
|
}
|
|
4607
4649
|
function getPremiumState() {
|
|
4608
4650
|
return { ...loadSettings().premium };
|
|
@@ -4628,6 +4670,22 @@ function resetPremium() {
|
|
|
4628
4670
|
function isToolGated(toolName) {
|
|
4629
4671
|
return PREMIUM_TOOLS.has(toolName) && !isPremium();
|
|
4630
4672
|
}
|
|
4673
|
+
function isFeatureGated(featureName) {
|
|
4674
|
+
return PREMIUM_FEATURES.has(featureName) && !isPremium();
|
|
4675
|
+
}
|
|
4676
|
+
function getPremiumToolGateMessage(toolName) {
|
|
4677
|
+
return `This tool (${toolName}) requires Vessel Premium. Upgrade at Settings > Premium to unlock screenshot, session management, workflow tracking, and more.`;
|
|
4678
|
+
}
|
|
4679
|
+
function assertToolUnlocked(toolName) {
|
|
4680
|
+
if (isToolGated(toolName)) {
|
|
4681
|
+
throw new Error(getPremiumToolGateMessage(toolName));
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
function assertFeatureUnlocked(featureName, featureLabel = featureName) {
|
|
4685
|
+
if (isFeatureGated(featureName)) {
|
|
4686
|
+
throw new Error(`${featureLabel} requires Vessel Premium.`);
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4631
4689
|
async function getCheckoutUrl(email) {
|
|
4632
4690
|
try {
|
|
4633
4691
|
const params = new URLSearchParams();
|
|
@@ -4647,9 +4705,31 @@ async function getCheckoutUrl(email) {
|
|
|
4647
4705
|
}
|
|
4648
4706
|
}
|
|
4649
4707
|
async function getPortalUrl() {
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
)
|
|
4708
|
+
const current = loadSettings().premium;
|
|
4709
|
+
const identifier = current.verificationToken;
|
|
4710
|
+
if (!identifier) {
|
|
4711
|
+
return errorResult(
|
|
4712
|
+
"Verify your Premium subscription before opening billing management."
|
|
4713
|
+
);
|
|
4714
|
+
}
|
|
4715
|
+
try {
|
|
4716
|
+
const res = await fetch(`${VERIFICATION_API}/portal`, {
|
|
4717
|
+
method: "POST",
|
|
4718
|
+
headers: { "Content-Type": "application/json" },
|
|
4719
|
+
body: JSON.stringify({ identifier })
|
|
4720
|
+
});
|
|
4721
|
+
if (!res.ok) {
|
|
4722
|
+
const detail = await readApiErrorDetail(res);
|
|
4723
|
+
return errorResult(detail || `HTTP ${res.status}`);
|
|
4724
|
+
}
|
|
4725
|
+
const { url } = await res.json();
|
|
4726
|
+
if (typeof url !== "string" || !url.trim()) {
|
|
4727
|
+
return errorResult("Billing portal did not return a valid URL.");
|
|
4728
|
+
}
|
|
4729
|
+
return okResult({ url });
|
|
4730
|
+
} catch (err) {
|
|
4731
|
+
return errorResult(getErrorMessage(err, "Failed to open billing portal"));
|
|
4732
|
+
}
|
|
4653
4733
|
}
|
|
4654
4734
|
async function verifySubscription$1(identifier) {
|
|
4655
4735
|
const current = loadSettings().premium;
|
|
@@ -5297,6 +5377,17 @@ const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
|
5297
5377
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5298
5378
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5299
5379
|
const logger$k = createLogger("Extractor");
|
|
5380
|
+
const EXTRACTION_CACHE_TTL_MS = 1500;
|
|
5381
|
+
const MAX_EXTRACTION_CACHE_ENTRIES = 50;
|
|
5382
|
+
const extractionCache = /* @__PURE__ */ new Map();
|
|
5383
|
+
function invalidateExtractionCache(webContents) {
|
|
5384
|
+
const prefix = `${webContents.id}:`;
|
|
5385
|
+
for (const key2 of extractionCache.keys()) {
|
|
5386
|
+
if (key2.startsWith(prefix)) {
|
|
5387
|
+
extractionCache.delete(key2);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5300
5391
|
const EMPTY_PAGE_CONTENT = {
|
|
5301
5392
|
title: "",
|
|
5302
5393
|
content: "",
|
|
@@ -6183,9 +6274,14 @@ async function extractContentInner(webContents) {
|
|
|
6183
6274
|
);
|
|
6184
6275
|
}
|
|
6185
6276
|
async function extractContent(webContents) {
|
|
6277
|
+
const cacheKey = `${webContents.id}:${webContents.getURL() || ""}`;
|
|
6278
|
+
const cached = extractionCache.get(cacheKey);
|
|
6279
|
+
if (cached && Date.now() - cached.capturedAt < EXTRACTION_CACHE_TTL_MS) {
|
|
6280
|
+
return structuredClone(cached.content);
|
|
6281
|
+
}
|
|
6186
6282
|
try {
|
|
6187
6283
|
const timeoutMs = await estimateExtractionTimeout(webContents);
|
|
6188
|
-
|
|
6284
|
+
const content = await Promise.race([
|
|
6189
6285
|
extractContentInner(webContents),
|
|
6190
6286
|
new Promise(
|
|
6191
6287
|
(_, reject) => setTimeout(
|
|
@@ -6194,6 +6290,15 @@ async function extractContent(webContents) {
|
|
|
6194
6290
|
)
|
|
6195
6291
|
)
|
|
6196
6292
|
]);
|
|
6293
|
+
extractionCache.set(cacheKey, {
|
|
6294
|
+
capturedAt: Date.now(),
|
|
6295
|
+
content: structuredClone(content)
|
|
6296
|
+
});
|
|
6297
|
+
if (extractionCache.size > MAX_EXTRACTION_CACHE_ENTRIES) {
|
|
6298
|
+
const oldestKey = extractionCache.keys().next().value;
|
|
6299
|
+
if (oldestKey) extractionCache.delete(oldestKey);
|
|
6300
|
+
}
|
|
6301
|
+
return content;
|
|
6197
6302
|
} catch (err) {
|
|
6198
6303
|
const url = webContents.getURL() || "";
|
|
6199
6304
|
let domain = "unknown";
|
|
@@ -6288,6 +6393,7 @@ function attachDestroyCleanup(wc) {
|
|
|
6288
6393
|
const MAX_PERSISTED_DIFF_BURSTS = 50;
|
|
6289
6394
|
const MAX_HISTORY_DAYS = 30;
|
|
6290
6395
|
const SAVE_DEBOUNCE_MS$2 = 500;
|
|
6396
|
+
const BACKGROUND_DIFF_CAPTURE_DELAY_MS = 15e3;
|
|
6291
6397
|
function getHistoryFilePath() {
|
|
6292
6398
|
return path.join(electron.app.getPath("userData"), "vessel-page-diff-history.json");
|
|
6293
6399
|
}
|
|
@@ -6420,7 +6526,7 @@ function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
|
6420
6526
|
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
6421
6527
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
6422
6528
|
}
|
|
6423
|
-
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
6529
|
+
function scheduleTimerAt(wc, sendToRendererViews, dueAt, options = {}) {
|
|
6424
6530
|
attachDestroyCleanup(wc);
|
|
6425
6531
|
const wcId = wc.id;
|
|
6426
6532
|
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
@@ -6428,14 +6534,24 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
|
6428
6534
|
const timer = setTimeout(() => {
|
|
6429
6535
|
cleanupTimersForWcId(wcId);
|
|
6430
6536
|
if (wc.isDestroyed()) return;
|
|
6537
|
+
if (options.isActive && !options.isActive()) {
|
|
6538
|
+
scheduleTimerAt(
|
|
6539
|
+
wc,
|
|
6540
|
+
sendToRendererViews,
|
|
6541
|
+
Date.now() + BACKGROUND_DIFF_CAPTURE_DELAY_MS,
|
|
6542
|
+
options
|
|
6543
|
+
);
|
|
6544
|
+
return;
|
|
6545
|
+
}
|
|
6431
6546
|
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
6432
6547
|
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
6433
6548
|
}, Math.max(0, dueAt - Date.now()));
|
|
6434
6549
|
pendingPageSnapshotTimers.set(wcId, timer);
|
|
6435
6550
|
pendingPageSnapshotDueAt.set(wcId, dueAt);
|
|
6436
6551
|
}
|
|
6437
|
-
function notePageMutationActivity(wc, sendToRendererViews) {
|
|
6552
|
+
function notePageMutationActivity(wc, sendToRendererViews, options = {}) {
|
|
6438
6553
|
if (wc.isDestroyed()) return;
|
|
6554
|
+
if (options.isActive && !options.isActive()) return;
|
|
6439
6555
|
const wcId = wc.id;
|
|
6440
6556
|
const now = Date.now();
|
|
6441
6557
|
lastMutationActivityAt.set(wcId, now);
|
|
@@ -6443,18 +6559,19 @@ function notePageMutationActivity(wc, sendToRendererViews) {
|
|
|
6443
6559
|
if (existingDueAt == null) return;
|
|
6444
6560
|
const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
|
|
6445
6561
|
if (nextDueAt <= existingDueAt) return;
|
|
6446
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6562
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6447
6563
|
}
|
|
6448
|
-
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
6564
|
+
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0, options = {}) {
|
|
6449
6565
|
if (wc.isDestroyed()) return;
|
|
6450
6566
|
const wcId = wc.id;
|
|
6451
6567
|
const now = Date.now();
|
|
6452
|
-
const
|
|
6568
|
+
const effectiveDelayMs = options.isActive && !options.isActive() ? Math.max(delayMs, BACKGROUND_DIFF_CAPTURE_DELAY_MS) : delayMs;
|
|
6569
|
+
const nextDueAt = computeNextSnapshotDueAt(wcId, now, effectiveDelayMs);
|
|
6453
6570
|
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
6454
6571
|
if (existingDueAt != null && existingDueAt >= nextDueAt) {
|
|
6455
6572
|
return;
|
|
6456
6573
|
}
|
|
6457
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6574
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6458
6575
|
}
|
|
6459
6576
|
function enableClipboardShortcuts(view) {
|
|
6460
6577
|
view.webContents.on("before-input-event", (event, input) => {
|
|
@@ -6670,6 +6787,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
6670
6787
|
sidebarView.webContents.send(channel, ...args);
|
|
6671
6788
|
};
|
|
6672
6789
|
tabManager.onPageLoad((url, wc) => {
|
|
6790
|
+
const activeWc = tabManager.getActiveTab()?.view.webContents;
|
|
6791
|
+
if (activeWc?.id !== wc.id) return;
|
|
6673
6792
|
void capturePageSnapshot(url, wc, sendToRendererViews);
|
|
6674
6793
|
});
|
|
6675
6794
|
const state2 = {
|
|
@@ -7436,6 +7555,36 @@ const PROVIDERS = {
|
|
|
7436
7555
|
apiKeyHint: "Optional — only if your endpoint requires authentication"
|
|
7437
7556
|
}
|
|
7438
7557
|
};
|
|
7558
|
+
function parseModelSizeInBillions(model) {
|
|
7559
|
+
const match = model.toLowerCase().match(/(?:^|[:/_\-\s])(\d+(?:\.\d+)?)b(?:$|[:/_\-\s])/i);
|
|
7560
|
+
if (!match) return null;
|
|
7561
|
+
const parsed = Number(match[1]);
|
|
7562
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
7563
|
+
}
|
|
7564
|
+
function isLoopbackBaseUrl(baseUrl) {
|
|
7565
|
+
if (!baseUrl) return false;
|
|
7566
|
+
try {
|
|
7567
|
+
const url = new URL(baseUrl);
|
|
7568
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
7569
|
+
} catch {
|
|
7570
|
+
return false;
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
function resolveAgentToolProfile(config) {
|
|
7574
|
+
const providerId = config.id;
|
|
7575
|
+
const isLocalProvider = providerId === "ollama" || providerId === "llama_cpp" || providerId === "custom" && isLoopbackBaseUrl(config.baseUrl);
|
|
7576
|
+
if (!isLocalProvider) return "default";
|
|
7577
|
+
const sizeInBillions = parseModelSizeInBillions(config.model);
|
|
7578
|
+
if (sizeInBillions === null) {
|
|
7579
|
+
return "compact";
|
|
7580
|
+
}
|
|
7581
|
+
return sizeInBillions <= 14 ? "compact" : "default";
|
|
7582
|
+
}
|
|
7583
|
+
const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
|
|
7584
|
+
const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
7585
|
+
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7586
|
+
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7587
|
+
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7439
7588
|
const SAFE_TOOL_ALIASES = {
|
|
7440
7589
|
goto_url: "navigate",
|
|
7441
7590
|
go_to_url: "navigate",
|
|
@@ -7500,407 +7649,117 @@ function normalizeToolAlias(name) {
|
|
|
7500
7649
|
}
|
|
7501
7650
|
return name;
|
|
7502
7651
|
}
|
|
7503
|
-
function
|
|
7504
|
-
const
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7652
|
+
function stableToolSignature(name, args) {
|
|
7653
|
+
const canonicalArgs = canonicalizeArgsForTool(name, args);
|
|
7654
|
+
const sortedEntries = Object.entries(canonicalArgs).sort(
|
|
7655
|
+
([left], [right]) => left.localeCompare(right)
|
|
7656
|
+
);
|
|
7657
|
+
return JSON.stringify([name, sortedEntries]);
|
|
7508
7658
|
}
|
|
7509
|
-
function
|
|
7510
|
-
|
|
7659
|
+
function normalizeToolToken(value) {
|
|
7660
|
+
return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7661
|
+
}
|
|
7662
|
+
function canonicalizeUrlLike(value) {
|
|
7511
7663
|
try {
|
|
7512
|
-
const url = new URL(
|
|
7513
|
-
|
|
7664
|
+
const url = new URL(value.trim());
|
|
7665
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
7666
|
+
url.hostname = url.hostname.replace(/^www\./, "");
|
|
7667
|
+
url.hash = "";
|
|
7668
|
+
if (url.pathname.endsWith("/") && url.pathname !== "/") {
|
|
7669
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
7670
|
+
}
|
|
7671
|
+
return url.toString();
|
|
7672
|
+
}
|
|
7514
7673
|
} catch {
|
|
7515
|
-
return false;
|
|
7516
7674
|
}
|
|
7675
|
+
return value.trim();
|
|
7517
7676
|
}
|
|
7518
|
-
function
|
|
7519
|
-
const
|
|
7520
|
-
|
|
7521
|
-
if (
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
return "compact";
|
|
7677
|
+
function toLikelyUrl(value) {
|
|
7678
|
+
const trimmed = value.trim().replace(/^["']|["']$/g, "");
|
|
7679
|
+
if (!trimmed) return null;
|
|
7680
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
7681
|
+
if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
|
|
7682
|
+
return `https://${trimmed}`;
|
|
7525
7683
|
}
|
|
7526
|
-
return
|
|
7527
|
-
}
|
|
7528
|
-
const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
|
|
7529
|
-
const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
7530
|
-
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7531
|
-
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7532
|
-
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7533
|
-
const logger$j = createLogger("OpenAIProvider");
|
|
7534
|
-
function shouldDebugAgentLoop() {
|
|
7535
|
-
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7536
|
-
return value === "1" || value === "true";
|
|
7537
|
-
}
|
|
7538
|
-
function previewDebugValue(value, maxLength = 800) {
|
|
7539
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
7540
|
-
if (normalized.length <= maxLength) return normalized;
|
|
7541
|
-
return `${normalized.slice(0, maxLength)}…`;
|
|
7684
|
+
return null;
|
|
7542
7685
|
}
|
|
7543
|
-
function
|
|
7544
|
-
|
|
7686
|
+
function scalarArgsForTool(name, scalar) {
|
|
7687
|
+
const trimmed = scalar.trim();
|
|
7688
|
+
if (!trimmed) return null;
|
|
7689
|
+
if (name === "navigate") {
|
|
7690
|
+
const url = toLikelyUrl(trimmed);
|
|
7691
|
+
return url ? { url } : null;
|
|
7692
|
+
}
|
|
7693
|
+
if (name === "search") {
|
|
7694
|
+
return { query: trimmed.replace(/^["']|["']$/g, "") };
|
|
7695
|
+
}
|
|
7696
|
+
if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
|
|
7697
|
+
return { text: trimmed.replace(/^["']|["']$/g, "") };
|
|
7698
|
+
}
|
|
7699
|
+
if (name === "read_page") {
|
|
7700
|
+
const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
|
|
7701
|
+
if (mode) return { mode };
|
|
7702
|
+
}
|
|
7703
|
+
if (name === "save_bookmark") {
|
|
7704
|
+
const url = toLikelyUrl(trimmed);
|
|
7705
|
+
if (url) return { url };
|
|
7706
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
7707
|
+
if (lastSpace > 0) {
|
|
7708
|
+
const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
|
|
7709
|
+
if (maybeUrl) {
|
|
7710
|
+
return {
|
|
7711
|
+
url: maybeUrl,
|
|
7712
|
+
title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "")
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
return null;
|
|
7545
7718
|
}
|
|
7546
|
-
function
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
description: t.description ?? "",
|
|
7552
|
-
parameters: t.input_schema
|
|
7719
|
+
function firstStringArg(args, keys) {
|
|
7720
|
+
for (const key2 of keys) {
|
|
7721
|
+
const value = args[key2];
|
|
7722
|
+
if (typeof value === "string" && value.trim()) {
|
|
7723
|
+
return value.trim();
|
|
7553
7724
|
}
|
|
7554
|
-
}
|
|
7725
|
+
}
|
|
7726
|
+
return null;
|
|
7555
7727
|
}
|
|
7556
|
-
function
|
|
7557
|
-
|
|
7728
|
+
function normalizeElementTargetArgs(args) {
|
|
7729
|
+
const normalized = { ...args };
|
|
7730
|
+
if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
|
|
7731
|
+
normalized.index = Number(normalized.index.trim());
|
|
7732
|
+
}
|
|
7733
|
+
if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
|
|
7734
|
+
const selector = firstStringArg(normalized, [
|
|
7735
|
+
"cssSelector",
|
|
7736
|
+
"css_selector",
|
|
7737
|
+
"querySelector",
|
|
7738
|
+
"query_selector"
|
|
7739
|
+
]);
|
|
7740
|
+
if (selector) normalized.selector = selector;
|
|
7741
|
+
}
|
|
7742
|
+
if (typeof normalized.text !== "string" || !normalized.text.trim()) {
|
|
7743
|
+
const text = firstStringArg(normalized, [
|
|
7744
|
+
"label",
|
|
7745
|
+
"title",
|
|
7746
|
+
"name",
|
|
7747
|
+
"target",
|
|
7748
|
+
"element",
|
|
7749
|
+
"linkText",
|
|
7750
|
+
"link_text",
|
|
7751
|
+
"ariaLabel",
|
|
7752
|
+
"aria_label"
|
|
7753
|
+
]);
|
|
7754
|
+
if (text) normalized.text = text;
|
|
7755
|
+
}
|
|
7756
|
+
return normalized;
|
|
7558
7757
|
}
|
|
7559
|
-
function
|
|
7560
|
-
return
|
|
7758
|
+
function hasElementTarget(args) {
|
|
7759
|
+
return typeof args.index === "number" || typeof args.selector === "string" && args.selector.trim().length > 0 || typeof args.text === "string" && args.text.trim().length > 0;
|
|
7561
7760
|
}
|
|
7562
|
-
function
|
|
7563
|
-
|
|
7564
|
-
if (!supportsReasoningParam) return void 0;
|
|
7565
|
-
switch (effort) {
|
|
7566
|
-
case "off":
|
|
7567
|
-
if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
|
|
7568
|
-
return void 0;
|
|
7569
|
-
}
|
|
7570
|
-
return "none";
|
|
7571
|
-
case "low":
|
|
7572
|
-
return "low";
|
|
7573
|
-
case "medium":
|
|
7574
|
-
return "medium";
|
|
7575
|
-
case "high":
|
|
7576
|
-
return "high";
|
|
7577
|
-
case "max":
|
|
7578
|
-
return "xhigh";
|
|
7579
|
-
default:
|
|
7580
|
-
return void 0;
|
|
7581
|
-
}
|
|
7582
|
-
}
|
|
7583
|
-
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
7584
|
-
if (profile !== "compact") return null;
|
|
7585
|
-
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
7586
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
7587
|
-
return {
|
|
7588
|
-
role: "user",
|
|
7589
|
-
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
7590
|
-
Do not ask the user what they want next unless the request is genuinely ambiguous or blocked. After navigation or page reads, keep executing the same task.` + (stateReminder ? `
|
|
7591
|
-
${stateReminder}` : "") + (phaseReminder ? `
|
|
7592
|
-
${phaseReminder}` : "")
|
|
7593
|
-
};
|
|
7594
|
-
}
|
|
7595
|
-
function extractSingleGoalDomain(goal) {
|
|
7596
|
-
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
|
|
7597
|
-
if (!matches || matches.length !== 1) return null;
|
|
7598
|
-
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
7599
|
-
}
|
|
7600
|
-
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
7601
|
-
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
7602
|
-
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
7603
|
-
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
7604
|
-
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
7605
|
-
const assistant = assistantText.toLowerCase();
|
|
7606
|
-
const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
|
|
7607
|
-
const lines = [
|
|
7608
|
-
`The task is still in progress: ${userMessage}`,
|
|
7609
|
-
`Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
|
|
7610
|
-
];
|
|
7611
|
-
if (alreadyOnGoalSite) {
|
|
7612
|
-
lines.push(
|
|
7613
|
-
`You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
|
|
7614
|
-
);
|
|
7615
|
-
}
|
|
7616
|
-
if (stateReminder) {
|
|
7617
|
-
lines.push(stateReminder);
|
|
7618
|
-
}
|
|
7619
|
-
if (phaseReminder) {
|
|
7620
|
-
lines.push(phaseReminder);
|
|
7621
|
-
}
|
|
7622
|
-
return lines.join("\n");
|
|
7623
|
-
}
|
|
7624
|
-
function buildPhaseReminder(userMessage, assistantText) {
|
|
7625
|
-
const goal = userMessage.toLowerCase();
|
|
7626
|
-
const text = assistantText.toLowerCase();
|
|
7627
|
-
if (!goal || !text) return "";
|
|
7628
|
-
const wantsCart = /\b(cart|bag|basket|checkout)\b/.test(goal);
|
|
7629
|
-
const wantsExplanation = /\b(explain|reason|why)\b/.test(goal);
|
|
7630
|
-
const wantsBookRecommendations = /\b(book|books|recommend|recommended|interesting|novel|fiction|nonfiction)\b/.test(
|
|
7631
|
-
goal
|
|
7632
|
-
);
|
|
7633
|
-
const hasFiveItemList = /(?:^|\n)\s*1\./.test(assistantText) && /(?:^|\n)\s*2\./.test(assistantText) && /(?:^|\n)\s*3\./.test(assistantText) && /(?:^|\n)\s*4\./.test(assistantText) && /(?:^|\n)\s*5\./.test(assistantText);
|
|
7634
|
-
const selectedItems = hasFiveItemList || /i(?:'| a)?ve chosen/.test(text) || /i have chosen/.test(text) || /i selected/.test(text) || /here are the books/i.test(assistantText) || /here are the items/i.test(assistantText);
|
|
7635
|
-
const intendsCart = /next[, ]+i will add/.test(text) || /i(?:'| a)?ll start with the first/.test(text) || /proceed systematically/.test(text) || /add (these|the chosen|the selected).*(cart|bag|basket)/.test(text);
|
|
7636
|
-
const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
|
|
7637
|
-
text
|
|
7638
|
-
);
|
|
7639
|
-
const explanationDone = /here is why i chose/.test(text) || /here are my reasons/.test(text) || /reason:/.test(text) || /reasons:/.test(text) || /why i chose/.test(text);
|
|
7640
|
-
const listingLoopSignals = /page contains a list of books|book listings|book cards|visible book|load more results|scroll further|scroll down|inspect the visible|focus on the book listings|targeting the book images|limited to interactive elements|identify the book cards|click one of the visible book/.test(
|
|
7641
|
-
text
|
|
7642
|
-
);
|
|
7643
|
-
const missedResultsSignals = /visible_only mode did not return specific book titles|did not yield a book title link|did not yield specific book titles|navigation links rather than book titles|inspect elements did not yield|inspect the page to find a specific book title|inspect the page to locate a book title|book title link from the search results/.test(
|
|
7644
|
-
text
|
|
7645
|
-
);
|
|
7646
|
-
const falseCartSuccessSignals = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
|
|
7647
|
-
text
|
|
7648
|
-
) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
|
|
7649
|
-
text
|
|
7650
|
-
);
|
|
7651
|
-
const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
|
|
7652
|
-
text
|
|
7653
|
-
) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
|
|
7654
|
-
text
|
|
7655
|
-
);
|
|
7656
|
-
const selectedItemsRestartSignals = /navigate back to the search results page|search for ".*" directly in the search box|search for .* directly|page structure has shifted|refresh the page|restart search/.test(
|
|
7657
|
-
text
|
|
7658
|
-
);
|
|
7659
|
-
const multiClickSelectionSignals = /i(?:'| a)?ll start by clicking on the following books|i will start by clicking on the following books|i will click on the following books|clicked on five different book titles|clicked on \d+ different book titles|clicking through the selected titles|click each of the selected titles/.test(
|
|
7660
|
-
text
|
|
7661
|
-
);
|
|
7662
|
-
const staleSelectionSignals = /cannot locate the elements to click|page structure is not being reliably captured|specific titles failed|page may have changed|stale-index|no visible area|not visible/.test(
|
|
7663
|
-
text
|
|
7664
|
-
);
|
|
7665
|
-
const intermediateCartDialogSignals = /(added to cart|has been added to the cart|cart confirmation)/.test(text) && /(continue shopping|search results page|return to the search results page|back button|go back)/.test(
|
|
7666
|
-
text
|
|
7667
|
-
) && !/(all requested books are now in the cart|all 5 books are now in the cart|5 of 5 requested books are now in the cart)/.test(
|
|
7668
|
-
text
|
|
7669
|
-
);
|
|
7670
|
-
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
|
|
7671
|
-
return `Progress reminder: If product results or primary results are already visible, do not keep rereading or rescrolling the same listing page. Open one promising result now. On the detail page, add that item to the cart before returning for the next unseen result.`;
|
|
7672
|
-
}
|
|
7673
|
-
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
|
|
7674
|
-
return `Progress reminder: On a results page, do not use visible_only or generic inspect_element to hunt product results. Call read_page(mode="results_only") once. If Primary Results are shown, click a listed result directly.`;
|
|
7675
|
-
}
|
|
7676
|
-
if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
|
|
7677
|
-
return `Progress reminder: Do not assume an item was added just because its product page is open or you inspected it. Only treat the cart step as complete after a successful Add to Cart click followed by cart confirmation, View Cart, Continue Shopping, or the cart page itself.`;
|
|
7678
|
-
}
|
|
7679
|
-
if (wantsCart && skippedSingleResultSignals && !selectedItems) {
|
|
7680
|
-
return `Progress reminder: Do not skip to a new query just because the match is not exact. If the results page shows even one plausible product result, inspect or click that result before concluding there is no match.`;
|
|
7681
|
-
}
|
|
7682
|
-
if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
|
|
7683
|
-
return `Progress reminder: After an Add to Cart success, prefer the cart-confirmation dialog action Continue Shopping while more items remain. Do not click View Cart or Go to Basket yet, and do not use the browser back button while the dialog is still open.`;
|
|
7684
|
-
}
|
|
7685
|
-
if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
|
|
7686
|
-
return `Progress reminder: The chosen items are already decided. Do not restart search, refresh the results page, or navigate back to browse again unless a specific saved link fails. Use the current results page or the chosen result links you already have: open one chosen result, add it to the cart, confirm success, then continue to the next chosen result.`;
|
|
7687
|
-
}
|
|
7688
|
-
if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
|
|
7689
|
-
return `Progress reminder: Do not batch-click multiple results from a listing or category page. Open exactly one visible result, finish that item's Add to Cart flow, confirm success, then use Continue Shopping or go back once to choose the next unseen result. If a remembered label or index fails, trust the latest page state and refresh it with one read_page call before continuing.`;
|
|
7690
|
-
}
|
|
7691
|
-
if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
|
|
7692
|
-
return `Progress reminder: You already selected the requested items. Do not restart browsing or searching unless a specific cart step fails. Continue adding the selected items to the cart one by one. Use the chosen result links you already have, add one selected item to the cart, confirm success, then continue to the next one. Do not click multiple chosen results in a row from the same listing page.`;
|
|
7693
|
-
}
|
|
7694
|
-
if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
|
|
7695
|
-
return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
|
|
7696
|
-
}
|
|
7697
|
-
return "";
|
|
7698
|
-
}
|
|
7699
|
-
function buildLatestStateReminder(toolResultPreview) {
|
|
7700
|
-
const text = toolResultPreview.trim();
|
|
7701
|
-
if (!text) return "";
|
|
7702
|
-
const stateMatch = text.match(
|
|
7703
|
-
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
7704
|
-
);
|
|
7705
|
-
if (stateMatch) {
|
|
7706
|
-
const url = stateMatch[1]?.trim();
|
|
7707
|
-
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
7708
|
-
if (url) {
|
|
7709
|
-
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7710
|
-
}
|
|
7711
|
-
}
|
|
7712
|
-
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
7713
|
-
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
7714
|
-
if (structuredUrl) {
|
|
7715
|
-
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7716
|
-
}
|
|
7717
|
-
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
7718
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
7719
|
-
if (navigatedUrl) {
|
|
7720
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7721
|
-
}
|
|
7722
|
-
return "";
|
|
7723
|
-
}
|
|
7724
|
-
function shouldRecoverCompactStall(text, userMessage) {
|
|
7725
|
-
const trimmed = text.trim().toLowerCase();
|
|
7726
|
-
if (!trimmed) return true;
|
|
7727
|
-
if (trimmed.length <= 160 && trimmed.includes("?")) return true;
|
|
7728
|
-
if (userMessage && buildPhaseReminder(userMessage, text)) {
|
|
7729
|
-
return true;
|
|
7730
|
-
}
|
|
7731
|
-
const repetitivePlanningSignals = [
|
|
7732
|
-
"next step:",
|
|
7733
|
-
"i will now inspect",
|
|
7734
|
-
"i will now read",
|
|
7735
|
-
"i will now click",
|
|
7736
|
-
"i'll use readpage",
|
|
7737
|
-
"i'll use read_page",
|
|
7738
|
-
"i'll start by clicking",
|
|
7739
|
-
"i have clicked on five different book titles",
|
|
7740
|
-
"clicked on five different book titles",
|
|
7741
|
-
"i'll begin with",
|
|
7742
|
-
"if the selection is unclear"
|
|
7743
|
-
];
|
|
7744
|
-
if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
7745
|
-
return true;
|
|
7746
|
-
}
|
|
7747
|
-
const falseCartSuccessWithoutConfirmation = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
|
|
7748
|
-
trimmed
|
|
7749
|
-
) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
|
|
7750
|
-
trimmed
|
|
7751
|
-
);
|
|
7752
|
-
if (falseCartSuccessWithoutConfirmation) {
|
|
7753
|
-
return true;
|
|
7754
|
-
}
|
|
7755
|
-
const completionSignals = [
|
|
7756
|
-
"i found",
|
|
7757
|
-
"i chose",
|
|
7758
|
-
"i selected",
|
|
7759
|
-
"i added",
|
|
7760
|
-
"here are",
|
|
7761
|
-
"these are",
|
|
7762
|
-
"recommendations",
|
|
7763
|
-
"reasoning",
|
|
7764
|
-
"why i chose",
|
|
7765
|
-
"added them to the cart"
|
|
7766
|
-
];
|
|
7767
|
-
if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
7768
|
-
return false;
|
|
7769
|
-
}
|
|
7770
|
-
return [
|
|
7771
|
-
"what are you hoping",
|
|
7772
|
-
"what would you like",
|
|
7773
|
-
"how can i help",
|
|
7774
|
-
"let me know",
|
|
7775
|
-
"are you looking for",
|
|
7776
|
-
"just browsing",
|
|
7777
|
-
"i need to",
|
|
7778
|
-
"i will",
|
|
7779
|
-
"i'll",
|
|
7780
|
-
"since i cannot see",
|
|
7781
|
-
"since i can't see",
|
|
7782
|
-
"cannot see the current page",
|
|
7783
|
-
"scroll down to",
|
|
7784
|
-
"load more results",
|
|
7785
|
-
"as placeholders",
|
|
7786
|
-
"would you like me to proceed",
|
|
7787
|
-
"action:",
|
|
7788
|
-
"one moment",
|
|
7789
|
-
"i will now navigate",
|
|
7790
|
-
"navigating to ",
|
|
7791
|
-
"this will take me",
|
|
7792
|
-
"i will use the browser"
|
|
7793
|
-
].some((pattern) => trimmed.includes(pattern));
|
|
7794
|
-
}
|
|
7795
|
-
function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
|
|
7796
|
-
return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
|
|
7797
|
-
}
|
|
7798
|
-
function stableToolSignature(name, args) {
|
|
7799
|
-
const canonicalArgs = canonicalizeArgsForTool(name, args);
|
|
7800
|
-
const sortedEntries = Object.entries(canonicalArgs).sort(
|
|
7801
|
-
([left], [right]) => left.localeCompare(right)
|
|
7802
|
-
);
|
|
7803
|
-
return JSON.stringify([name, sortedEntries]);
|
|
7804
|
-
}
|
|
7805
|
-
function normalizeToolToken(value) {
|
|
7806
|
-
return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7807
|
-
}
|
|
7808
|
-
function canonicalizeUrlLike(value) {
|
|
7809
|
-
try {
|
|
7810
|
-
const url = new URL(value.trim());
|
|
7811
|
-
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
7812
|
-
url.hostname = url.hostname.replace(/^www\./, "");
|
|
7813
|
-
url.hash = "";
|
|
7814
|
-
if (url.pathname.endsWith("/") && url.pathname !== "/") {
|
|
7815
|
-
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
7816
|
-
}
|
|
7817
|
-
return url.toString();
|
|
7818
|
-
}
|
|
7819
|
-
} catch {
|
|
7820
|
-
}
|
|
7821
|
-
return value.trim();
|
|
7822
|
-
}
|
|
7823
|
-
function toLikelyUrl(value) {
|
|
7824
|
-
const trimmed = value.trim().replace(/^["']|["']$/g, "");
|
|
7825
|
-
if (!trimmed) return null;
|
|
7826
|
-
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
7827
|
-
if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
|
|
7828
|
-
return `https://${trimmed}`;
|
|
7829
|
-
}
|
|
7830
|
-
return null;
|
|
7831
|
-
}
|
|
7832
|
-
function scalarArgsForTool(name, scalar) {
|
|
7833
|
-
const trimmed = scalar.trim();
|
|
7834
|
-
if (!trimmed) return null;
|
|
7835
|
-
if (name === "navigate") {
|
|
7836
|
-
const url = toLikelyUrl(trimmed);
|
|
7837
|
-
return url ? { url } : null;
|
|
7838
|
-
}
|
|
7839
|
-
if (name === "search") {
|
|
7840
|
-
return { query: trimmed.replace(/^["']|["']$/g, "") };
|
|
7841
|
-
}
|
|
7842
|
-
if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
|
|
7843
|
-
return { text: trimmed.replace(/^["']|["']$/g, "") };
|
|
7844
|
-
}
|
|
7845
|
-
if (name === "read_page") {
|
|
7846
|
-
const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
|
|
7847
|
-
if (mode) return { mode };
|
|
7848
|
-
}
|
|
7849
|
-
if (name === "save_bookmark") {
|
|
7850
|
-
const url = toLikelyUrl(trimmed);
|
|
7851
|
-
if (url) return { url };
|
|
7852
|
-
const lastSpace = trimmed.lastIndexOf(" ");
|
|
7853
|
-
if (lastSpace > 0) {
|
|
7854
|
-
const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
|
|
7855
|
-
if (maybeUrl) return { url: maybeUrl, title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "") };
|
|
7856
|
-
}
|
|
7857
|
-
}
|
|
7858
|
-
return null;
|
|
7859
|
-
}
|
|
7860
|
-
function firstStringArg(args, keys) {
|
|
7861
|
-
for (const key2 of keys) {
|
|
7862
|
-
const value = args[key2];
|
|
7863
|
-
if (typeof value === "string" && value.trim()) {
|
|
7864
|
-
return value.trim();
|
|
7865
|
-
}
|
|
7866
|
-
}
|
|
7867
|
-
return null;
|
|
7868
|
-
}
|
|
7869
|
-
function normalizeElementTargetArgs(args) {
|
|
7870
|
-
const normalized = { ...args };
|
|
7871
|
-
if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
|
|
7872
|
-
normalized.index = Number(normalized.index.trim());
|
|
7873
|
-
}
|
|
7874
|
-
if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
|
|
7875
|
-
const selector = firstStringArg(normalized, [
|
|
7876
|
-
"cssSelector",
|
|
7877
|
-
"css_selector",
|
|
7878
|
-
"querySelector",
|
|
7879
|
-
"query_selector"
|
|
7880
|
-
]);
|
|
7881
|
-
if (selector) normalized.selector = selector;
|
|
7882
|
-
}
|
|
7883
|
-
if (typeof normalized.text !== "string" || !normalized.text.trim()) {
|
|
7884
|
-
const text = firstStringArg(normalized, [
|
|
7885
|
-
"label",
|
|
7886
|
-
"title",
|
|
7887
|
-
"name",
|
|
7888
|
-
"target",
|
|
7889
|
-
"element",
|
|
7890
|
-
"linkText",
|
|
7891
|
-
"link_text",
|
|
7892
|
-
"ariaLabel",
|
|
7893
|
-
"aria_label"
|
|
7894
|
-
]);
|
|
7895
|
-
if (text) normalized.text = text;
|
|
7896
|
-
}
|
|
7897
|
-
return normalized;
|
|
7898
|
-
}
|
|
7899
|
-
function hasElementTarget(args) {
|
|
7900
|
-
return typeof args.index === "number" || typeof args.selector === "string" && args.selector.trim().length > 0 || typeof args.text === "string" && args.text.trim().length > 0;
|
|
7901
|
-
}
|
|
7902
|
-
function isTargetlessClickArgs(args) {
|
|
7903
|
-
return !hasElementTarget(normalizeElementTargetArgs(args));
|
|
7761
|
+
function isTargetlessClickArgs(args) {
|
|
7762
|
+
return !hasElementTarget(normalizeElementTargetArgs(args));
|
|
7904
7763
|
}
|
|
7905
7764
|
function tryParseJsonWithCommonRepairs(raw) {
|
|
7906
7765
|
const trimmed = raw.trim();
|
|
@@ -8044,21 +7903,13 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
8044
7903
|
if (availableToolNames.has("search") && (/search|find|lookup|query/.test(normalized) || normalized === "google" || normalized.startsWith("google_"))) {
|
|
8045
7904
|
return "search";
|
|
8046
7905
|
}
|
|
8047
|
-
if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
|
|
8048
|
-
return "scroll";
|
|
8049
|
-
}
|
|
8050
|
-
if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
|
|
8051
|
-
return "read_page";
|
|
8052
|
-
}
|
|
8053
|
-
return aliased;
|
|
8054
|
-
}
|
|
8055
|
-
function logAgentLoopDebug(payload) {
|
|
8056
|
-
if (!shouldDebugAgentLoop()) return;
|
|
8057
|
-
try {
|
|
8058
|
-
logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
8059
|
-
} catch (err) {
|
|
8060
|
-
logger$j.warn("Failed to serialize debug payload:", err);
|
|
7906
|
+
if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
|
|
7907
|
+
return "scroll";
|
|
7908
|
+
}
|
|
7909
|
+
if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
|
|
7910
|
+
return "read_page";
|
|
8061
7911
|
}
|
|
7912
|
+
return aliased;
|
|
8062
7913
|
}
|
|
8063
7914
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
8064
7915
|
const trimmed = text.trim();
|
|
@@ -8175,7 +8026,280 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
|
|
|
8175
8026
|
});
|
|
8176
8027
|
return recovered;
|
|
8177
8028
|
}
|
|
8178
|
-
return recovered;
|
|
8029
|
+
return recovered;
|
|
8030
|
+
}
|
|
8031
|
+
const logger$j = createLogger("OpenAIProvider");
|
|
8032
|
+
function shouldDebugAgentLoop() {
|
|
8033
|
+
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
8034
|
+
return value === "1" || value === "true";
|
|
8035
|
+
}
|
|
8036
|
+
function previewDebugValue(value, maxLength = 800) {
|
|
8037
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
8038
|
+
if (normalized.length <= maxLength) return normalized;
|
|
8039
|
+
return `${normalized.slice(0, maxLength)}…`;
|
|
8040
|
+
}
|
|
8041
|
+
function previewToolDebugContent(content) {
|
|
8042
|
+
return previewDebugValue(content, 500);
|
|
8043
|
+
}
|
|
8044
|
+
function toOpenAITools(tools) {
|
|
8045
|
+
return tools.map((t) => ({
|
|
8046
|
+
type: "function",
|
|
8047
|
+
function: {
|
|
8048
|
+
name: t.name,
|
|
8049
|
+
description: t.description ?? "",
|
|
8050
|
+
parameters: t.input_schema
|
|
8051
|
+
}
|
|
8052
|
+
}));
|
|
8053
|
+
}
|
|
8054
|
+
function agentTemperatureForProfile(profile) {
|
|
8055
|
+
return profile === "compact" ? 0.2 : void 0;
|
|
8056
|
+
}
|
|
8057
|
+
function modelLikelySupportsOpenAIReasoningEffort(model) {
|
|
8058
|
+
return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
|
|
8059
|
+
}
|
|
8060
|
+
function toOpenAIReasoningEffort(effort, providerId, model) {
|
|
8061
|
+
const supportsReasoningParam = providerId === "openrouter" || providerId === "custom" || providerId === "openai" && modelLikelySupportsOpenAIReasoningEffort(model);
|
|
8062
|
+
if (!supportsReasoningParam) return void 0;
|
|
8063
|
+
switch (effort) {
|
|
8064
|
+
case "off":
|
|
8065
|
+
if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
|
|
8066
|
+
return void 0;
|
|
8067
|
+
}
|
|
8068
|
+
return "none";
|
|
8069
|
+
case "low":
|
|
8070
|
+
return "low";
|
|
8071
|
+
case "medium":
|
|
8072
|
+
return "medium";
|
|
8073
|
+
case "high":
|
|
8074
|
+
return "high";
|
|
8075
|
+
case "max":
|
|
8076
|
+
return "xhigh";
|
|
8077
|
+
default:
|
|
8078
|
+
return void 0;
|
|
8079
|
+
}
|
|
8080
|
+
}
|
|
8081
|
+
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
8082
|
+
if (profile !== "compact") return null;
|
|
8083
|
+
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
8084
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
8085
|
+
return {
|
|
8086
|
+
role: "user",
|
|
8087
|
+
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
8088
|
+
Do not ask the user what they want next unless the request is genuinely ambiguous or blocked. After navigation or page reads, keep executing the same task.` + (stateReminder ? `
|
|
8089
|
+
${stateReminder}` : "") + (phaseReminder ? `
|
|
8090
|
+
${phaseReminder}` : "")
|
|
8091
|
+
};
|
|
8092
|
+
}
|
|
8093
|
+
function extractSingleGoalDomain(goal) {
|
|
8094
|
+
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
|
|
8095
|
+
if (!matches || matches.length !== 1) return null;
|
|
8096
|
+
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
8097
|
+
}
|
|
8098
|
+
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
8099
|
+
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
8100
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
8101
|
+
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
8102
|
+
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
8103
|
+
const assistant = assistantText.toLowerCase();
|
|
8104
|
+
const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
|
|
8105
|
+
const lines = [
|
|
8106
|
+
`The task is still in progress: ${userMessage}`,
|
|
8107
|
+
`Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
|
|
8108
|
+
];
|
|
8109
|
+
if (alreadyOnGoalSite) {
|
|
8110
|
+
lines.push(
|
|
8111
|
+
`You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
|
|
8112
|
+
);
|
|
8113
|
+
}
|
|
8114
|
+
if (stateReminder) {
|
|
8115
|
+
lines.push(stateReminder);
|
|
8116
|
+
}
|
|
8117
|
+
if (phaseReminder) {
|
|
8118
|
+
lines.push(phaseReminder);
|
|
8119
|
+
}
|
|
8120
|
+
return lines.join("\n");
|
|
8121
|
+
}
|
|
8122
|
+
function buildPhaseReminder(userMessage, assistantText) {
|
|
8123
|
+
const goal = userMessage.toLowerCase();
|
|
8124
|
+
const text = assistantText.toLowerCase();
|
|
8125
|
+
if (!goal || !text) return "";
|
|
8126
|
+
const wantsCart = /\b(cart|bag|basket|checkout)\b/.test(goal);
|
|
8127
|
+
const wantsExplanation = /\b(explain|reason|why)\b/.test(goal);
|
|
8128
|
+
const wantsBookRecommendations = /\b(book|books|recommend|recommended|interesting|novel|fiction|nonfiction)\b/.test(
|
|
8129
|
+
goal
|
|
8130
|
+
);
|
|
8131
|
+
const hasFiveItemList = /(?:^|\n)\s*1\./.test(assistantText) && /(?:^|\n)\s*2\./.test(assistantText) && /(?:^|\n)\s*3\./.test(assistantText) && /(?:^|\n)\s*4\./.test(assistantText) && /(?:^|\n)\s*5\./.test(assistantText);
|
|
8132
|
+
const selectedItems = hasFiveItemList || /i(?:'| a)?ve chosen/.test(text) || /i have chosen/.test(text) || /i selected/.test(text) || /here are the books/i.test(assistantText) || /here are the items/i.test(assistantText);
|
|
8133
|
+
const intendsCart = /next[, ]+i will add/.test(text) || /i(?:'| a)?ll start with the first/.test(text) || /proceed systematically/.test(text) || /add (these|the chosen|the selected).*(cart|bag|basket)/.test(text);
|
|
8134
|
+
const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
|
|
8135
|
+
text
|
|
8136
|
+
);
|
|
8137
|
+
const explanationDone = /here is why i chose/.test(text) || /here are my reasons/.test(text) || /reason:/.test(text) || /reasons:/.test(text) || /why i chose/.test(text);
|
|
8138
|
+
const listingLoopSignals = /page contains a list of books|book listings|book cards|visible book|load more results|scroll further|scroll down|inspect the visible|focus on the book listings|targeting the book images|limited to interactive elements|identify the book cards|click one of the visible book/.test(
|
|
8139
|
+
text
|
|
8140
|
+
);
|
|
8141
|
+
const missedResultsSignals = /visible_only mode did not return specific book titles|did not yield a book title link|did not yield specific book titles|navigation links rather than book titles|inspect elements did not yield|inspect the page to find a specific book title|inspect the page to locate a book title|book title link from the search results/.test(
|
|
8142
|
+
text
|
|
8143
|
+
);
|
|
8144
|
+
const falseCartSuccessSignals = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
|
|
8145
|
+
text
|
|
8146
|
+
) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
|
|
8147
|
+
text
|
|
8148
|
+
);
|
|
8149
|
+
const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
|
|
8150
|
+
text
|
|
8151
|
+
) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
|
|
8152
|
+
text
|
|
8153
|
+
);
|
|
8154
|
+
const selectedItemsRestartSignals = /navigate back to the search results page|search for ".*" directly in the search box|search for .* directly|page structure has shifted|refresh the page|restart search/.test(
|
|
8155
|
+
text
|
|
8156
|
+
);
|
|
8157
|
+
const multiClickSelectionSignals = /i(?:'| a)?ll start by clicking on the following books|i will start by clicking on the following books|i will click on the following books|clicked on five different book titles|clicked on \d+ different book titles|clicking through the selected titles|click each of the selected titles/.test(
|
|
8158
|
+
text
|
|
8159
|
+
);
|
|
8160
|
+
const staleSelectionSignals = /cannot locate the elements to click|page structure is not being reliably captured|specific titles failed|page may have changed|stale-index|no visible area|not visible/.test(
|
|
8161
|
+
text
|
|
8162
|
+
);
|
|
8163
|
+
const intermediateCartDialogSignals = /(added to cart|has been added to the cart|cart confirmation)/.test(text) && /(continue shopping|search results page|return to the search results page|back button|go back)/.test(
|
|
8164
|
+
text
|
|
8165
|
+
) && !/(all requested books are now in the cart|all 5 books are now in the cart|5 of 5 requested books are now in the cart)/.test(
|
|
8166
|
+
text
|
|
8167
|
+
);
|
|
8168
|
+
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
|
|
8169
|
+
return `Progress reminder: If product results or primary results are already visible, do not keep rereading or rescrolling the same listing page. Open one promising result now. On the detail page, add that item to the cart before returning for the next unseen result.`;
|
|
8170
|
+
}
|
|
8171
|
+
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
|
|
8172
|
+
return `Progress reminder: On a results page, do not use visible_only or generic inspect_element to hunt product results. Call read_page(mode="results_only") once. If Primary Results are shown, click a listed result directly.`;
|
|
8173
|
+
}
|
|
8174
|
+
if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
|
|
8175
|
+
return `Progress reminder: Do not assume an item was added just because its product page is open or you inspected it. Only treat the cart step as complete after a successful Add to Cart click followed by cart confirmation, View Cart, Continue Shopping, or the cart page itself.`;
|
|
8176
|
+
}
|
|
8177
|
+
if (wantsCart && skippedSingleResultSignals && !selectedItems) {
|
|
8178
|
+
return `Progress reminder: Do not skip to a new query just because the match is not exact. If the results page shows even one plausible product result, inspect or click that result before concluding there is no match.`;
|
|
8179
|
+
}
|
|
8180
|
+
if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
|
|
8181
|
+
return `Progress reminder: After an Add to Cart success, prefer the cart-confirmation dialog action Continue Shopping while more items remain. Do not click View Cart or Go to Basket yet, and do not use the browser back button while the dialog is still open.`;
|
|
8182
|
+
}
|
|
8183
|
+
if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
|
|
8184
|
+
return `Progress reminder: The chosen items are already decided. Do not restart search, refresh the results page, or navigate back to browse again unless a specific saved link fails. Use the current results page or the chosen result links you already have: open one chosen result, add it to the cart, confirm success, then continue to the next chosen result.`;
|
|
8185
|
+
}
|
|
8186
|
+
if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
|
|
8187
|
+
return `Progress reminder: Do not batch-click multiple results from a listing or category page. Open exactly one visible result, finish that item's Add to Cart flow, confirm success, then use Continue Shopping or go back once to choose the next unseen result. If a remembered label or index fails, trust the latest page state and refresh it with one read_page call before continuing.`;
|
|
8188
|
+
}
|
|
8189
|
+
if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
|
|
8190
|
+
return `Progress reminder: You already selected the requested items. Do not restart browsing or searching unless a specific cart step fails. Continue adding the selected items to the cart one by one. Use the chosen result links you already have, add one selected item to the cart, confirm success, then continue to the next one. Do not click multiple chosen results in a row from the same listing page.`;
|
|
8191
|
+
}
|
|
8192
|
+
if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
|
|
8193
|
+
return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
|
|
8194
|
+
}
|
|
8195
|
+
return "";
|
|
8196
|
+
}
|
|
8197
|
+
function buildLatestStateReminder(toolResultPreview) {
|
|
8198
|
+
const text = toolResultPreview.trim();
|
|
8199
|
+
if (!text) return "";
|
|
8200
|
+
const stateMatch = text.match(
|
|
8201
|
+
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
8202
|
+
);
|
|
8203
|
+
if (stateMatch) {
|
|
8204
|
+
const url = stateMatch[1]?.trim();
|
|
8205
|
+
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
8206
|
+
if (url) {
|
|
8207
|
+
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8211
|
+
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8212
|
+
if (structuredUrl) {
|
|
8213
|
+
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8214
|
+
}
|
|
8215
|
+
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
8216
|
+
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
8217
|
+
if (navigatedUrl) {
|
|
8218
|
+
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8219
|
+
}
|
|
8220
|
+
return "";
|
|
8221
|
+
}
|
|
8222
|
+
function shouldRecoverCompactStall(text, userMessage) {
|
|
8223
|
+
const trimmed = text.trim().toLowerCase();
|
|
8224
|
+
if (!trimmed) return true;
|
|
8225
|
+
if (trimmed.length <= 160 && trimmed.includes("?")) return true;
|
|
8226
|
+
if (userMessage && buildPhaseReminder(userMessage, text)) {
|
|
8227
|
+
return true;
|
|
8228
|
+
}
|
|
8229
|
+
const repetitivePlanningSignals = [
|
|
8230
|
+
"next step:",
|
|
8231
|
+
"i will now inspect",
|
|
8232
|
+
"i will now read",
|
|
8233
|
+
"i will now click",
|
|
8234
|
+
"i'll use readpage",
|
|
8235
|
+
"i'll use read_page",
|
|
8236
|
+
"i'll start by clicking",
|
|
8237
|
+
"i have clicked on five different book titles",
|
|
8238
|
+
"clicked on five different book titles",
|
|
8239
|
+
"i'll begin with",
|
|
8240
|
+
"if the selection is unclear"
|
|
8241
|
+
];
|
|
8242
|
+
if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
8243
|
+
return true;
|
|
8244
|
+
}
|
|
8245
|
+
const falseCartSuccessWithoutConfirmation = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
|
|
8246
|
+
trimmed
|
|
8247
|
+
) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
|
|
8248
|
+
trimmed
|
|
8249
|
+
);
|
|
8250
|
+
if (falseCartSuccessWithoutConfirmation) {
|
|
8251
|
+
return true;
|
|
8252
|
+
}
|
|
8253
|
+
const completionSignals = [
|
|
8254
|
+
"i found",
|
|
8255
|
+
"i chose",
|
|
8256
|
+
"i selected",
|
|
8257
|
+
"i added",
|
|
8258
|
+
"here are",
|
|
8259
|
+
"these are",
|
|
8260
|
+
"recommendations",
|
|
8261
|
+
"reasoning",
|
|
8262
|
+
"why i chose",
|
|
8263
|
+
"added them to the cart"
|
|
8264
|
+
];
|
|
8265
|
+
if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
8266
|
+
return false;
|
|
8267
|
+
}
|
|
8268
|
+
return [
|
|
8269
|
+
"what are you hoping",
|
|
8270
|
+
"what would you like",
|
|
8271
|
+
"how can i help",
|
|
8272
|
+
"let me know",
|
|
8273
|
+
"are you looking for",
|
|
8274
|
+
"just browsing",
|
|
8275
|
+
"i need to",
|
|
8276
|
+
"i will",
|
|
8277
|
+
"i'll",
|
|
8278
|
+
"since i cannot see",
|
|
8279
|
+
"since i can't see",
|
|
8280
|
+
"cannot see the current page",
|
|
8281
|
+
"scroll down to",
|
|
8282
|
+
"load more results",
|
|
8283
|
+
"as placeholders",
|
|
8284
|
+
"would you like me to proceed",
|
|
8285
|
+
"action:",
|
|
8286
|
+
"one moment",
|
|
8287
|
+
"i will now navigate",
|
|
8288
|
+
"navigating to ",
|
|
8289
|
+
"this will take me",
|
|
8290
|
+
"i will use the browser"
|
|
8291
|
+
].some((pattern) => trimmed.includes(pattern));
|
|
8292
|
+
}
|
|
8293
|
+
function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
|
|
8294
|
+
return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
|
|
8295
|
+
}
|
|
8296
|
+
function logAgentLoopDebug(payload) {
|
|
8297
|
+
if (!shouldDebugAgentLoop()) return;
|
|
8298
|
+
try {
|
|
8299
|
+
logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
8300
|
+
} catch (err) {
|
|
8301
|
+
logger$j.warn("Failed to serialize debug payload:", err);
|
|
8302
|
+
}
|
|
8179
8303
|
}
|
|
8180
8304
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
8181
8305
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
@@ -12642,6 +12766,8 @@ const UNSORTED_ID = "unsorted";
|
|
|
12642
12766
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
12643
12767
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
12644
12768
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
12769
|
+
const DEFAULT_BOOKMARK_SEARCH_LIMIT = 50;
|
|
12770
|
+
const MAX_BOOKMARK_SEARCH_LIMIT = 200;
|
|
12645
12771
|
let state$2 = null;
|
|
12646
12772
|
const listeners = /* @__PURE__ */ new Set();
|
|
12647
12773
|
function cloneState(current) {
|
|
@@ -12650,6 +12776,10 @@ function cloneState(current) {
|
|
|
12650
12776
|
bookmarks: current.bookmarks.map((bookmark) => ({ ...bookmark }))
|
|
12651
12777
|
};
|
|
12652
12778
|
}
|
|
12779
|
+
function getFolderMap() {
|
|
12780
|
+
load$1();
|
|
12781
|
+
return new Map(state$2.folders.map((folder) => [folder.id, folder]));
|
|
12782
|
+
}
|
|
12653
12783
|
function getBookmarksPath() {
|
|
12654
12784
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
12655
12785
|
}
|
|
@@ -12876,13 +13006,16 @@ function listFolderOverviews() {
|
|
|
12876
13006
|
}))
|
|
12877
13007
|
];
|
|
12878
13008
|
}
|
|
12879
|
-
function searchBookmarks(query) {
|
|
13009
|
+
function searchBookmarks(query, limit = DEFAULT_BOOKMARK_SEARCH_LIMIT) {
|
|
12880
13010
|
load$1();
|
|
12881
13011
|
if (!query.trim()) return [];
|
|
13012
|
+
const foldersById = getFolderMap();
|
|
13013
|
+
const safeLimit = Math.max(
|
|
13014
|
+
1,
|
|
13015
|
+
Math.min(MAX_BOOKMARK_SEARCH_LIMIT, Math.floor(limit))
|
|
13016
|
+
);
|
|
12882
13017
|
return state$2.bookmarks.map((bookmark) => {
|
|
12883
|
-
const folder =
|
|
12884
|
-
(item) => item.id === bookmark.folderId
|
|
12885
|
-
);
|
|
13018
|
+
const folder = foldersById.get(bookmark.folderId);
|
|
12886
13019
|
const { matchedFields, score } = getBookmarkSearchMatch({
|
|
12887
13020
|
query,
|
|
12888
13021
|
title: bookmark.title,
|
|
@@ -12906,7 +13039,7 @@ function searchBookmarks(query) {
|
|
|
12906
13039
|
score: result.score
|
|
12907
13040
|
})).sort(
|
|
12908
13041
|
(a, b) => b.score - a.score || b.bookmark.savedAt.localeCompare(a.bookmark.savedAt)
|
|
12909
|
-
);
|
|
13042
|
+
).slice(0, safeLimit);
|
|
12910
13043
|
}
|
|
12911
13044
|
function createFolderWithSummary(name, summary) {
|
|
12912
13045
|
load$1();
|
|
@@ -14174,6 +14307,97 @@ function formatCompactToolResult(name, result) {
|
|
|
14174
14307
|
return limitText(result, 18, 1400);
|
|
14175
14308
|
}
|
|
14176
14309
|
}
|
|
14310
|
+
const ADD_TO_CART_PATTERNS = [
|
|
14311
|
+
"add to cart",
|
|
14312
|
+
"add to bag",
|
|
14313
|
+
"add to basket",
|
|
14314
|
+
"add to my cart",
|
|
14315
|
+
"add to my bag",
|
|
14316
|
+
"add to my basket",
|
|
14317
|
+
"add item to cart",
|
|
14318
|
+
"add item to bag",
|
|
14319
|
+
"add item to basket"
|
|
14320
|
+
];
|
|
14321
|
+
const CART_CLICK_COOLDOWN_MS = 15e3;
|
|
14322
|
+
const CART_ADDED_TTL_MS = 30 * 6e4;
|
|
14323
|
+
const recentCartClicks = /* @__PURE__ */ new Map();
|
|
14324
|
+
const cartAddedProducts = /* @__PURE__ */ new Map();
|
|
14325
|
+
function isAddToCartText(text) {
|
|
14326
|
+
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
14327
|
+
return ADD_TO_CART_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
14328
|
+
}
|
|
14329
|
+
function recordCartClick(url) {
|
|
14330
|
+
recentCartClicks.set(url, Date.now());
|
|
14331
|
+
pruneRecentCartClicks();
|
|
14332
|
+
}
|
|
14333
|
+
function hasRecentCartClick(url) {
|
|
14334
|
+
const recent = recentCartClicks.get(url);
|
|
14335
|
+
if (!recent) return false;
|
|
14336
|
+
if (Date.now() - recent > CART_CLICK_COOLDOWN_MS) {
|
|
14337
|
+
recentCartClicks.delete(url);
|
|
14338
|
+
return false;
|
|
14339
|
+
}
|
|
14340
|
+
return true;
|
|
14341
|
+
}
|
|
14342
|
+
function isDuplicateCartClick(url, text) {
|
|
14343
|
+
return hasRecentCartClick(url) && isAddToCartText(text);
|
|
14344
|
+
}
|
|
14345
|
+
function recordProductAddedToCart(url, productName) {
|
|
14346
|
+
pruneCartAddedProducts();
|
|
14347
|
+
cartAddedProducts.set(normalizeCartProductKey(url), {
|
|
14348
|
+
title: productName || url,
|
|
14349
|
+
ts: Date.now()
|
|
14350
|
+
});
|
|
14351
|
+
}
|
|
14352
|
+
function isProductAlreadyInCart(url) {
|
|
14353
|
+
pruneCartAddedProducts();
|
|
14354
|
+
return cartAddedProducts.has(normalizeCartProductKey(url));
|
|
14355
|
+
}
|
|
14356
|
+
function getCartAddedSummary(url) {
|
|
14357
|
+
pruneCartAddedProducts();
|
|
14358
|
+
const origin = cartOrigin(url);
|
|
14359
|
+
const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
|
|
14360
|
+
if (!items) return "";
|
|
14361
|
+
const count = items.split("\n").length;
|
|
14362
|
+
return `
|
|
14363
|
+
Already in cart (${count} items):
|
|
14364
|
+
${items}`;
|
|
14365
|
+
}
|
|
14366
|
+
function clearCartClickState() {
|
|
14367
|
+
cartAddedProducts.clear();
|
|
14368
|
+
recentCartClicks.clear();
|
|
14369
|
+
}
|
|
14370
|
+
function pruneRecentCartClicks(now = Date.now()) {
|
|
14371
|
+
for (const [key2, ts] of recentCartClicks) {
|
|
14372
|
+
if (now - ts > CART_CLICK_COOLDOWN_MS) {
|
|
14373
|
+
recentCartClicks.delete(key2);
|
|
14374
|
+
}
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14377
|
+
function normalizeCartProductKey(url) {
|
|
14378
|
+
try {
|
|
14379
|
+
const parsed = new URL(url);
|
|
14380
|
+
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
14381
|
+
return `${parsed.origin}${pathname}`;
|
|
14382
|
+
} catch {
|
|
14383
|
+
return url;
|
|
14384
|
+
}
|
|
14385
|
+
}
|
|
14386
|
+
function pruneCartAddedProducts(now = Date.now()) {
|
|
14387
|
+
for (const [key2, entry] of cartAddedProducts) {
|
|
14388
|
+
if (now - entry.ts > CART_ADDED_TTL_MS) {
|
|
14389
|
+
cartAddedProducts.delete(key2);
|
|
14390
|
+
}
|
|
14391
|
+
}
|
|
14392
|
+
}
|
|
14393
|
+
function cartOrigin(url) {
|
|
14394
|
+
if (!url) return null;
|
|
14395
|
+
try {
|
|
14396
|
+
return new URL(url).origin;
|
|
14397
|
+
} catch {
|
|
14398
|
+
return null;
|
|
14399
|
+
}
|
|
14400
|
+
}
|
|
14177
14401
|
const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
|
|
14178
14402
|
const HUGGING_FACE_MODEL_TASKS = [
|
|
14179
14403
|
{
|
|
@@ -14437,9 +14661,12 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
14437
14661
|
class TabMutex {
|
|
14438
14662
|
queue = Promise.resolve();
|
|
14439
14663
|
enqueue(fn) {
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14664
|
+
const run = this.queue.then(fn, fn);
|
|
14665
|
+
this.queue = run.then(
|
|
14666
|
+
() => void 0,
|
|
14667
|
+
() => void 0
|
|
14668
|
+
);
|
|
14669
|
+
return run;
|
|
14443
14670
|
}
|
|
14444
14671
|
}
|
|
14445
14672
|
const logger$e = createLogger("PageActions");
|
|
@@ -15267,45 +15494,9 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15267
15494
|
}
|
|
15268
15495
|
}
|
|
15269
15496
|
}
|
|
15270
|
-
const ADD_TO_CART_PATTERNS = [
|
|
15271
|
-
"add to cart",
|
|
15272
|
-
"add to bag",
|
|
15273
|
-
"add to basket",
|
|
15274
|
-
"add to my cart",
|
|
15275
|
-
"add to my bag",
|
|
15276
|
-
"add to my basket",
|
|
15277
|
-
"add item to cart",
|
|
15278
|
-
"add item to bag",
|
|
15279
|
-
"add item to basket"
|
|
15280
|
-
];
|
|
15281
|
-
const recentCartClicks = /* @__PURE__ */ new Map();
|
|
15282
|
-
const CART_CLICK_COOLDOWN_MS = 15e3;
|
|
15283
|
-
const CART_ADDED_TTL_MS = 30 * 6e4;
|
|
15284
|
-
const cartAddedProducts = /* @__PURE__ */ new Map();
|
|
15285
15497
|
let clickStreakUrl = null;
|
|
15286
15498
|
let clickStreakCount = 0;
|
|
15287
15499
|
const CLICK_STREAK_THRESHOLD = 3;
|
|
15288
|
-
function isAddToCartText(text) {
|
|
15289
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
15290
|
-
return ADD_TO_CART_PATTERNS.some((p) => normalized.includes(p));
|
|
15291
|
-
}
|
|
15292
|
-
function recordCartClick(url, text) {
|
|
15293
|
-
recentCartClicks.set(url, { text, ts: Date.now() });
|
|
15294
|
-
for (const [key2, entry] of recentCartClicks) {
|
|
15295
|
-
if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
|
|
15296
|
-
recentCartClicks.delete(key2);
|
|
15297
|
-
}
|
|
15298
|
-
}
|
|
15299
|
-
}
|
|
15300
|
-
function isDuplicateCartClick(url, text) {
|
|
15301
|
-
const recent = recentCartClicks.get(url);
|
|
15302
|
-
if (!recent) return false;
|
|
15303
|
-
if (Date.now() - recent.ts > CART_CLICK_COOLDOWN_MS) {
|
|
15304
|
-
recentCartClicks.delete(url);
|
|
15305
|
-
return false;
|
|
15306
|
-
}
|
|
15307
|
-
return isAddToCartText(text);
|
|
15308
|
-
}
|
|
15309
15500
|
async function getProductPageTitle(wc) {
|
|
15310
15501
|
try {
|
|
15311
15502
|
const heading = await executePageScript(
|
|
@@ -15330,54 +15521,8 @@ async function getProductPageTitle(wc) {
|
|
|
15330
15521
|
}
|
|
15331
15522
|
return wc.getTitle() || "";
|
|
15332
15523
|
}
|
|
15333
|
-
function normalizeCartProductKey(url) {
|
|
15334
|
-
try {
|
|
15335
|
-
const parsed = new URL(url);
|
|
15336
|
-
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
15337
|
-
return `${parsed.origin}${pathname}`;
|
|
15338
|
-
} catch {
|
|
15339
|
-
return url;
|
|
15340
|
-
}
|
|
15341
|
-
}
|
|
15342
|
-
function pruneCartAddedProducts(now = Date.now()) {
|
|
15343
|
-
for (const [key2, entry] of cartAddedProducts) {
|
|
15344
|
-
if (now - entry.ts > CART_ADDED_TTL_MS) {
|
|
15345
|
-
cartAddedProducts.delete(key2);
|
|
15346
|
-
}
|
|
15347
|
-
}
|
|
15348
|
-
}
|
|
15349
|
-
function cartOrigin(url) {
|
|
15350
|
-
if (!url) return null;
|
|
15351
|
-
try {
|
|
15352
|
-
return new URL(url).origin;
|
|
15353
|
-
} catch {
|
|
15354
|
-
return null;
|
|
15355
|
-
}
|
|
15356
|
-
}
|
|
15357
|
-
function recordProductAddedToCart(url, productName) {
|
|
15358
|
-
pruneCartAddedProducts();
|
|
15359
|
-
cartAddedProducts.set(normalizeCartProductKey(url), {
|
|
15360
|
-
title: productName || url,
|
|
15361
|
-
ts: Date.now()
|
|
15362
|
-
});
|
|
15363
|
-
}
|
|
15364
|
-
function isProductAlreadyInCart(url) {
|
|
15365
|
-
pruneCartAddedProducts();
|
|
15366
|
-
return cartAddedProducts.has(normalizeCartProductKey(url));
|
|
15367
|
-
}
|
|
15368
|
-
function getCartAddedSummary(url) {
|
|
15369
|
-
pruneCartAddedProducts();
|
|
15370
|
-
const origin = cartOrigin(url);
|
|
15371
|
-
const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
|
|
15372
|
-
if (!items) return "";
|
|
15373
|
-
const count = items.split("\n").length;
|
|
15374
|
-
return `
|
|
15375
|
-
Already in cart (${count} items):
|
|
15376
|
-
${items}`;
|
|
15377
|
-
}
|
|
15378
15524
|
function clearCartState() {
|
|
15379
|
-
|
|
15380
|
-
recentCartClicks.clear();
|
|
15525
|
+
clearCartClickState();
|
|
15381
15526
|
clickStreakUrl = null;
|
|
15382
15527
|
clickStreakCount = 0;
|
|
15383
15528
|
}
|
|
@@ -15429,7 +15574,7 @@ Go back and select a different product.`;
|
|
|
15429
15574
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15430
15575
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15431
15576
|
if (idxCartMatch) {
|
|
15432
|
-
recordCartClick(beforeUrl2
|
|
15577
|
+
recordCartClick(beforeUrl2);
|
|
15433
15578
|
}
|
|
15434
15579
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15435
15580
|
const afterUrl2 = wc.getURL();
|
|
@@ -15496,7 +15641,7 @@ Go back and select a different product.`;
|
|
|
15496
15641
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15497
15642
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15498
15643
|
if (shadowCartMatch) {
|
|
15499
|
-
recordCartClick(beforeUrl2
|
|
15644
|
+
recordCartClick(beforeUrl2);
|
|
15500
15645
|
}
|
|
15501
15646
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15502
15647
|
const afterUrl2 = wc.getURL();
|
|
@@ -15532,7 +15677,7 @@ Note: Page did not change after click.`;
|
|
|
15532
15677
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
15533
15678
|
return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
|
|
15534
15679
|
}
|
|
15535
|
-
if (!cartMatch &&
|
|
15680
|
+
if (!cartMatch && hasRecentCartClick(beforeUrl)) {
|
|
15536
15681
|
const dialogActions = await getCartDialogActions(wc);
|
|
15537
15682
|
if (dialogActions) {
|
|
15538
15683
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
@@ -15552,7 +15697,7 @@ Click one of these dialog actions instead.`;
|
|
|
15552
15697
|
Go back and select a different product.`;
|
|
15553
15698
|
}
|
|
15554
15699
|
if (cartMatch) {
|
|
15555
|
-
recordCartClick(beforeUrl
|
|
15700
|
+
recordCartClick(beforeUrl);
|
|
15556
15701
|
}
|
|
15557
15702
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
15558
15703
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
@@ -18986,6 +19131,57 @@ function onAIStreamIdle(listener) {
|
|
|
18986
19131
|
idleListeners.add(listener);
|
|
18987
19132
|
return () => idleListeners.delete(listener);
|
|
18988
19133
|
}
|
|
19134
|
+
const MAX_PROVIDER_HISTORY_MESSAGES = 24;
|
|
19135
|
+
const MAX_PROVIDER_HISTORY_CHARS = 24e3;
|
|
19136
|
+
const MAX_PROVIDER_HISTORY_MESSAGE_CHARS = 3e3;
|
|
19137
|
+
const MAX_PROVIDER_HISTORY_SUMMARY_CHARS = 2e3;
|
|
19138
|
+
function truncateText(value, maxLength) {
|
|
19139
|
+
if (value.length <= maxLength) return value;
|
|
19140
|
+
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
|
|
19141
|
+
}
|
|
19142
|
+
function normalizeHistoryMessage(message) {
|
|
19143
|
+
return {
|
|
19144
|
+
role: message.role,
|
|
19145
|
+
content: truncateText(message.content, MAX_PROVIDER_HISTORY_MESSAGE_CHARS)
|
|
19146
|
+
};
|
|
19147
|
+
}
|
|
19148
|
+
function totalHistoryChars(history) {
|
|
19149
|
+
return history.reduce((total, message) => total + message.content.length, 0);
|
|
19150
|
+
}
|
|
19151
|
+
function summarizeOmittedHistory(history) {
|
|
19152
|
+
const snippets = history.slice(-12).map((message) => `${message.role}: ${truncateText(message.content.replace(/\s+/g, " ").trim(), 220)}`).filter((line) => line.length > "assistant: ".length);
|
|
19153
|
+
const content = truncateText(
|
|
19154
|
+
[
|
|
19155
|
+
`[Earlier conversation compacted: ${history.length} message${history.length === 1 ? "" : "s"} omitted.]`,
|
|
19156
|
+
...snippets
|
|
19157
|
+
].join("\n"),
|
|
19158
|
+
MAX_PROVIDER_HISTORY_SUMMARY_CHARS
|
|
19159
|
+
);
|
|
19160
|
+
return { role: "user", content };
|
|
19161
|
+
}
|
|
19162
|
+
function compactProviderHistory(history = []) {
|
|
19163
|
+
const normalized = history.map(normalizeHistoryMessage);
|
|
19164
|
+
if (normalized.length <= MAX_PROVIDER_HISTORY_MESSAGES && totalHistoryChars(normalized) <= MAX_PROVIDER_HISTORY_CHARS) {
|
|
19165
|
+
return normalized;
|
|
19166
|
+
}
|
|
19167
|
+
const recent = [];
|
|
19168
|
+
const recentBudget = MAX_PROVIDER_HISTORY_CHARS - MAX_PROVIDER_HISTORY_SUMMARY_CHARS;
|
|
19169
|
+
let usedChars = 0;
|
|
19170
|
+
for (let index = normalized.length - 1; index >= 0; index--) {
|
|
19171
|
+
const message = normalized[index];
|
|
19172
|
+
const nextChars = usedChars + message.content.length;
|
|
19173
|
+
if (recent.length >= MAX_PROVIDER_HISTORY_MESSAGES || nextChars > recentBudget) {
|
|
19174
|
+
break;
|
|
19175
|
+
}
|
|
19176
|
+
recent.unshift(message);
|
|
19177
|
+
usedChars = nextChars;
|
|
19178
|
+
}
|
|
19179
|
+
if (recent.length === 0 && normalized.length > 0) {
|
|
19180
|
+
recent.unshift(normalized[normalized.length - 1]);
|
|
19181
|
+
}
|
|
19182
|
+
const omitted = normalized.slice(0, normalized.length - recent.length);
|
|
19183
|
+
return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
|
|
19184
|
+
}
|
|
18989
19185
|
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
18990
19186
|
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
18991
19187
|
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
@@ -20341,6 +20537,14 @@ function asErrorTextResponse(message) {
|
|
|
20341
20537
|
function asNoActiveTabResponse() {
|
|
20342
20538
|
return asErrorTextResponse("No active tab");
|
|
20343
20539
|
}
|
|
20540
|
+
function getPremiumToolGateResponse(toolName) {
|
|
20541
|
+
try {
|
|
20542
|
+
assertToolUnlocked(toolName);
|
|
20543
|
+
return null;
|
|
20544
|
+
} catch (error) {
|
|
20545
|
+
return asTextResponse(getErrorMessage(error));
|
|
20546
|
+
}
|
|
20547
|
+
}
|
|
20344
20548
|
function asPromptResponse(text) {
|
|
20345
20549
|
return {
|
|
20346
20550
|
messages: [
|
|
@@ -20443,6 +20647,8 @@ async function getPostActionState(tabManager, name) {
|
|
|
20443
20647
|
return "";
|
|
20444
20648
|
}
|
|
20445
20649
|
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
20650
|
+
const premiumGate = getPremiumToolGateResponse(name);
|
|
20651
|
+
if (premiumGate) return premiumGate;
|
|
20446
20652
|
try {
|
|
20447
20653
|
const result = await runtime2.runControlledAction({
|
|
20448
20654
|
source: "mcp",
|
|
@@ -21752,6 +21958,8 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
21752
21958
|
description: "Capture a screenshot of the current page. Returns a base64-encoded PNG image."
|
|
21753
21959
|
},
|
|
21754
21960
|
async () => {
|
|
21961
|
+
const premiumGate = getPremiumToolGateResponse("screenshot");
|
|
21962
|
+
if (premiumGate) return premiumGate;
|
|
21755
21963
|
const tab = tabManager.getActiveTab();
|
|
21756
21964
|
if (!tab) return asNoActiveTabResponse();
|
|
21757
21965
|
try {
|
|
@@ -22774,6 +22982,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
22774
22982
|
}
|
|
22775
22983
|
},
|
|
22776
22984
|
async ({ goal, steps }) => {
|
|
22985
|
+
const premiumGate = getPremiumToolGateResponse("flow_start");
|
|
22986
|
+
if (premiumGate) return premiumGate;
|
|
22777
22987
|
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
22778
22988
|
const tab = tabManager.getActiveTab();
|
|
22779
22989
|
const flow = runtime2.startFlow(
|
|
@@ -22797,6 +23007,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22797
23007
|
}
|
|
22798
23008
|
},
|
|
22799
23009
|
async ({ detail }) => {
|
|
23010
|
+
const premiumGate = getPremiumToolGateResponse("flow_advance");
|
|
23011
|
+
if (premiumGate) return premiumGate;
|
|
22800
23012
|
const flow = runtime2.advanceFlow(detail);
|
|
22801
23013
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
22802
23014
|
const ctx = runtime2.getFlowContext();
|
|
@@ -22810,6 +23022,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22810
23022
|
description: "Check the current workflow progress."
|
|
22811
23023
|
},
|
|
22812
23024
|
async () => {
|
|
23025
|
+
const premiumGate = getPremiumToolGateResponse("flow_status");
|
|
23026
|
+
if (premiumGate) return premiumGate;
|
|
22813
23027
|
const flow = runtime2.getFlowState();
|
|
22814
23028
|
if (!flow) return asTextResponse("No active workflow.");
|
|
22815
23029
|
return asTextResponse(runtime2.getFlowContext());
|
|
@@ -22822,6 +23036,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22822
23036
|
description: "Clear the active workflow tracker."
|
|
22823
23037
|
},
|
|
22824
23038
|
async () => {
|
|
23039
|
+
const premiumGate = getPremiumToolGateResponse("flow_end");
|
|
23040
|
+
if (premiumGate) return premiumGate;
|
|
22825
23041
|
runtime2.clearFlow();
|
|
22826
23042
|
return asTextResponse("Workflow ended.");
|
|
22827
23043
|
}
|
|
@@ -23462,6 +23678,8 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
23462
23678
|
}
|
|
23463
23679
|
},
|
|
23464
23680
|
async ({ domain }) => {
|
|
23681
|
+
const premiumGate = getPremiumToolGateResponse("vault_status");
|
|
23682
|
+
if (premiumGate) return premiumGate;
|
|
23465
23683
|
let targetDomain = domain;
|
|
23466
23684
|
if (!targetDomain) {
|
|
23467
23685
|
const tab = tabManager.getActiveTab();
|
|
@@ -23528,6 +23746,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23528
23746
|
submit_after,
|
|
23529
23747
|
submit_index
|
|
23530
23748
|
}) => {
|
|
23749
|
+
const premiumGate = getPremiumToolGateResponse("vault_login");
|
|
23750
|
+
if (premiumGate) return premiumGate;
|
|
23531
23751
|
const tab = tabManager.getActiveTab();
|
|
23532
23752
|
if (!tab) return asNoActiveTabResponse();
|
|
23533
23753
|
const wc = tab.view.webContents;
|
|
@@ -23622,6 +23842,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23622
23842
|
}
|
|
23623
23843
|
},
|
|
23624
23844
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
23845
|
+
const premiumGate = getPremiumToolGateResponse("vault_totp");
|
|
23846
|
+
if (premiumGate) return premiumGate;
|
|
23625
23847
|
const tab = tabManager.getActiveTab();
|
|
23626
23848
|
if (!tab) return asNoActiveTabResponse();
|
|
23627
23849
|
const wc = tab.view.webContents;
|
|
@@ -23701,6 +23923,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23701
23923
|
})
|
|
23702
23924
|
},
|
|
23703
23925
|
async ({ domain }) => {
|
|
23926
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_list");
|
|
23927
|
+
if (premiumGate) return premiumGate;
|
|
23704
23928
|
const consent = await requestHumanVaultConsent({
|
|
23705
23929
|
action: "list",
|
|
23706
23930
|
domain: domain ?? "all"
|
|
@@ -23751,6 +23975,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23751
23975
|
})
|
|
23752
23976
|
},
|
|
23753
23977
|
async ({ entry_id, username_index, password_index, submit_after, submit_index }) => {
|
|
23978
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_fill");
|
|
23979
|
+
if (premiumGate) return premiumGate;
|
|
23754
23980
|
const tab = tabManager.getActiveTab();
|
|
23755
23981
|
if (!tab) return asNoActiveTabResponse();
|
|
23756
23982
|
let hostname;
|
|
@@ -23843,6 +24069,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23843
24069
|
})
|
|
23844
24070
|
},
|
|
23845
24071
|
async ({ entry_id }) => {
|
|
24072
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_remove");
|
|
24073
|
+
if (premiumGate) return premiumGate;
|
|
23846
24074
|
const entry = getEntry(entry_id);
|
|
23847
24075
|
if (!entry) {
|
|
23848
24076
|
return asErrorTextResponse(`No entry found with ID ${entry_id}.`);
|
|
@@ -23867,6 +24095,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23867
24095
|
inputSchema: zod.z.object({})
|
|
23868
24096
|
},
|
|
23869
24097
|
async () => {
|
|
24098
|
+
const premiumGate = getPremiumToolGateResponse("metrics");
|
|
24099
|
+
if (premiumGate) return premiumGate;
|
|
23870
24100
|
const m = runtime2.getMetrics();
|
|
23871
24101
|
const lines = [
|
|
23872
24102
|
`Session Metrics:`,
|
|
@@ -24220,7 +24450,7 @@ function assertNumber(value, name) {
|
|
|
24220
24450
|
}
|
|
24221
24451
|
}
|
|
24222
24452
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
24223
|
-
function isValidEmail(value) {
|
|
24453
|
+
function isValidEmail$1(value) {
|
|
24224
24454
|
return EMAIL_RE.test(value.trim());
|
|
24225
24455
|
}
|
|
24226
24456
|
function getActiveTabInfo(tabManager) {
|
|
@@ -24232,6 +24462,8 @@ function getActiveTabInfo(tabManager) {
|
|
|
24232
24462
|
}
|
|
24233
24463
|
const logger$9 = createLogger("Scheduler");
|
|
24234
24464
|
let jobs = [];
|
|
24465
|
+
let pollInterval = null;
|
|
24466
|
+
let alignStartTimeout = null;
|
|
24235
24467
|
let removeIdleListener = null;
|
|
24236
24468
|
let broadcastFn = null;
|
|
24237
24469
|
function getScheduledKitIds() {
|
|
@@ -24320,10 +24552,12 @@ function computeNextRun(schedule, from = /* @__PURE__ */ new Date()) {
|
|
|
24320
24552
|
const next = new Date(from);
|
|
24321
24553
|
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
24322
24554
|
const daysUntil = (schedule.dayOfWeek - next.getDay() + 7) % 7;
|
|
24323
|
-
if (daysUntil === 0
|
|
24324
|
-
|
|
24555
|
+
if (daysUntil === 0) {
|
|
24556
|
+
if (next <= from) {
|
|
24557
|
+
next.setDate(next.getDate() + 7);
|
|
24558
|
+
}
|
|
24325
24559
|
} else {
|
|
24326
|
-
next.setDate(next.getDate() +
|
|
24560
|
+
next.setDate(next.getDate() + daysUntil);
|
|
24327
24561
|
}
|
|
24328
24562
|
return next;
|
|
24329
24563
|
}
|
|
@@ -24450,18 +24684,19 @@ function tick(windowState, runtime2) {
|
|
|
24450
24684
|
fireNext();
|
|
24451
24685
|
}
|
|
24452
24686
|
function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
24687
|
+
stopScheduler();
|
|
24453
24688
|
broadcastFn = sendToAll;
|
|
24454
24689
|
loadJobs();
|
|
24455
24690
|
if (normalizeJobs()) {
|
|
24456
24691
|
saveJobs();
|
|
24457
24692
|
}
|
|
24458
|
-
removeIdleListener?.();
|
|
24459
24693
|
removeIdleListener = onAIStreamIdle(() => tick(windowState, runtime2));
|
|
24460
24694
|
const now = /* @__PURE__ */ new Date();
|
|
24461
24695
|
const msToNextMinute = (60 - now.getSeconds()) * 1e3 - now.getMilliseconds();
|
|
24462
|
-
setTimeout(() => {
|
|
24696
|
+
alignStartTimeout = setTimeout(() => {
|
|
24697
|
+
alignStartTimeout = null;
|
|
24463
24698
|
tick(windowState, runtime2);
|
|
24464
|
-
setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24699
|
+
pollInterval = setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24465
24700
|
}, msToNextMinute);
|
|
24466
24701
|
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
|
|
24467
24702
|
assertTrustedIpcSender(event);
|
|
@@ -24523,6 +24758,20 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24523
24758
|
return true;
|
|
24524
24759
|
});
|
|
24525
24760
|
}
|
|
24761
|
+
function stopScheduler() {
|
|
24762
|
+
if (removeIdleListener) {
|
|
24763
|
+
removeIdleListener();
|
|
24764
|
+
removeIdleListener = null;
|
|
24765
|
+
}
|
|
24766
|
+
if (alignStartTimeout) {
|
|
24767
|
+
clearTimeout(alignStartTimeout);
|
|
24768
|
+
alignStartTimeout = null;
|
|
24769
|
+
}
|
|
24770
|
+
if (pollInterval) {
|
|
24771
|
+
clearInterval(pollInterval);
|
|
24772
|
+
pollInterval = null;
|
|
24773
|
+
}
|
|
24774
|
+
}
|
|
24526
24775
|
const SAVE_DEBOUNCE_MS = 250;
|
|
24527
24776
|
const PROFILE_FIELDS = [
|
|
24528
24777
|
"label",
|
|
@@ -24920,6 +25169,7 @@ function registerAutofillHandlers(windowState) {
|
|
|
24920
25169
|
}
|
|
24921
25170
|
function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
24922
25171
|
const pageEventBuckets = /* @__PURE__ */ new Map();
|
|
25172
|
+
const isActiveWebContents = (webContentsId) => windowState.tabManager.getActiveTab()?.view.webContents.id === webContentsId;
|
|
24923
25173
|
const allowPageEvent = (webContentsId) => {
|
|
24924
25174
|
const now = Date.now();
|
|
24925
25175
|
const bucket = pageEventBuckets.get(webContentsId);
|
|
@@ -24956,14 +25206,20 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
24956
25206
|
if (!wc || wc.isDestroyed()) return;
|
|
24957
25207
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24958
25208
|
if (!allowPageEvent(wc.id)) return;
|
|
24959
|
-
|
|
25209
|
+
invalidateExtractionCache(wc);
|
|
25210
|
+
notePageMutationActivity(wc, sendToRendererViews, {
|
|
25211
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25212
|
+
});
|
|
24960
25213
|
});
|
|
24961
25214
|
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
24962
25215
|
const wc = event.sender;
|
|
24963
25216
|
if (!wc || wc.isDestroyed()) return;
|
|
24964
25217
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24965
25218
|
if (!allowPageEvent(wc.id)) return;
|
|
24966
|
-
|
|
25219
|
+
invalidateExtractionCache(wc);
|
|
25220
|
+
schedulePageSnapshotCapture(wc, sendToRendererViews, 0, {
|
|
25221
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25222
|
+
});
|
|
24967
25223
|
});
|
|
24968
25224
|
}
|
|
24969
25225
|
function renderReportAsMarkdown(report, traces) {
|
|
@@ -25232,9 +25488,40 @@ RULES:
|
|
|
25232
25488
|
}
|
|
25233
25489
|
const logger$7 = createLogger("ResearchOrchestrator");
|
|
25234
25490
|
const MAX_THREADS = 5;
|
|
25491
|
+
const MAX_TRACE_ARGS_CHARS = 1200;
|
|
25492
|
+
const MAX_TRACE_RESULT_CHARS = 2e3;
|
|
25235
25493
|
function clone$1(value) {
|
|
25236
25494
|
return structuredClone(value);
|
|
25237
25495
|
}
|
|
25496
|
+
function truncateTraceText(value, maxLength) {
|
|
25497
|
+
if (value.length <= maxLength) return value;
|
|
25498
|
+
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
|
|
25499
|
+
}
|
|
25500
|
+
function slimTraceArgs(args) {
|
|
25501
|
+
const json = JSON.stringify(args);
|
|
25502
|
+
if (json.length <= MAX_TRACE_ARGS_CHARS) return clone$1(args);
|
|
25503
|
+
return {
|
|
25504
|
+
_truncated: true,
|
|
25505
|
+
originalChars: json.length,
|
|
25506
|
+
preview: truncateTraceText(json, MAX_TRACE_ARGS_CHARS)
|
|
25507
|
+
};
|
|
25508
|
+
}
|
|
25509
|
+
function slimTraceResult(result) {
|
|
25510
|
+
if (result.length <= MAX_TRACE_RESULT_CHARS) return result;
|
|
25511
|
+
return [
|
|
25512
|
+
`[Trace result truncated from ${result.length} chars.]`,
|
|
25513
|
+
truncateTraceText(result, MAX_TRACE_RESULT_CHARS)
|
|
25514
|
+
].join("\n");
|
|
25515
|
+
}
|
|
25516
|
+
function createTraceToolCall(tool, args, result, startedAt) {
|
|
25517
|
+
return {
|
|
25518
|
+
tool,
|
|
25519
|
+
args: slimTraceArgs(args),
|
|
25520
|
+
result: slimTraceResult(result),
|
|
25521
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25522
|
+
durationMs: Date.now() - startedAt
|
|
25523
|
+
};
|
|
25524
|
+
}
|
|
25238
25525
|
function normalizeSourceDomain(value) {
|
|
25239
25526
|
const trimmed = value.trim().toLowerCase();
|
|
25240
25527
|
if (!trimmed) return "";
|
|
@@ -25643,13 +25930,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25643
25930
|
title: String(args.url || "excluded source"),
|
|
25644
25931
|
reason: msg
|
|
25645
25932
|
});
|
|
25646
|
-
trace.toolCalls.push(
|
|
25647
|
-
tool: name,
|
|
25648
|
-
args,
|
|
25649
|
-
result: msg,
|
|
25650
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25651
|
-
durationMs: 0
|
|
25652
|
-
});
|
|
25933
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25653
25934
|
return msg;
|
|
25654
25935
|
}
|
|
25655
25936
|
}
|
|
@@ -25657,25 +25938,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25657
25938
|
sourcesConsumed++;
|
|
25658
25939
|
if (sourcesConsumed > thread.sourceBudget) {
|
|
25659
25940
|
const msg = `Source budget (${thread.sourceBudget}) exceeded. Summarize findings and stop.`;
|
|
25660
|
-
trace.toolCalls.push(
|
|
25661
|
-
tool: name,
|
|
25662
|
-
args,
|
|
25663
|
-
result: msg,
|
|
25664
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25665
|
-
durationMs: 0
|
|
25666
|
-
});
|
|
25941
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25667
25942
|
return msg;
|
|
25668
25943
|
}
|
|
25669
25944
|
}
|
|
25670
25945
|
try {
|
|
25671
25946
|
const output = await executeAction(name, args, actionCtx);
|
|
25672
|
-
trace.toolCalls.push(
|
|
25673
|
-
tool: name,
|
|
25674
|
-
args,
|
|
25675
|
-
result: output,
|
|
25676
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25677
|
-
durationMs: Date.now() - t0
|
|
25678
|
-
});
|
|
25947
|
+
trace.toolCalls.push(createTraceToolCall(name, args, output, t0));
|
|
25679
25948
|
return output;
|
|
25680
25949
|
} catch (err) {
|
|
25681
25950
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -25690,13 +25959,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25690
25959
|
message: msg,
|
|
25691
25960
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25692
25961
|
});
|
|
25693
|
-
trace.toolCalls.push({
|
|
25694
|
-
tool: name,
|
|
25695
|
-
args,
|
|
25696
|
-
result: `Error: ${msg}`,
|
|
25697
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25698
|
-
durationMs: Date.now() - t0
|
|
25699
|
-
});
|
|
25962
|
+
trace.toolCalls.push(createTraceToolCall(name, args, `Error: ${msg}`, t0));
|
|
25700
25963
|
return `Error: ${msg}`;
|
|
25701
25964
|
}
|
|
25702
25965
|
},
|
|
@@ -25916,15 +26179,20 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25916
26179
|
this.emit();
|
|
25917
26180
|
}
|
|
25918
26181
|
}
|
|
26182
|
+
function assertVaultUnlocked() {
|
|
26183
|
+
assertFeatureUnlocked("vault", "Agent Credential Vault");
|
|
26184
|
+
}
|
|
25919
26185
|
function registerVaultHandlers() {
|
|
25920
26186
|
electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
|
|
25921
26187
|
assertTrustedIpcSender(event);
|
|
26188
|
+
assertVaultUnlocked();
|
|
25922
26189
|
return listEntries$1();
|
|
25923
26190
|
});
|
|
25924
26191
|
electron.ipcMain.handle(
|
|
25925
26192
|
Channels.VAULT_ADD,
|
|
25926
26193
|
(event, entry) => {
|
|
25927
26194
|
assertTrustedIpcSender(event);
|
|
26195
|
+
assertVaultUnlocked();
|
|
25928
26196
|
if (!entry || typeof entry !== "object") {
|
|
25929
26197
|
throw new Error("Invalid vault entry");
|
|
25930
26198
|
}
|
|
@@ -25951,6 +26219,7 @@ function registerVaultHandlers() {
|
|
|
25951
26219
|
Channels.VAULT_UPDATE,
|
|
25952
26220
|
(event, id, updates) => {
|
|
25953
26221
|
assertTrustedIpcSender(event);
|
|
26222
|
+
assertVaultUnlocked();
|
|
25954
26223
|
assertString(id, "id");
|
|
25955
26224
|
if (!updates || typeof updates !== "object") {
|
|
25956
26225
|
throw new Error("Invalid updates");
|
|
@@ -25960,12 +26229,14 @@ function registerVaultHandlers() {
|
|
|
25960
26229
|
);
|
|
25961
26230
|
electron.ipcMain.handle(Channels.VAULT_REMOVE, (event, id) => {
|
|
25962
26231
|
assertTrustedIpcSender(event);
|
|
26232
|
+
assertVaultUnlocked();
|
|
25963
26233
|
assertString(id, "id");
|
|
25964
26234
|
trackVaultAction("credential_removed");
|
|
25965
26235
|
return removeEntry$1(id);
|
|
25966
26236
|
});
|
|
25967
26237
|
electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (event, limit) => {
|
|
25968
26238
|
assertTrustedIpcSender(event);
|
|
26239
|
+
assertVaultUnlocked();
|
|
25969
26240
|
return readAuditLog$1(limit);
|
|
25970
26241
|
});
|
|
25971
26242
|
}
|
|
@@ -25983,14 +26254,19 @@ function normalizeTags(value) {
|
|
|
25983
26254
|
const tags = value.filter((tag) => typeof tag === "string").map((tag) => tag.trim()).filter(Boolean);
|
|
25984
26255
|
return tags.length > 0 ? tags : void 0;
|
|
25985
26256
|
}
|
|
26257
|
+
function assertHumanVaultUnlocked() {
|
|
26258
|
+
assertFeatureUnlocked("human_vault", "Passwords");
|
|
26259
|
+
}
|
|
25986
26260
|
function registerHumanVaultHandlers() {
|
|
25987
26261
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (event, domain) => {
|
|
25988
26262
|
assertTrustedIpcSender(event);
|
|
26263
|
+
assertHumanVaultUnlocked();
|
|
25989
26264
|
if (domain !== void 0) assertString(domain, "domain");
|
|
25990
26265
|
return domain ? findForDomain(domain) : listEntries();
|
|
25991
26266
|
});
|
|
25992
26267
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (event, id) => {
|
|
25993
26268
|
assertTrustedIpcSender(event);
|
|
26269
|
+
assertHumanVaultUnlocked();
|
|
25994
26270
|
assertString(id, "id");
|
|
25995
26271
|
return getEntrySafe(id);
|
|
25996
26272
|
});
|
|
@@ -25998,6 +26274,7 @@ function registerHumanVaultHandlers() {
|
|
|
25998
26274
|
Channels.HUMAN_VAULT_SAVE,
|
|
25999
26275
|
(event, input) => {
|
|
26000
26276
|
assertTrustedIpcSender(event);
|
|
26277
|
+
assertHumanVaultUnlocked();
|
|
26001
26278
|
if (!input || typeof input !== "object") {
|
|
26002
26279
|
throw new Error("Invalid credential entry");
|
|
26003
26280
|
}
|
|
@@ -26029,6 +26306,7 @@ function registerHumanVaultHandlers() {
|
|
|
26029
26306
|
Channels.HUMAN_VAULT_UPDATE,
|
|
26030
26307
|
(event, id, updates) => {
|
|
26031
26308
|
assertTrustedIpcSender(event);
|
|
26309
|
+
assertHumanVaultUnlocked();
|
|
26032
26310
|
assertString(id, "id");
|
|
26033
26311
|
if (!updates || typeof updates !== "object") {
|
|
26034
26312
|
throw new Error("Invalid updates");
|
|
@@ -26072,11 +26350,13 @@ function registerHumanVaultHandlers() {
|
|
|
26072
26350
|
);
|
|
26073
26351
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (event, id) => {
|
|
26074
26352
|
assertTrustedIpcSender(event);
|
|
26353
|
+
assertHumanVaultUnlocked();
|
|
26075
26354
|
assertString(id, "id");
|
|
26076
26355
|
return removeEntry(id);
|
|
26077
26356
|
});
|
|
26078
26357
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (event, limit) => {
|
|
26079
26358
|
assertTrustedIpcSender(event);
|
|
26359
|
+
assertHumanVaultUnlocked();
|
|
26080
26360
|
return readAuditLog(limit);
|
|
26081
26361
|
});
|
|
26082
26362
|
}
|
|
@@ -27212,6 +27492,10 @@ function registerHistoryHandlers() {
|
|
|
27212
27492
|
assertTrustedIpcSender(event);
|
|
27213
27493
|
return getState$1();
|
|
27214
27494
|
});
|
|
27495
|
+
electron.ipcMain.handle(Channels.HISTORY_LIST, (event, offset, limit) => {
|
|
27496
|
+
assertTrustedIpcSender(event);
|
|
27497
|
+
return listEntries$2(offset, limit);
|
|
27498
|
+
});
|
|
27215
27499
|
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (event, query) => {
|
|
27216
27500
|
assertTrustedIpcSender(event);
|
|
27217
27501
|
return search(query);
|
|
@@ -27366,7 +27650,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27366
27650
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (event, email) => {
|
|
27367
27651
|
assertTrustedIpcSender(event);
|
|
27368
27652
|
assertString(email, "email");
|
|
27369
|
-
if (!isValidEmail(email)) {
|
|
27653
|
+
if (!isValidEmail$1(email)) {
|
|
27370
27654
|
return errorResult("Invalid email format");
|
|
27371
27655
|
}
|
|
27372
27656
|
trackPremiumFunnel("activation_attempted");
|
|
@@ -27393,7 +27677,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27393
27677
|
assertString(email, "email");
|
|
27394
27678
|
assertString(code, "code");
|
|
27395
27679
|
assertString(challengeToken, "challengeToken");
|
|
27396
|
-
if (!isValidEmail(email)) {
|
|
27680
|
+
if (!isValidEmail$1(email)) {
|
|
27397
27681
|
return errorResult("Invalid email format", {
|
|
27398
27682
|
state: getPremiumState()
|
|
27399
27683
|
});
|
|
@@ -27597,6 +27881,47 @@ function registerCodexHandlers() {
|
|
|
27597
27881
|
return { ok: true };
|
|
27598
27882
|
});
|
|
27599
27883
|
}
|
|
27884
|
+
const SUPPORT_API = process.env.VESSEL_SUPPORT_API || process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
27885
|
+
const MAX_FEEDBACK_MESSAGE_LENGTH = 5e3;
|
|
27886
|
+
const FEEDBACK_REQUEST_TIMEOUT_MS = 15e3;
|
|
27887
|
+
function isValidEmail(email) {
|
|
27888
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
27889
|
+
}
|
|
27890
|
+
async function submitFeedback(payload) {
|
|
27891
|
+
const email = payload.email.trim().toLowerCase();
|
|
27892
|
+
const message = payload.message.trim();
|
|
27893
|
+
if (!isValidEmail(email)) {
|
|
27894
|
+
return errorResult("Enter a valid reply email.");
|
|
27895
|
+
}
|
|
27896
|
+
if (!message) {
|
|
27897
|
+
return errorResult("Write a feedback message before sending.");
|
|
27898
|
+
}
|
|
27899
|
+
if (message.length > MAX_FEEDBACK_MESSAGE_LENGTH) {
|
|
27900
|
+
return errorResult(
|
|
27901
|
+
`Feedback must be ${MAX_FEEDBACK_MESSAGE_LENGTH.toLocaleString()} characters or less.`
|
|
27902
|
+
);
|
|
27903
|
+
}
|
|
27904
|
+
try {
|
|
27905
|
+
const signal = AbortSignal.timeout(FEEDBACK_REQUEST_TIMEOUT_MS);
|
|
27906
|
+
const res = await fetch(`${SUPPORT_API}/feedback`, {
|
|
27907
|
+
method: "POST",
|
|
27908
|
+
headers: { "Content-Type": "application/json" },
|
|
27909
|
+
signal,
|
|
27910
|
+
body: JSON.stringify({
|
|
27911
|
+
email,
|
|
27912
|
+
message,
|
|
27913
|
+
source: payload.source
|
|
27914
|
+
})
|
|
27915
|
+
});
|
|
27916
|
+
const data = await res.json().catch(() => ({}));
|
|
27917
|
+
if (!res.ok) {
|
|
27918
|
+
return errorResult(data.error || `HTTP ${res.status}`);
|
|
27919
|
+
}
|
|
27920
|
+
return okResult();
|
|
27921
|
+
} catch (error) {
|
|
27922
|
+
return errorResult(getErrorMessage(error, "Failed to send feedback."));
|
|
27923
|
+
}
|
|
27924
|
+
}
|
|
27600
27925
|
const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
|
|
27601
27926
|
const ALLOWED_PERMISSION_TYPES = /* @__PURE__ */ new Set([
|
|
27602
27927
|
"clipboard-read",
|
|
@@ -28110,7 +28435,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28110
28435
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
28111
28436
|
tabManager,
|
|
28112
28437
|
runtime2,
|
|
28113
|
-
history,
|
|
28438
|
+
compactProviderHistory(history),
|
|
28114
28439
|
researchOrchestrator
|
|
28115
28440
|
);
|
|
28116
28441
|
} catch (err) {
|
|
@@ -28262,6 +28587,12 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28262
28587
|
requireTrusted(event);
|
|
28263
28588
|
return regenerateMcpAuthToken();
|
|
28264
28589
|
});
|
|
28590
|
+
electron.ipcMain.handle(Channels.SUPPORT_SUBMIT_FEEDBACK, async (event, email, message) => {
|
|
28591
|
+
requireTrusted(event);
|
|
28592
|
+
assertString(email, "email");
|
|
28593
|
+
assertString(message, "message");
|
|
28594
|
+
return submitFeedback({ email, message, source: "settings_account" });
|
|
28595
|
+
});
|
|
28265
28596
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
|
|
28266
28597
|
requireTrusted(event);
|
|
28267
28598
|
assertString(key2, "key");
|
|
@@ -29903,7 +30234,7 @@ async function bootstrap() {
|
|
|
29903
30234
|
);
|
|
29904
30235
|
}
|
|
29905
30236
|
};
|
|
29906
|
-
const windowState = createMainWindow((tabs, activeId) => {
|
|
30237
|
+
const windowState = createMainWindow((tabs, activeId, meta) => {
|
|
29907
30238
|
windowState.chromeView.webContents.send(
|
|
29908
30239
|
Channels.TAB_STATE_UPDATE,
|
|
29909
30240
|
tabs,
|
|
@@ -29911,7 +30242,9 @@ async function bootstrap() {
|
|
|
29911
30242
|
);
|
|
29912
30243
|
void syncActiveHighlightCount(windowState);
|
|
29913
30244
|
layoutViews(windowState);
|
|
29914
|
-
|
|
30245
|
+
if (meta.persistSession) {
|
|
30246
|
+
runtime?.onTabStateChanged();
|
|
30247
|
+
}
|
|
29915
30248
|
});
|
|
29916
30249
|
let didRevealMainWindow = false;
|
|
29917
30250
|
const revealMainWindow = () => {
|