@quanta-intellect/vessel-browser 0.1.104 → 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 +867 -557
- 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,6 +4623,15 @@ 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
4637
|
if (premium.status !== "active" && premium.status !== "trialing") {
|
|
@@ -4629,6 +4670,22 @@ function resetPremium() {
|
|
|
4629
4670
|
function isToolGated(toolName) {
|
|
4630
4671
|
return PREMIUM_TOOLS.has(toolName) && !isPremium();
|
|
4631
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
|
+
}
|
|
4632
4689
|
async function getCheckoutUrl(email) {
|
|
4633
4690
|
try {
|
|
4634
4691
|
const params = new URLSearchParams();
|
|
@@ -5320,6 +5377,17 @@ const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
|
5320
5377
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5321
5378
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5322
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
|
+
}
|
|
5323
5391
|
const EMPTY_PAGE_CONTENT = {
|
|
5324
5392
|
title: "",
|
|
5325
5393
|
content: "",
|
|
@@ -6206,9 +6274,14 @@ async function extractContentInner(webContents) {
|
|
|
6206
6274
|
);
|
|
6207
6275
|
}
|
|
6208
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
|
+
}
|
|
6209
6282
|
try {
|
|
6210
6283
|
const timeoutMs = await estimateExtractionTimeout(webContents);
|
|
6211
|
-
|
|
6284
|
+
const content = await Promise.race([
|
|
6212
6285
|
extractContentInner(webContents),
|
|
6213
6286
|
new Promise(
|
|
6214
6287
|
(_, reject) => setTimeout(
|
|
@@ -6217,6 +6290,15 @@ async function extractContent(webContents) {
|
|
|
6217
6290
|
)
|
|
6218
6291
|
)
|
|
6219
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;
|
|
6220
6302
|
} catch (err) {
|
|
6221
6303
|
const url = webContents.getURL() || "";
|
|
6222
6304
|
let domain = "unknown";
|
|
@@ -6311,6 +6393,7 @@ function attachDestroyCleanup(wc) {
|
|
|
6311
6393
|
const MAX_PERSISTED_DIFF_BURSTS = 50;
|
|
6312
6394
|
const MAX_HISTORY_DAYS = 30;
|
|
6313
6395
|
const SAVE_DEBOUNCE_MS$2 = 500;
|
|
6396
|
+
const BACKGROUND_DIFF_CAPTURE_DELAY_MS = 15e3;
|
|
6314
6397
|
function getHistoryFilePath() {
|
|
6315
6398
|
return path.join(electron.app.getPath("userData"), "vessel-page-diff-history.json");
|
|
6316
6399
|
}
|
|
@@ -6443,7 +6526,7 @@ function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
|
6443
6526
|
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
6444
6527
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
6445
6528
|
}
|
|
6446
|
-
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
6529
|
+
function scheduleTimerAt(wc, sendToRendererViews, dueAt, options = {}) {
|
|
6447
6530
|
attachDestroyCleanup(wc);
|
|
6448
6531
|
const wcId = wc.id;
|
|
6449
6532
|
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
@@ -6451,14 +6534,24 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
|
6451
6534
|
const timer = setTimeout(() => {
|
|
6452
6535
|
cleanupTimersForWcId(wcId);
|
|
6453
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
|
+
}
|
|
6454
6546
|
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
6455
6547
|
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
6456
6548
|
}, Math.max(0, dueAt - Date.now()));
|
|
6457
6549
|
pendingPageSnapshotTimers.set(wcId, timer);
|
|
6458
6550
|
pendingPageSnapshotDueAt.set(wcId, dueAt);
|
|
6459
6551
|
}
|
|
6460
|
-
function notePageMutationActivity(wc, sendToRendererViews) {
|
|
6552
|
+
function notePageMutationActivity(wc, sendToRendererViews, options = {}) {
|
|
6461
6553
|
if (wc.isDestroyed()) return;
|
|
6554
|
+
if (options.isActive && !options.isActive()) return;
|
|
6462
6555
|
const wcId = wc.id;
|
|
6463
6556
|
const now = Date.now();
|
|
6464
6557
|
lastMutationActivityAt.set(wcId, now);
|
|
@@ -6466,18 +6559,19 @@ function notePageMutationActivity(wc, sendToRendererViews) {
|
|
|
6466
6559
|
if (existingDueAt == null) return;
|
|
6467
6560
|
const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
|
|
6468
6561
|
if (nextDueAt <= existingDueAt) return;
|
|
6469
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6562
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6470
6563
|
}
|
|
6471
|
-
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
6564
|
+
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0, options = {}) {
|
|
6472
6565
|
if (wc.isDestroyed()) return;
|
|
6473
6566
|
const wcId = wc.id;
|
|
6474
6567
|
const now = Date.now();
|
|
6475
|
-
const
|
|
6568
|
+
const effectiveDelayMs = options.isActive && !options.isActive() ? Math.max(delayMs, BACKGROUND_DIFF_CAPTURE_DELAY_MS) : delayMs;
|
|
6569
|
+
const nextDueAt = computeNextSnapshotDueAt(wcId, now, effectiveDelayMs);
|
|
6476
6570
|
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
6477
6571
|
if (existingDueAt != null && existingDueAt >= nextDueAt) {
|
|
6478
6572
|
return;
|
|
6479
6573
|
}
|
|
6480
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6574
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6481
6575
|
}
|
|
6482
6576
|
function enableClipboardShortcuts(view) {
|
|
6483
6577
|
view.webContents.on("before-input-event", (event, input) => {
|
|
@@ -6693,6 +6787,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
6693
6787
|
sidebarView.webContents.send(channel, ...args);
|
|
6694
6788
|
};
|
|
6695
6789
|
tabManager.onPageLoad((url, wc) => {
|
|
6790
|
+
const activeWc = tabManager.getActiveTab()?.view.webContents;
|
|
6791
|
+
if (activeWc?.id !== wc.id) return;
|
|
6696
6792
|
void capturePageSnapshot(url, wc, sendToRendererViews);
|
|
6697
6793
|
});
|
|
6698
6794
|
const state2 = {
|
|
@@ -7459,6 +7555,36 @@ const PROVIDERS = {
|
|
|
7459
7555
|
apiKeyHint: "Optional — only if your endpoint requires authentication"
|
|
7460
7556
|
}
|
|
7461
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;
|
|
7462
7588
|
const SAFE_TOOL_ALIASES = {
|
|
7463
7589
|
goto_url: "navigate",
|
|
7464
7590
|
go_to_url: "navigate",
|
|
@@ -7523,399 +7649,109 @@ function normalizeToolAlias(name) {
|
|
|
7523
7649
|
}
|
|
7524
7650
|
return name;
|
|
7525
7651
|
}
|
|
7526
|
-
function
|
|
7527
|
-
const
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
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]);
|
|
7531
7658
|
}
|
|
7532
|
-
function
|
|
7533
|
-
|
|
7659
|
+
function normalizeToolToken(value) {
|
|
7660
|
+
return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7661
|
+
}
|
|
7662
|
+
function canonicalizeUrlLike(value) {
|
|
7534
7663
|
try {
|
|
7535
|
-
const url = new URL(
|
|
7536
|
-
|
|
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
|
+
}
|
|
7537
7673
|
} catch {
|
|
7538
|
-
return false;
|
|
7539
7674
|
}
|
|
7675
|
+
return value.trim();
|
|
7540
7676
|
}
|
|
7541
|
-
function
|
|
7542
|
-
const
|
|
7543
|
-
|
|
7544
|
-
if (
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
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}`;
|
|
7548
7683
|
}
|
|
7549
|
-
return
|
|
7550
|
-
}
|
|
7551
|
-
const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
|
|
7552
|
-
const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
7553
|
-
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7554
|
-
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7555
|
-
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7556
|
-
const logger$j = createLogger("OpenAIProvider");
|
|
7557
|
-
function shouldDebugAgentLoop() {
|
|
7558
|
-
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7559
|
-
return value === "1" || value === "true";
|
|
7560
|
-
}
|
|
7561
|
-
function previewDebugValue(value, maxLength = 800) {
|
|
7562
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
7563
|
-
if (normalized.length <= maxLength) return normalized;
|
|
7564
|
-
return `${normalized.slice(0, maxLength)}…`;
|
|
7565
|
-
}
|
|
7566
|
-
function previewToolDebugContent(content) {
|
|
7567
|
-
return previewDebugValue(content, 500);
|
|
7568
|
-
}
|
|
7569
|
-
function toOpenAITools(tools) {
|
|
7570
|
-
return tools.map((t) => ({
|
|
7571
|
-
type: "function",
|
|
7572
|
-
function: {
|
|
7573
|
-
name: t.name,
|
|
7574
|
-
description: t.description ?? "",
|
|
7575
|
-
parameters: t.input_schema
|
|
7576
|
-
}
|
|
7577
|
-
}));
|
|
7578
|
-
}
|
|
7579
|
-
function agentTemperatureForProfile(profile) {
|
|
7580
|
-
return profile === "compact" ? 0.2 : void 0;
|
|
7581
|
-
}
|
|
7582
|
-
function modelLikelySupportsOpenAIReasoningEffort(model) {
|
|
7583
|
-
return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
|
|
7684
|
+
return null;
|
|
7584
7685
|
}
|
|
7585
|
-
function
|
|
7586
|
-
const
|
|
7587
|
-
if (!
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
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
|
+
};
|
|
7592
7714
|
}
|
|
7593
|
-
|
|
7594
|
-
case "low":
|
|
7595
|
-
return "low";
|
|
7596
|
-
case "medium":
|
|
7597
|
-
return "medium";
|
|
7598
|
-
case "high":
|
|
7599
|
-
return "high";
|
|
7600
|
-
case "max":
|
|
7601
|
-
return "xhigh";
|
|
7602
|
-
default:
|
|
7603
|
-
return void 0;
|
|
7715
|
+
}
|
|
7604
7716
|
}
|
|
7717
|
+
return null;
|
|
7605
7718
|
}
|
|
7606
|
-
function
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
${stateReminder}` : "") + (phaseReminder ? `
|
|
7615
|
-
${phaseReminder}` : "")
|
|
7616
|
-
};
|
|
7617
|
-
}
|
|
7618
|
-
function extractSingleGoalDomain(goal) {
|
|
7619
|
-
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
|
|
7620
|
-
if (!matches || matches.length !== 1) return null;
|
|
7621
|
-
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
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();
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
return null;
|
|
7622
7727
|
}
|
|
7623
|
-
function
|
|
7624
|
-
const
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
7628
|
-
const assistant = assistantText.toLowerCase();
|
|
7629
|
-
const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
|
|
7630
|
-
const lines = [
|
|
7631
|
-
`The task is still in progress: ${userMessage}`,
|
|
7632
|
-
`Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
|
|
7633
|
-
];
|
|
7634
|
-
if (alreadyOnGoalSite) {
|
|
7635
|
-
lines.push(
|
|
7636
|
-
`You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
|
|
7637
|
-
);
|
|
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());
|
|
7638
7732
|
}
|
|
7639
|
-
if (
|
|
7640
|
-
|
|
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;
|
|
7641
7741
|
}
|
|
7642
|
-
if (
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
);
|
|
7656
|
-
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);
|
|
7657
|
-
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);
|
|
7658
|
-
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);
|
|
7659
|
-
const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
|
|
7660
|
-
text
|
|
7661
|
-
);
|
|
7662
|
-
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);
|
|
7663
|
-
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(
|
|
7664
|
-
text
|
|
7665
|
-
);
|
|
7666
|
-
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(
|
|
7667
|
-
text
|
|
7668
|
-
);
|
|
7669
|
-
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(
|
|
7670
|
-
text
|
|
7671
|
-
) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
|
|
7672
|
-
text
|
|
7673
|
-
);
|
|
7674
|
-
const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
|
|
7675
|
-
text
|
|
7676
|
-
) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
|
|
7677
|
-
text
|
|
7678
|
-
);
|
|
7679
|
-
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(
|
|
7680
|
-
text
|
|
7681
|
-
);
|
|
7682
|
-
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(
|
|
7683
|
-
text
|
|
7684
|
-
);
|
|
7685
|
-
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(
|
|
7686
|
-
text
|
|
7687
|
-
);
|
|
7688
|
-
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(
|
|
7689
|
-
text
|
|
7690
|
-
) && !/(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(
|
|
7691
|
-
text
|
|
7692
|
-
);
|
|
7693
|
-
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
|
|
7694
|
-
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.`;
|
|
7695
|
-
}
|
|
7696
|
-
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
|
|
7697
|
-
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.`;
|
|
7698
|
-
}
|
|
7699
|
-
if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
|
|
7700
|
-
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.`;
|
|
7701
|
-
}
|
|
7702
|
-
if (wantsCart && skippedSingleResultSignals && !selectedItems) {
|
|
7703
|
-
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.`;
|
|
7704
|
-
}
|
|
7705
|
-
if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
|
|
7706
|
-
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.`;
|
|
7707
|
-
}
|
|
7708
|
-
if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
|
|
7709
|
-
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.`;
|
|
7710
|
-
}
|
|
7711
|
-
if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
|
|
7712
|
-
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.`;
|
|
7713
|
-
}
|
|
7714
|
-
if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
|
|
7715
|
-
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.`;
|
|
7716
|
-
}
|
|
7717
|
-
if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
|
|
7718
|
-
return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
|
|
7719
|
-
}
|
|
7720
|
-
return "";
|
|
7721
|
-
}
|
|
7722
|
-
function buildLatestStateReminder(toolResultPreview) {
|
|
7723
|
-
const text = toolResultPreview.trim();
|
|
7724
|
-
if (!text) return "";
|
|
7725
|
-
const stateMatch = text.match(
|
|
7726
|
-
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
7727
|
-
);
|
|
7728
|
-
if (stateMatch) {
|
|
7729
|
-
const url = stateMatch[1]?.trim();
|
|
7730
|
-
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
7731
|
-
if (url) {
|
|
7732
|
-
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7733
|
-
}
|
|
7734
|
-
}
|
|
7735
|
-
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
7736
|
-
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
7737
|
-
if (structuredUrl) {
|
|
7738
|
-
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7739
|
-
}
|
|
7740
|
-
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
7741
|
-
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
7742
|
-
if (navigatedUrl) {
|
|
7743
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7744
|
-
}
|
|
7745
|
-
return "";
|
|
7746
|
-
}
|
|
7747
|
-
function shouldRecoverCompactStall(text, userMessage) {
|
|
7748
|
-
const trimmed = text.trim().toLowerCase();
|
|
7749
|
-
if (!trimmed) return true;
|
|
7750
|
-
if (trimmed.length <= 160 && trimmed.includes("?")) return true;
|
|
7751
|
-
if (userMessage && buildPhaseReminder(userMessage, text)) {
|
|
7752
|
-
return true;
|
|
7753
|
-
}
|
|
7754
|
-
const repetitivePlanningSignals = [
|
|
7755
|
-
"next step:",
|
|
7756
|
-
"i will now inspect",
|
|
7757
|
-
"i will now read",
|
|
7758
|
-
"i will now click",
|
|
7759
|
-
"i'll use readpage",
|
|
7760
|
-
"i'll use read_page",
|
|
7761
|
-
"i'll start by clicking",
|
|
7762
|
-
"i have clicked on five different book titles",
|
|
7763
|
-
"clicked on five different book titles",
|
|
7764
|
-
"i'll begin with",
|
|
7765
|
-
"if the selection is unclear"
|
|
7766
|
-
];
|
|
7767
|
-
if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
7768
|
-
return true;
|
|
7769
|
-
}
|
|
7770
|
-
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(
|
|
7771
|
-
trimmed
|
|
7772
|
-
) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
|
|
7773
|
-
trimmed
|
|
7774
|
-
);
|
|
7775
|
-
if (falseCartSuccessWithoutConfirmation) {
|
|
7776
|
-
return true;
|
|
7777
|
-
}
|
|
7778
|
-
const completionSignals = [
|
|
7779
|
-
"i found",
|
|
7780
|
-
"i chose",
|
|
7781
|
-
"i selected",
|
|
7782
|
-
"i added",
|
|
7783
|
-
"here are",
|
|
7784
|
-
"these are",
|
|
7785
|
-
"recommendations",
|
|
7786
|
-
"reasoning",
|
|
7787
|
-
"why i chose",
|
|
7788
|
-
"added them to the cart"
|
|
7789
|
-
];
|
|
7790
|
-
if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
7791
|
-
return false;
|
|
7792
|
-
}
|
|
7793
|
-
return [
|
|
7794
|
-
"what are you hoping",
|
|
7795
|
-
"what would you like",
|
|
7796
|
-
"how can i help",
|
|
7797
|
-
"let me know",
|
|
7798
|
-
"are you looking for",
|
|
7799
|
-
"just browsing",
|
|
7800
|
-
"i need to",
|
|
7801
|
-
"i will",
|
|
7802
|
-
"i'll",
|
|
7803
|
-
"since i cannot see",
|
|
7804
|
-
"since i can't see",
|
|
7805
|
-
"cannot see the current page",
|
|
7806
|
-
"scroll down to",
|
|
7807
|
-
"load more results",
|
|
7808
|
-
"as placeholders",
|
|
7809
|
-
"would you like me to proceed",
|
|
7810
|
-
"action:",
|
|
7811
|
-
"one moment",
|
|
7812
|
-
"i will now navigate",
|
|
7813
|
-
"navigating to ",
|
|
7814
|
-
"this will take me",
|
|
7815
|
-
"i will use the browser"
|
|
7816
|
-
].some((pattern) => trimmed.includes(pattern));
|
|
7817
|
-
}
|
|
7818
|
-
function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
|
|
7819
|
-
return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
|
|
7820
|
-
}
|
|
7821
|
-
function stableToolSignature(name, args) {
|
|
7822
|
-
const canonicalArgs = canonicalizeArgsForTool(name, args);
|
|
7823
|
-
const sortedEntries = Object.entries(canonicalArgs).sort(
|
|
7824
|
-
([left], [right]) => left.localeCompare(right)
|
|
7825
|
-
);
|
|
7826
|
-
return JSON.stringify([name, sortedEntries]);
|
|
7827
|
-
}
|
|
7828
|
-
function normalizeToolToken(value) {
|
|
7829
|
-
return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7830
|
-
}
|
|
7831
|
-
function canonicalizeUrlLike(value) {
|
|
7832
|
-
try {
|
|
7833
|
-
const url = new URL(value.trim());
|
|
7834
|
-
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
7835
|
-
url.hostname = url.hostname.replace(/^www\./, "");
|
|
7836
|
-
url.hash = "";
|
|
7837
|
-
if (url.pathname.endsWith("/") && url.pathname !== "/") {
|
|
7838
|
-
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
7839
|
-
}
|
|
7840
|
-
return url.toString();
|
|
7841
|
-
}
|
|
7842
|
-
} catch {
|
|
7843
|
-
}
|
|
7844
|
-
return value.trim();
|
|
7845
|
-
}
|
|
7846
|
-
function toLikelyUrl(value) {
|
|
7847
|
-
const trimmed = value.trim().replace(/^["']|["']$/g, "");
|
|
7848
|
-
if (!trimmed) return null;
|
|
7849
|
-
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
7850
|
-
if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
|
|
7851
|
-
return `https://${trimmed}`;
|
|
7852
|
-
}
|
|
7853
|
-
return null;
|
|
7854
|
-
}
|
|
7855
|
-
function scalarArgsForTool(name, scalar) {
|
|
7856
|
-
const trimmed = scalar.trim();
|
|
7857
|
-
if (!trimmed) return null;
|
|
7858
|
-
if (name === "navigate") {
|
|
7859
|
-
const url = toLikelyUrl(trimmed);
|
|
7860
|
-
return url ? { url } : null;
|
|
7861
|
-
}
|
|
7862
|
-
if (name === "search") {
|
|
7863
|
-
return { query: trimmed.replace(/^["']|["']$/g, "") };
|
|
7864
|
-
}
|
|
7865
|
-
if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
|
|
7866
|
-
return { text: trimmed.replace(/^["']|["']$/g, "") };
|
|
7867
|
-
}
|
|
7868
|
-
if (name === "read_page") {
|
|
7869
|
-
const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
|
|
7870
|
-
if (mode) return { mode };
|
|
7871
|
-
}
|
|
7872
|
-
if (name === "save_bookmark") {
|
|
7873
|
-
const url = toLikelyUrl(trimmed);
|
|
7874
|
-
if (url) return { url };
|
|
7875
|
-
const lastSpace = trimmed.lastIndexOf(" ");
|
|
7876
|
-
if (lastSpace > 0) {
|
|
7877
|
-
const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
|
|
7878
|
-
if (maybeUrl) return { url: maybeUrl, title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "") };
|
|
7879
|
-
}
|
|
7880
|
-
}
|
|
7881
|
-
return null;
|
|
7882
|
-
}
|
|
7883
|
-
function firstStringArg(args, keys) {
|
|
7884
|
-
for (const key2 of keys) {
|
|
7885
|
-
const value = args[key2];
|
|
7886
|
-
if (typeof value === "string" && value.trim()) {
|
|
7887
|
-
return value.trim();
|
|
7888
|
-
}
|
|
7889
|
-
}
|
|
7890
|
-
return null;
|
|
7891
|
-
}
|
|
7892
|
-
function normalizeElementTargetArgs(args) {
|
|
7893
|
-
const normalized = { ...args };
|
|
7894
|
-
if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
|
|
7895
|
-
normalized.index = Number(normalized.index.trim());
|
|
7896
|
-
}
|
|
7897
|
-
if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
|
|
7898
|
-
const selector = firstStringArg(normalized, [
|
|
7899
|
-
"cssSelector",
|
|
7900
|
-
"css_selector",
|
|
7901
|
-
"querySelector",
|
|
7902
|
-
"query_selector"
|
|
7903
|
-
]);
|
|
7904
|
-
if (selector) normalized.selector = selector;
|
|
7905
|
-
}
|
|
7906
|
-
if (typeof normalized.text !== "string" || !normalized.text.trim()) {
|
|
7907
|
-
const text = firstStringArg(normalized, [
|
|
7908
|
-
"label",
|
|
7909
|
-
"title",
|
|
7910
|
-
"name",
|
|
7911
|
-
"target",
|
|
7912
|
-
"element",
|
|
7913
|
-
"linkText",
|
|
7914
|
-
"link_text",
|
|
7915
|
-
"ariaLabel",
|
|
7916
|
-
"aria_label"
|
|
7917
|
-
]);
|
|
7918
|
-
if (text) normalized.text = text;
|
|
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;
|
|
7919
7755
|
}
|
|
7920
7756
|
return normalized;
|
|
7921
7757
|
}
|
|
@@ -8067,21 +7903,13 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
8067
7903
|
if (availableToolNames.has("search") && (/search|find|lookup|query/.test(normalized) || normalized === "google" || normalized.startsWith("google_"))) {
|
|
8068
7904
|
return "search";
|
|
8069
7905
|
}
|
|
8070
|
-
if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
|
|
8071
|
-
return "scroll";
|
|
8072
|
-
}
|
|
8073
|
-
if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
|
|
8074
|
-
return "read_page";
|
|
8075
|
-
}
|
|
8076
|
-
return aliased;
|
|
8077
|
-
}
|
|
8078
|
-
function logAgentLoopDebug(payload) {
|
|
8079
|
-
if (!shouldDebugAgentLoop()) return;
|
|
8080
|
-
try {
|
|
8081
|
-
logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
8082
|
-
} catch (err) {
|
|
8083
|
-
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";
|
|
8084
7911
|
}
|
|
7912
|
+
return aliased;
|
|
8085
7913
|
}
|
|
8086
7914
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
8087
7915
|
const trimmed = text.trim();
|
|
@@ -8198,7 +8026,280 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
|
|
|
8198
8026
|
});
|
|
8199
8027
|
return recovered;
|
|
8200
8028
|
}
|
|
8201
|
-
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
|
+
}
|
|
8202
8303
|
}
|
|
8203
8304
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
8204
8305
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
@@ -12665,6 +12766,8 @@ const UNSORTED_ID = "unsorted";
|
|
|
12665
12766
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
12666
12767
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
12667
12768
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
12769
|
+
const DEFAULT_BOOKMARK_SEARCH_LIMIT = 50;
|
|
12770
|
+
const MAX_BOOKMARK_SEARCH_LIMIT = 200;
|
|
12668
12771
|
let state$2 = null;
|
|
12669
12772
|
const listeners = /* @__PURE__ */ new Set();
|
|
12670
12773
|
function cloneState(current) {
|
|
@@ -12673,6 +12776,10 @@ function cloneState(current) {
|
|
|
12673
12776
|
bookmarks: current.bookmarks.map((bookmark) => ({ ...bookmark }))
|
|
12674
12777
|
};
|
|
12675
12778
|
}
|
|
12779
|
+
function getFolderMap() {
|
|
12780
|
+
load$1();
|
|
12781
|
+
return new Map(state$2.folders.map((folder) => [folder.id, folder]));
|
|
12782
|
+
}
|
|
12676
12783
|
function getBookmarksPath() {
|
|
12677
12784
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
12678
12785
|
}
|
|
@@ -12899,13 +13006,16 @@ function listFolderOverviews() {
|
|
|
12899
13006
|
}))
|
|
12900
13007
|
];
|
|
12901
13008
|
}
|
|
12902
|
-
function searchBookmarks(query) {
|
|
13009
|
+
function searchBookmarks(query, limit = DEFAULT_BOOKMARK_SEARCH_LIMIT) {
|
|
12903
13010
|
load$1();
|
|
12904
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
|
+
);
|
|
12905
13017
|
return state$2.bookmarks.map((bookmark) => {
|
|
12906
|
-
const folder =
|
|
12907
|
-
(item) => item.id === bookmark.folderId
|
|
12908
|
-
);
|
|
13018
|
+
const folder = foldersById.get(bookmark.folderId);
|
|
12909
13019
|
const { matchedFields, score } = getBookmarkSearchMatch({
|
|
12910
13020
|
query,
|
|
12911
13021
|
title: bookmark.title,
|
|
@@ -12929,7 +13039,7 @@ function searchBookmarks(query) {
|
|
|
12929
13039
|
score: result.score
|
|
12930
13040
|
})).sort(
|
|
12931
13041
|
(a, b) => b.score - a.score || b.bookmark.savedAt.localeCompare(a.bookmark.savedAt)
|
|
12932
|
-
);
|
|
13042
|
+
).slice(0, safeLimit);
|
|
12933
13043
|
}
|
|
12934
13044
|
function createFolderWithSummary(name, summary) {
|
|
12935
13045
|
load$1();
|
|
@@ -14197,6 +14307,97 @@ function formatCompactToolResult(name, result) {
|
|
|
14197
14307
|
return limitText(result, 18, 1400);
|
|
14198
14308
|
}
|
|
14199
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
|
+
}
|
|
14200
14401
|
const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
|
|
14201
14402
|
const HUGGING_FACE_MODEL_TASKS = [
|
|
14202
14403
|
{
|
|
@@ -14460,9 +14661,12 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
14460
14661
|
class TabMutex {
|
|
14461
14662
|
queue = Promise.resolve();
|
|
14462
14663
|
enqueue(fn) {
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14664
|
+
const run = this.queue.then(fn, fn);
|
|
14665
|
+
this.queue = run.then(
|
|
14666
|
+
() => void 0,
|
|
14667
|
+
() => void 0
|
|
14668
|
+
);
|
|
14669
|
+
return run;
|
|
14466
14670
|
}
|
|
14467
14671
|
}
|
|
14468
14672
|
const logger$e = createLogger("PageActions");
|
|
@@ -15290,45 +15494,9 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15290
15494
|
}
|
|
15291
15495
|
}
|
|
15292
15496
|
}
|
|
15293
|
-
const ADD_TO_CART_PATTERNS = [
|
|
15294
|
-
"add to cart",
|
|
15295
|
-
"add to bag",
|
|
15296
|
-
"add to basket",
|
|
15297
|
-
"add to my cart",
|
|
15298
|
-
"add to my bag",
|
|
15299
|
-
"add to my basket",
|
|
15300
|
-
"add item to cart",
|
|
15301
|
-
"add item to bag",
|
|
15302
|
-
"add item to basket"
|
|
15303
|
-
];
|
|
15304
|
-
const recentCartClicks = /* @__PURE__ */ new Map();
|
|
15305
|
-
const CART_CLICK_COOLDOWN_MS = 15e3;
|
|
15306
|
-
const CART_ADDED_TTL_MS = 30 * 6e4;
|
|
15307
|
-
const cartAddedProducts = /* @__PURE__ */ new Map();
|
|
15308
15497
|
let clickStreakUrl = null;
|
|
15309
15498
|
let clickStreakCount = 0;
|
|
15310
15499
|
const CLICK_STREAK_THRESHOLD = 3;
|
|
15311
|
-
function isAddToCartText(text) {
|
|
15312
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
15313
|
-
return ADD_TO_CART_PATTERNS.some((p) => normalized.includes(p));
|
|
15314
|
-
}
|
|
15315
|
-
function recordCartClick(url, text) {
|
|
15316
|
-
recentCartClicks.set(url, { text, ts: Date.now() });
|
|
15317
|
-
for (const [key2, entry] of recentCartClicks) {
|
|
15318
|
-
if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
|
|
15319
|
-
recentCartClicks.delete(key2);
|
|
15320
|
-
}
|
|
15321
|
-
}
|
|
15322
|
-
}
|
|
15323
|
-
function isDuplicateCartClick(url, text) {
|
|
15324
|
-
const recent = recentCartClicks.get(url);
|
|
15325
|
-
if (!recent) return false;
|
|
15326
|
-
if (Date.now() - recent.ts > CART_CLICK_COOLDOWN_MS) {
|
|
15327
|
-
recentCartClicks.delete(url);
|
|
15328
|
-
return false;
|
|
15329
|
-
}
|
|
15330
|
-
return isAddToCartText(text);
|
|
15331
|
-
}
|
|
15332
15500
|
async function getProductPageTitle(wc) {
|
|
15333
15501
|
try {
|
|
15334
15502
|
const heading = await executePageScript(
|
|
@@ -15353,54 +15521,8 @@ async function getProductPageTitle(wc) {
|
|
|
15353
15521
|
}
|
|
15354
15522
|
return wc.getTitle() || "";
|
|
15355
15523
|
}
|
|
15356
|
-
function normalizeCartProductKey(url) {
|
|
15357
|
-
try {
|
|
15358
|
-
const parsed = new URL(url);
|
|
15359
|
-
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
15360
|
-
return `${parsed.origin}${pathname}`;
|
|
15361
|
-
} catch {
|
|
15362
|
-
return url;
|
|
15363
|
-
}
|
|
15364
|
-
}
|
|
15365
|
-
function pruneCartAddedProducts(now = Date.now()) {
|
|
15366
|
-
for (const [key2, entry] of cartAddedProducts) {
|
|
15367
|
-
if (now - entry.ts > CART_ADDED_TTL_MS) {
|
|
15368
|
-
cartAddedProducts.delete(key2);
|
|
15369
|
-
}
|
|
15370
|
-
}
|
|
15371
|
-
}
|
|
15372
|
-
function cartOrigin(url) {
|
|
15373
|
-
if (!url) return null;
|
|
15374
|
-
try {
|
|
15375
|
-
return new URL(url).origin;
|
|
15376
|
-
} catch {
|
|
15377
|
-
return null;
|
|
15378
|
-
}
|
|
15379
|
-
}
|
|
15380
|
-
function recordProductAddedToCart(url, productName) {
|
|
15381
|
-
pruneCartAddedProducts();
|
|
15382
|
-
cartAddedProducts.set(normalizeCartProductKey(url), {
|
|
15383
|
-
title: productName || url,
|
|
15384
|
-
ts: Date.now()
|
|
15385
|
-
});
|
|
15386
|
-
}
|
|
15387
|
-
function isProductAlreadyInCart(url) {
|
|
15388
|
-
pruneCartAddedProducts();
|
|
15389
|
-
return cartAddedProducts.has(normalizeCartProductKey(url));
|
|
15390
|
-
}
|
|
15391
|
-
function getCartAddedSummary(url) {
|
|
15392
|
-
pruneCartAddedProducts();
|
|
15393
|
-
const origin = cartOrigin(url);
|
|
15394
|
-
const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
|
|
15395
|
-
if (!items) return "";
|
|
15396
|
-
const count = items.split("\n").length;
|
|
15397
|
-
return `
|
|
15398
|
-
Already in cart (${count} items):
|
|
15399
|
-
${items}`;
|
|
15400
|
-
}
|
|
15401
15524
|
function clearCartState() {
|
|
15402
|
-
|
|
15403
|
-
recentCartClicks.clear();
|
|
15525
|
+
clearCartClickState();
|
|
15404
15526
|
clickStreakUrl = null;
|
|
15405
15527
|
clickStreakCount = 0;
|
|
15406
15528
|
}
|
|
@@ -15452,7 +15574,7 @@ Go back and select a different product.`;
|
|
|
15452
15574
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15453
15575
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15454
15576
|
if (idxCartMatch) {
|
|
15455
|
-
recordCartClick(beforeUrl2
|
|
15577
|
+
recordCartClick(beforeUrl2);
|
|
15456
15578
|
}
|
|
15457
15579
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15458
15580
|
const afterUrl2 = wc.getURL();
|
|
@@ -15519,7 +15641,7 @@ Go back and select a different product.`;
|
|
|
15519
15641
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15520
15642
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15521
15643
|
if (shadowCartMatch) {
|
|
15522
|
-
recordCartClick(beforeUrl2
|
|
15644
|
+
recordCartClick(beforeUrl2);
|
|
15523
15645
|
}
|
|
15524
15646
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15525
15647
|
const afterUrl2 = wc.getURL();
|
|
@@ -15555,7 +15677,7 @@ Note: Page did not change after click.`;
|
|
|
15555
15677
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
15556
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).`;
|
|
15557
15679
|
}
|
|
15558
|
-
if (!cartMatch &&
|
|
15680
|
+
if (!cartMatch && hasRecentCartClick(beforeUrl)) {
|
|
15559
15681
|
const dialogActions = await getCartDialogActions(wc);
|
|
15560
15682
|
if (dialogActions) {
|
|
15561
15683
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
@@ -15575,7 +15697,7 @@ Click one of these dialog actions instead.`;
|
|
|
15575
15697
|
Go back and select a different product.`;
|
|
15576
15698
|
}
|
|
15577
15699
|
if (cartMatch) {
|
|
15578
|
-
recordCartClick(beforeUrl
|
|
15700
|
+
recordCartClick(beforeUrl);
|
|
15579
15701
|
}
|
|
15580
15702
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
15581
15703
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
@@ -19009,6 +19131,57 @@ function onAIStreamIdle(listener) {
|
|
|
19009
19131
|
idleListeners.add(listener);
|
|
19010
19132
|
return () => idleListeners.delete(listener);
|
|
19011
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
|
+
}
|
|
19012
19185
|
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
19013
19186
|
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
19014
19187
|
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
@@ -20364,6 +20537,14 @@ function asErrorTextResponse(message) {
|
|
|
20364
20537
|
function asNoActiveTabResponse() {
|
|
20365
20538
|
return asErrorTextResponse("No active tab");
|
|
20366
20539
|
}
|
|
20540
|
+
function getPremiumToolGateResponse(toolName) {
|
|
20541
|
+
try {
|
|
20542
|
+
assertToolUnlocked(toolName);
|
|
20543
|
+
return null;
|
|
20544
|
+
} catch (error) {
|
|
20545
|
+
return asTextResponse(getErrorMessage(error));
|
|
20546
|
+
}
|
|
20547
|
+
}
|
|
20367
20548
|
function asPromptResponse(text) {
|
|
20368
20549
|
return {
|
|
20369
20550
|
messages: [
|
|
@@ -20466,6 +20647,8 @@ async function getPostActionState(tabManager, name) {
|
|
|
20466
20647
|
return "";
|
|
20467
20648
|
}
|
|
20468
20649
|
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
20650
|
+
const premiumGate = getPremiumToolGateResponse(name);
|
|
20651
|
+
if (premiumGate) return premiumGate;
|
|
20469
20652
|
try {
|
|
20470
20653
|
const result = await runtime2.runControlledAction({
|
|
20471
20654
|
source: "mcp",
|
|
@@ -21775,6 +21958,8 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
21775
21958
|
description: "Capture a screenshot of the current page. Returns a base64-encoded PNG image."
|
|
21776
21959
|
},
|
|
21777
21960
|
async () => {
|
|
21961
|
+
const premiumGate = getPremiumToolGateResponse("screenshot");
|
|
21962
|
+
if (premiumGate) return premiumGate;
|
|
21778
21963
|
const tab = tabManager.getActiveTab();
|
|
21779
21964
|
if (!tab) return asNoActiveTabResponse();
|
|
21780
21965
|
try {
|
|
@@ -22797,6 +22982,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
22797
22982
|
}
|
|
22798
22983
|
},
|
|
22799
22984
|
async ({ goal, steps }) => {
|
|
22985
|
+
const premiumGate = getPremiumToolGateResponse("flow_start");
|
|
22986
|
+
if (premiumGate) return premiumGate;
|
|
22800
22987
|
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
22801
22988
|
const tab = tabManager.getActiveTab();
|
|
22802
22989
|
const flow = runtime2.startFlow(
|
|
@@ -22820,6 +23007,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22820
23007
|
}
|
|
22821
23008
|
},
|
|
22822
23009
|
async ({ detail }) => {
|
|
23010
|
+
const premiumGate = getPremiumToolGateResponse("flow_advance");
|
|
23011
|
+
if (premiumGate) return premiumGate;
|
|
22823
23012
|
const flow = runtime2.advanceFlow(detail);
|
|
22824
23013
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
22825
23014
|
const ctx = runtime2.getFlowContext();
|
|
@@ -22833,6 +23022,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22833
23022
|
description: "Check the current workflow progress."
|
|
22834
23023
|
},
|
|
22835
23024
|
async () => {
|
|
23025
|
+
const premiumGate = getPremiumToolGateResponse("flow_status");
|
|
23026
|
+
if (premiumGate) return premiumGate;
|
|
22836
23027
|
const flow = runtime2.getFlowState();
|
|
22837
23028
|
if (!flow) return asTextResponse("No active workflow.");
|
|
22838
23029
|
return asTextResponse(runtime2.getFlowContext());
|
|
@@ -22845,6 +23036,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22845
23036
|
description: "Clear the active workflow tracker."
|
|
22846
23037
|
},
|
|
22847
23038
|
async () => {
|
|
23039
|
+
const premiumGate = getPremiumToolGateResponse("flow_end");
|
|
23040
|
+
if (premiumGate) return premiumGate;
|
|
22848
23041
|
runtime2.clearFlow();
|
|
22849
23042
|
return asTextResponse("Workflow ended.");
|
|
22850
23043
|
}
|
|
@@ -23485,6 +23678,8 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
23485
23678
|
}
|
|
23486
23679
|
},
|
|
23487
23680
|
async ({ domain }) => {
|
|
23681
|
+
const premiumGate = getPremiumToolGateResponse("vault_status");
|
|
23682
|
+
if (premiumGate) return premiumGate;
|
|
23488
23683
|
let targetDomain = domain;
|
|
23489
23684
|
if (!targetDomain) {
|
|
23490
23685
|
const tab = tabManager.getActiveTab();
|
|
@@ -23551,6 +23746,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23551
23746
|
submit_after,
|
|
23552
23747
|
submit_index
|
|
23553
23748
|
}) => {
|
|
23749
|
+
const premiumGate = getPremiumToolGateResponse("vault_login");
|
|
23750
|
+
if (premiumGate) return premiumGate;
|
|
23554
23751
|
const tab = tabManager.getActiveTab();
|
|
23555
23752
|
if (!tab) return asNoActiveTabResponse();
|
|
23556
23753
|
const wc = tab.view.webContents;
|
|
@@ -23645,6 +23842,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23645
23842
|
}
|
|
23646
23843
|
},
|
|
23647
23844
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
23845
|
+
const premiumGate = getPremiumToolGateResponse("vault_totp");
|
|
23846
|
+
if (premiumGate) return premiumGate;
|
|
23648
23847
|
const tab = tabManager.getActiveTab();
|
|
23649
23848
|
if (!tab) return asNoActiveTabResponse();
|
|
23650
23849
|
const wc = tab.view.webContents;
|
|
@@ -23724,6 +23923,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23724
23923
|
})
|
|
23725
23924
|
},
|
|
23726
23925
|
async ({ domain }) => {
|
|
23926
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_list");
|
|
23927
|
+
if (premiumGate) return premiumGate;
|
|
23727
23928
|
const consent = await requestHumanVaultConsent({
|
|
23728
23929
|
action: "list",
|
|
23729
23930
|
domain: domain ?? "all"
|
|
@@ -23774,6 +23975,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23774
23975
|
})
|
|
23775
23976
|
},
|
|
23776
23977
|
async ({ entry_id, username_index, password_index, submit_after, submit_index }) => {
|
|
23978
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_fill");
|
|
23979
|
+
if (premiumGate) return premiumGate;
|
|
23777
23980
|
const tab = tabManager.getActiveTab();
|
|
23778
23981
|
if (!tab) return asNoActiveTabResponse();
|
|
23779
23982
|
let hostname;
|
|
@@ -23866,6 +24069,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23866
24069
|
})
|
|
23867
24070
|
},
|
|
23868
24071
|
async ({ entry_id }) => {
|
|
24072
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_remove");
|
|
24073
|
+
if (premiumGate) return premiumGate;
|
|
23869
24074
|
const entry = getEntry(entry_id);
|
|
23870
24075
|
if (!entry) {
|
|
23871
24076
|
return asErrorTextResponse(`No entry found with ID ${entry_id}.`);
|
|
@@ -23890,6 +24095,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23890
24095
|
inputSchema: zod.z.object({})
|
|
23891
24096
|
},
|
|
23892
24097
|
async () => {
|
|
24098
|
+
const premiumGate = getPremiumToolGateResponse("metrics");
|
|
24099
|
+
if (premiumGate) return premiumGate;
|
|
23893
24100
|
const m = runtime2.getMetrics();
|
|
23894
24101
|
const lines = [
|
|
23895
24102
|
`Session Metrics:`,
|
|
@@ -24243,7 +24450,7 @@ function assertNumber(value, name) {
|
|
|
24243
24450
|
}
|
|
24244
24451
|
}
|
|
24245
24452
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
24246
|
-
function isValidEmail(value) {
|
|
24453
|
+
function isValidEmail$1(value) {
|
|
24247
24454
|
return EMAIL_RE.test(value.trim());
|
|
24248
24455
|
}
|
|
24249
24456
|
function getActiveTabInfo(tabManager) {
|
|
@@ -24255,6 +24462,8 @@ function getActiveTabInfo(tabManager) {
|
|
|
24255
24462
|
}
|
|
24256
24463
|
const logger$9 = createLogger("Scheduler");
|
|
24257
24464
|
let jobs = [];
|
|
24465
|
+
let pollInterval = null;
|
|
24466
|
+
let alignStartTimeout = null;
|
|
24258
24467
|
let removeIdleListener = null;
|
|
24259
24468
|
let broadcastFn = null;
|
|
24260
24469
|
function getScheduledKitIds() {
|
|
@@ -24343,10 +24552,12 @@ function computeNextRun(schedule, from = /* @__PURE__ */ new Date()) {
|
|
|
24343
24552
|
const next = new Date(from);
|
|
24344
24553
|
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
24345
24554
|
const daysUntil = (schedule.dayOfWeek - next.getDay() + 7) % 7;
|
|
24346
|
-
if (daysUntil === 0
|
|
24347
|
-
|
|
24555
|
+
if (daysUntil === 0) {
|
|
24556
|
+
if (next <= from) {
|
|
24557
|
+
next.setDate(next.getDate() + 7);
|
|
24558
|
+
}
|
|
24348
24559
|
} else {
|
|
24349
|
-
next.setDate(next.getDate() +
|
|
24560
|
+
next.setDate(next.getDate() + daysUntil);
|
|
24350
24561
|
}
|
|
24351
24562
|
return next;
|
|
24352
24563
|
}
|
|
@@ -24473,18 +24684,19 @@ function tick(windowState, runtime2) {
|
|
|
24473
24684
|
fireNext();
|
|
24474
24685
|
}
|
|
24475
24686
|
function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
24687
|
+
stopScheduler();
|
|
24476
24688
|
broadcastFn = sendToAll;
|
|
24477
24689
|
loadJobs();
|
|
24478
24690
|
if (normalizeJobs()) {
|
|
24479
24691
|
saveJobs();
|
|
24480
24692
|
}
|
|
24481
|
-
removeIdleListener?.();
|
|
24482
24693
|
removeIdleListener = onAIStreamIdle(() => tick(windowState, runtime2));
|
|
24483
24694
|
const now = /* @__PURE__ */ new Date();
|
|
24484
24695
|
const msToNextMinute = (60 - now.getSeconds()) * 1e3 - now.getMilliseconds();
|
|
24485
|
-
setTimeout(() => {
|
|
24696
|
+
alignStartTimeout = setTimeout(() => {
|
|
24697
|
+
alignStartTimeout = null;
|
|
24486
24698
|
tick(windowState, runtime2);
|
|
24487
|
-
setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24699
|
+
pollInterval = setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24488
24700
|
}, msToNextMinute);
|
|
24489
24701
|
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
|
|
24490
24702
|
assertTrustedIpcSender(event);
|
|
@@ -24546,6 +24758,20 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24546
24758
|
return true;
|
|
24547
24759
|
});
|
|
24548
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
|
+
}
|
|
24549
24775
|
const SAVE_DEBOUNCE_MS = 250;
|
|
24550
24776
|
const PROFILE_FIELDS = [
|
|
24551
24777
|
"label",
|
|
@@ -24943,6 +25169,7 @@ function registerAutofillHandlers(windowState) {
|
|
|
24943
25169
|
}
|
|
24944
25170
|
function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
24945
25171
|
const pageEventBuckets = /* @__PURE__ */ new Map();
|
|
25172
|
+
const isActiveWebContents = (webContentsId) => windowState.tabManager.getActiveTab()?.view.webContents.id === webContentsId;
|
|
24946
25173
|
const allowPageEvent = (webContentsId) => {
|
|
24947
25174
|
const now = Date.now();
|
|
24948
25175
|
const bucket = pageEventBuckets.get(webContentsId);
|
|
@@ -24979,14 +25206,20 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
24979
25206
|
if (!wc || wc.isDestroyed()) return;
|
|
24980
25207
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24981
25208
|
if (!allowPageEvent(wc.id)) return;
|
|
24982
|
-
|
|
25209
|
+
invalidateExtractionCache(wc);
|
|
25210
|
+
notePageMutationActivity(wc, sendToRendererViews, {
|
|
25211
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25212
|
+
});
|
|
24983
25213
|
});
|
|
24984
25214
|
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
24985
25215
|
const wc = event.sender;
|
|
24986
25216
|
if (!wc || wc.isDestroyed()) return;
|
|
24987
25217
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24988
25218
|
if (!allowPageEvent(wc.id)) return;
|
|
24989
|
-
|
|
25219
|
+
invalidateExtractionCache(wc);
|
|
25220
|
+
schedulePageSnapshotCapture(wc, sendToRendererViews, 0, {
|
|
25221
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25222
|
+
});
|
|
24990
25223
|
});
|
|
24991
25224
|
}
|
|
24992
25225
|
function renderReportAsMarkdown(report, traces) {
|
|
@@ -25255,9 +25488,40 @@ RULES:
|
|
|
25255
25488
|
}
|
|
25256
25489
|
const logger$7 = createLogger("ResearchOrchestrator");
|
|
25257
25490
|
const MAX_THREADS = 5;
|
|
25491
|
+
const MAX_TRACE_ARGS_CHARS = 1200;
|
|
25492
|
+
const MAX_TRACE_RESULT_CHARS = 2e3;
|
|
25258
25493
|
function clone$1(value) {
|
|
25259
25494
|
return structuredClone(value);
|
|
25260
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
|
+
}
|
|
25261
25525
|
function normalizeSourceDomain(value) {
|
|
25262
25526
|
const trimmed = value.trim().toLowerCase();
|
|
25263
25527
|
if (!trimmed) return "";
|
|
@@ -25666,13 +25930,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25666
25930
|
title: String(args.url || "excluded source"),
|
|
25667
25931
|
reason: msg
|
|
25668
25932
|
});
|
|
25669
|
-
trace.toolCalls.push(
|
|
25670
|
-
tool: name,
|
|
25671
|
-
args,
|
|
25672
|
-
result: msg,
|
|
25673
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25674
|
-
durationMs: 0
|
|
25675
|
-
});
|
|
25933
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25676
25934
|
return msg;
|
|
25677
25935
|
}
|
|
25678
25936
|
}
|
|
@@ -25680,25 +25938,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25680
25938
|
sourcesConsumed++;
|
|
25681
25939
|
if (sourcesConsumed > thread.sourceBudget) {
|
|
25682
25940
|
const msg = `Source budget (${thread.sourceBudget}) exceeded. Summarize findings and stop.`;
|
|
25683
|
-
trace.toolCalls.push(
|
|
25684
|
-
tool: name,
|
|
25685
|
-
args,
|
|
25686
|
-
result: msg,
|
|
25687
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25688
|
-
durationMs: 0
|
|
25689
|
-
});
|
|
25941
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25690
25942
|
return msg;
|
|
25691
25943
|
}
|
|
25692
25944
|
}
|
|
25693
25945
|
try {
|
|
25694
25946
|
const output = await executeAction(name, args, actionCtx);
|
|
25695
|
-
trace.toolCalls.push(
|
|
25696
|
-
tool: name,
|
|
25697
|
-
args,
|
|
25698
|
-
result: output,
|
|
25699
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25700
|
-
durationMs: Date.now() - t0
|
|
25701
|
-
});
|
|
25947
|
+
trace.toolCalls.push(createTraceToolCall(name, args, output, t0));
|
|
25702
25948
|
return output;
|
|
25703
25949
|
} catch (err) {
|
|
25704
25950
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -25713,13 +25959,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25713
25959
|
message: msg,
|
|
25714
25960
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25715
25961
|
});
|
|
25716
|
-
trace.toolCalls.push({
|
|
25717
|
-
tool: name,
|
|
25718
|
-
args,
|
|
25719
|
-
result: `Error: ${msg}`,
|
|
25720
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25721
|
-
durationMs: Date.now() - t0
|
|
25722
|
-
});
|
|
25962
|
+
trace.toolCalls.push(createTraceToolCall(name, args, `Error: ${msg}`, t0));
|
|
25723
25963
|
return `Error: ${msg}`;
|
|
25724
25964
|
}
|
|
25725
25965
|
},
|
|
@@ -25939,15 +26179,20 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25939
26179
|
this.emit();
|
|
25940
26180
|
}
|
|
25941
26181
|
}
|
|
26182
|
+
function assertVaultUnlocked() {
|
|
26183
|
+
assertFeatureUnlocked("vault", "Agent Credential Vault");
|
|
26184
|
+
}
|
|
25942
26185
|
function registerVaultHandlers() {
|
|
25943
26186
|
electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
|
|
25944
26187
|
assertTrustedIpcSender(event);
|
|
26188
|
+
assertVaultUnlocked();
|
|
25945
26189
|
return listEntries$1();
|
|
25946
26190
|
});
|
|
25947
26191
|
electron.ipcMain.handle(
|
|
25948
26192
|
Channels.VAULT_ADD,
|
|
25949
26193
|
(event, entry) => {
|
|
25950
26194
|
assertTrustedIpcSender(event);
|
|
26195
|
+
assertVaultUnlocked();
|
|
25951
26196
|
if (!entry || typeof entry !== "object") {
|
|
25952
26197
|
throw new Error("Invalid vault entry");
|
|
25953
26198
|
}
|
|
@@ -25974,6 +26219,7 @@ function registerVaultHandlers() {
|
|
|
25974
26219
|
Channels.VAULT_UPDATE,
|
|
25975
26220
|
(event, id, updates) => {
|
|
25976
26221
|
assertTrustedIpcSender(event);
|
|
26222
|
+
assertVaultUnlocked();
|
|
25977
26223
|
assertString(id, "id");
|
|
25978
26224
|
if (!updates || typeof updates !== "object") {
|
|
25979
26225
|
throw new Error("Invalid updates");
|
|
@@ -25983,12 +26229,14 @@ function registerVaultHandlers() {
|
|
|
25983
26229
|
);
|
|
25984
26230
|
electron.ipcMain.handle(Channels.VAULT_REMOVE, (event, id) => {
|
|
25985
26231
|
assertTrustedIpcSender(event);
|
|
26232
|
+
assertVaultUnlocked();
|
|
25986
26233
|
assertString(id, "id");
|
|
25987
26234
|
trackVaultAction("credential_removed");
|
|
25988
26235
|
return removeEntry$1(id);
|
|
25989
26236
|
});
|
|
25990
26237
|
electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (event, limit) => {
|
|
25991
26238
|
assertTrustedIpcSender(event);
|
|
26239
|
+
assertVaultUnlocked();
|
|
25992
26240
|
return readAuditLog$1(limit);
|
|
25993
26241
|
});
|
|
25994
26242
|
}
|
|
@@ -26006,14 +26254,19 @@ function normalizeTags(value) {
|
|
|
26006
26254
|
const tags = value.filter((tag) => typeof tag === "string").map((tag) => tag.trim()).filter(Boolean);
|
|
26007
26255
|
return tags.length > 0 ? tags : void 0;
|
|
26008
26256
|
}
|
|
26257
|
+
function assertHumanVaultUnlocked() {
|
|
26258
|
+
assertFeatureUnlocked("human_vault", "Passwords");
|
|
26259
|
+
}
|
|
26009
26260
|
function registerHumanVaultHandlers() {
|
|
26010
26261
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (event, domain) => {
|
|
26011
26262
|
assertTrustedIpcSender(event);
|
|
26263
|
+
assertHumanVaultUnlocked();
|
|
26012
26264
|
if (domain !== void 0) assertString(domain, "domain");
|
|
26013
26265
|
return domain ? findForDomain(domain) : listEntries();
|
|
26014
26266
|
});
|
|
26015
26267
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (event, id) => {
|
|
26016
26268
|
assertTrustedIpcSender(event);
|
|
26269
|
+
assertHumanVaultUnlocked();
|
|
26017
26270
|
assertString(id, "id");
|
|
26018
26271
|
return getEntrySafe(id);
|
|
26019
26272
|
});
|
|
@@ -26021,6 +26274,7 @@ function registerHumanVaultHandlers() {
|
|
|
26021
26274
|
Channels.HUMAN_VAULT_SAVE,
|
|
26022
26275
|
(event, input) => {
|
|
26023
26276
|
assertTrustedIpcSender(event);
|
|
26277
|
+
assertHumanVaultUnlocked();
|
|
26024
26278
|
if (!input || typeof input !== "object") {
|
|
26025
26279
|
throw new Error("Invalid credential entry");
|
|
26026
26280
|
}
|
|
@@ -26052,6 +26306,7 @@ function registerHumanVaultHandlers() {
|
|
|
26052
26306
|
Channels.HUMAN_VAULT_UPDATE,
|
|
26053
26307
|
(event, id, updates) => {
|
|
26054
26308
|
assertTrustedIpcSender(event);
|
|
26309
|
+
assertHumanVaultUnlocked();
|
|
26055
26310
|
assertString(id, "id");
|
|
26056
26311
|
if (!updates || typeof updates !== "object") {
|
|
26057
26312
|
throw new Error("Invalid updates");
|
|
@@ -26095,11 +26350,13 @@ function registerHumanVaultHandlers() {
|
|
|
26095
26350
|
);
|
|
26096
26351
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (event, id) => {
|
|
26097
26352
|
assertTrustedIpcSender(event);
|
|
26353
|
+
assertHumanVaultUnlocked();
|
|
26098
26354
|
assertString(id, "id");
|
|
26099
26355
|
return removeEntry(id);
|
|
26100
26356
|
});
|
|
26101
26357
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (event, limit) => {
|
|
26102
26358
|
assertTrustedIpcSender(event);
|
|
26359
|
+
assertHumanVaultUnlocked();
|
|
26103
26360
|
return readAuditLog(limit);
|
|
26104
26361
|
});
|
|
26105
26362
|
}
|
|
@@ -27235,6 +27492,10 @@ function registerHistoryHandlers() {
|
|
|
27235
27492
|
assertTrustedIpcSender(event);
|
|
27236
27493
|
return getState$1();
|
|
27237
27494
|
});
|
|
27495
|
+
electron.ipcMain.handle(Channels.HISTORY_LIST, (event, offset, limit) => {
|
|
27496
|
+
assertTrustedIpcSender(event);
|
|
27497
|
+
return listEntries$2(offset, limit);
|
|
27498
|
+
});
|
|
27238
27499
|
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (event, query) => {
|
|
27239
27500
|
assertTrustedIpcSender(event);
|
|
27240
27501
|
return search(query);
|
|
@@ -27389,7 +27650,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27389
27650
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (event, email) => {
|
|
27390
27651
|
assertTrustedIpcSender(event);
|
|
27391
27652
|
assertString(email, "email");
|
|
27392
|
-
if (!isValidEmail(email)) {
|
|
27653
|
+
if (!isValidEmail$1(email)) {
|
|
27393
27654
|
return errorResult("Invalid email format");
|
|
27394
27655
|
}
|
|
27395
27656
|
trackPremiumFunnel("activation_attempted");
|
|
@@ -27416,7 +27677,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27416
27677
|
assertString(email, "email");
|
|
27417
27678
|
assertString(code, "code");
|
|
27418
27679
|
assertString(challengeToken, "challengeToken");
|
|
27419
|
-
if (!isValidEmail(email)) {
|
|
27680
|
+
if (!isValidEmail$1(email)) {
|
|
27420
27681
|
return errorResult("Invalid email format", {
|
|
27421
27682
|
state: getPremiumState()
|
|
27422
27683
|
});
|
|
@@ -27620,6 +27881,47 @@ function registerCodexHandlers() {
|
|
|
27620
27881
|
return { ok: true };
|
|
27621
27882
|
});
|
|
27622
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
|
+
}
|
|
27623
27925
|
const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
|
|
27624
27926
|
const ALLOWED_PERMISSION_TYPES = /* @__PURE__ */ new Set([
|
|
27625
27927
|
"clipboard-read",
|
|
@@ -28133,7 +28435,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28133
28435
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
28134
28436
|
tabManager,
|
|
28135
28437
|
runtime2,
|
|
28136
|
-
history,
|
|
28438
|
+
compactProviderHistory(history),
|
|
28137
28439
|
researchOrchestrator
|
|
28138
28440
|
);
|
|
28139
28441
|
} catch (err) {
|
|
@@ -28285,6 +28587,12 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28285
28587
|
requireTrusted(event);
|
|
28286
28588
|
return regenerateMcpAuthToken();
|
|
28287
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
|
+
});
|
|
28288
28596
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
|
|
28289
28597
|
requireTrusted(event);
|
|
28290
28598
|
assertString(key2, "key");
|
|
@@ -29926,7 +30234,7 @@ async function bootstrap() {
|
|
|
29926
30234
|
);
|
|
29927
30235
|
}
|
|
29928
30236
|
};
|
|
29929
|
-
const windowState = createMainWindow((tabs, activeId) => {
|
|
30237
|
+
const windowState = createMainWindow((tabs, activeId, meta) => {
|
|
29930
30238
|
windowState.chromeView.webContents.send(
|
|
29931
30239
|
Channels.TAB_STATE_UPDATE,
|
|
29932
30240
|
tabs,
|
|
@@ -29934,7 +30242,9 @@ async function bootstrap() {
|
|
|
29934
30242
|
);
|
|
29935
30243
|
void syncActiveHighlightCount(windowState);
|
|
29936
30244
|
layoutViews(windowState);
|
|
29937
|
-
|
|
30245
|
+
if (meta.persistSession) {
|
|
30246
|
+
runtime?.onTabStateChanged();
|
|
30247
|
+
}
|
|
29938
30248
|
});
|
|
29939
30249
|
let didRevealMainWindow = false;
|
|
29940
30250
|
const revealMainWindow = () => {
|