@quanta-intellect/vessel-browser 0.1.104 → 0.1.115
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 +1535 -1083
- package/out/preload/content-script.js +20 -3
- package/out/preload/index.js +20 -0
- package/out/renderer/assets/{index-BF_JrL2V.css → index-CFbT1_av.css} +29 -2
- package/out/renderer/assets/{index-D3ABnKy4.js → index-CWiMuKTX.js} +907 -598
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -81,7 +81,7 @@ const defaults = {
|
|
|
81
81
|
const SAVE_DEBOUNCE_MS$6 = 150;
|
|
82
82
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
83
83
|
const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
|
|
84
|
-
const logger$
|
|
84
|
+
const logger$r = createLogger("Settings");
|
|
85
85
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
86
86
|
let settings = null;
|
|
87
87
|
let settingsIssues = [];
|
|
@@ -289,7 +289,7 @@ function persistNow() {
|
|
|
289
289
|
JSON.stringify(buildPersistedSettings(settings), null, 2),
|
|
290
290
|
{ encoding: "utf-8", mode: 384 }
|
|
291
291
|
)
|
|
292
|
-
).then(() => fs.promises.chmod(getSettingsPath(), 384).catch(() => void 0)).catch((err) => logger$
|
|
292
|
+
).then(() => fs.promises.chmod(getSettingsPath(), 384).catch(() => void 0)).catch((err) => logger$r.error("Failed to save settings:", err));
|
|
293
293
|
}
|
|
294
294
|
function saveSettings() {
|
|
295
295
|
saveDirty = true;
|
|
@@ -420,7 +420,7 @@ function loadTrustedAppURL(wc, url) {
|
|
|
420
420
|
}
|
|
421
421
|
const MAX_CUSTOM_HISTORY = 50;
|
|
422
422
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
423
|
-
const logger$
|
|
423
|
+
const logger$q = createLogger("Tab");
|
|
424
424
|
const sessionCertExceptions = /* @__PURE__ */ new WeakMap();
|
|
425
425
|
const sessionsWithVerifyProc = /* @__PURE__ */ new WeakSet();
|
|
426
426
|
const CERT_VERIFY_TRUST = 0;
|
|
@@ -486,7 +486,7 @@ class Tab {
|
|
|
486
486
|
guardedLoadURL(url, options) {
|
|
487
487
|
const blockReason = this.getNavigationBlockReason(url);
|
|
488
488
|
if (blockReason) {
|
|
489
|
-
logger$
|
|
489
|
+
logger$q.warn(blockReason);
|
|
490
490
|
return blockReason;
|
|
491
491
|
}
|
|
492
492
|
void this.view.webContents.loadURL(url, options);
|
|
@@ -570,7 +570,7 @@ class Tab {
|
|
|
570
570
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
571
571
|
const error = this.getNavigationBlockReason(url);
|
|
572
572
|
if (error) {
|
|
573
|
-
logger$
|
|
573
|
+
logger$q.warn(error);
|
|
574
574
|
return { action: "deny" };
|
|
575
575
|
}
|
|
576
576
|
this.onOpenUrl?.({
|
|
@@ -584,7 +584,7 @@ class Tab {
|
|
|
584
584
|
const error = this.getNavigationBlockReason(url);
|
|
585
585
|
if (!error) return;
|
|
586
586
|
event.preventDefault();
|
|
587
|
-
logger$
|
|
587
|
+
logger$q.warn(`${context}: ${error}`);
|
|
588
588
|
};
|
|
589
589
|
wc.on("will-navigate", (event, url) => {
|
|
590
590
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
@@ -668,7 +668,7 @@ class Tab {
|
|
|
668
668
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
669
669
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
670
670
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
671
|
-
`).catch((err) => logger$
|
|
671
|
+
`).catch((err) => logger$q.warn("Failed to inject scrollbar CSS:", err));
|
|
672
672
|
});
|
|
673
673
|
wc.on("page-favicon-updated", (_, favicons) => {
|
|
674
674
|
this._state.favicon = favicons[0] || "";
|
|
@@ -704,7 +704,7 @@ class Tab {
|
|
|
704
704
|
).then((highlightedText) => {
|
|
705
705
|
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
706
706
|
}).catch((err) => {
|
|
707
|
-
logger$
|
|
707
|
+
logger$q.warn("Failed to inspect highlighted text for context menu:", err);
|
|
708
708
|
this.buildContextMenu(wc, params, "");
|
|
709
709
|
});
|
|
710
710
|
});
|
|
@@ -905,7 +905,7 @@ class Tab {
|
|
|
905
905
|
"document.documentElement.outerHTML"
|
|
906
906
|
);
|
|
907
907
|
} catch (err) {
|
|
908
|
-
logger$
|
|
908
|
+
logger$q.warn("Failed to retrieve page source:", err);
|
|
909
909
|
return;
|
|
910
910
|
}
|
|
911
911
|
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1033,7 +1033,7 @@ class Tab {
|
|
|
1033
1033
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
1034
1034
|
}
|
|
1035
1035
|
})()
|
|
1036
|
-
`).catch((err) => logger$
|
|
1036
|
+
`).catch((err) => logger$q.warn("Failed to inject highlight listener:", err));
|
|
1037
1037
|
} else {
|
|
1038
1038
|
void wc.executeJavaScript(`
|
|
1039
1039
|
(function() {
|
|
@@ -1044,7 +1044,7 @@ class Tab {
|
|
|
1044
1044
|
delete window.__vesselHighlightHandler;
|
|
1045
1045
|
}
|
|
1046
1046
|
})()
|
|
1047
|
-
`).catch((err) => logger$
|
|
1047
|
+
`).catch((err) => logger$q.warn("Failed to remove highlight listener:", err));
|
|
1048
1048
|
}
|
|
1049
1049
|
}
|
|
1050
1050
|
get webContentsId() {
|
|
@@ -1081,7 +1081,7 @@ const SEARCH_ENGINE_PRESETS = {
|
|
|
1081
1081
|
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
1082
1082
|
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
1083
1083
|
};
|
|
1084
|
-
const logger$
|
|
1084
|
+
const logger$p = createLogger("JsonPersistence");
|
|
1085
1085
|
function canUseSafeStorage() {
|
|
1086
1086
|
try {
|
|
1087
1087
|
return electron.safeStorage.isEncryptionAvailable();
|
|
@@ -1146,7 +1146,7 @@ function createDebouncedJsonPersistence({
|
|
|
1146
1146
|
data,
|
|
1147
1147
|
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
1148
1148
|
)
|
|
1149
|
-
).then(() => fs.promises.chmod(filePath2, 384).catch(() => void 0)).catch((err) => logger$
|
|
1149
|
+
).then(() => fs.promises.chmod(filePath2, 384).catch(() => void 0)).catch((err) => logger$p.error(`Failed to save ${logLabel}:`, err));
|
|
1150
1150
|
};
|
|
1151
1151
|
const schedule = () => {
|
|
1152
1152
|
saveDirty2 = true;
|
|
@@ -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 () => {
|
|
@@ -2827,7 +2838,7 @@ function destroySession(tabId) {
|
|
|
2827
2838
|
sessions.delete(tabId);
|
|
2828
2839
|
}
|
|
2829
2840
|
}
|
|
2830
|
-
const logger$
|
|
2841
|
+
const logger$o = createLogger("TabManager");
|
|
2831
2842
|
function sanitizePdfFilename(title) {
|
|
2832
2843
|
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2833
2844
|
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
@@ -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) {
|
|
@@ -3226,7 +3238,7 @@ class TabManager {
|
|
|
3226
3238
|
}));
|
|
3227
3239
|
if (entries.length > 0) {
|
|
3228
3240
|
void highlightBatchOnPage(wc, entries).catch(
|
|
3229
|
-
(err) => logger$
|
|
3241
|
+
(err) => logger$o.warn("Failed to batch highlight:", err)
|
|
3230
3242
|
);
|
|
3231
3243
|
}
|
|
3232
3244
|
}
|
|
@@ -3248,12 +3260,12 @@ class TabManager {
|
|
|
3248
3260
|
const result = await captureSelectionHighlight(wc);
|
|
3249
3261
|
if (result.success && result.text) {
|
|
3250
3262
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
3251
|
-
(err) => logger$
|
|
3263
|
+
(err) => logger$o.warn("Failed to capture highlight:", err)
|
|
3252
3264
|
);
|
|
3253
3265
|
}
|
|
3254
3266
|
this.highlightCaptureCallback?.(result);
|
|
3255
3267
|
} catch (err) {
|
|
3256
|
-
logger$
|
|
3268
|
+
logger$o.warn("Failed to capture highlight from page:", err);
|
|
3257
3269
|
this.highlightCaptureCallback?.({
|
|
3258
3270
|
success: false,
|
|
3259
3271
|
message: "Could not capture selection"
|
|
@@ -3278,7 +3290,7 @@ class TabManager {
|
|
|
3278
3290
|
void this.removeHighlightMarksForText(wc, text);
|
|
3279
3291
|
}
|
|
3280
3292
|
} catch (err) {
|
|
3281
|
-
logger$
|
|
3293
|
+
logger$o.warn("Failed to remove highlight from matching tab:", err);
|
|
3282
3294
|
}
|
|
3283
3295
|
}
|
|
3284
3296
|
this.highlightCaptureCallback?.({
|
|
@@ -3309,12 +3321,12 @@ class TabManager {
|
|
|
3309
3321
|
void 0,
|
|
3310
3322
|
color
|
|
3311
3323
|
).catch(
|
|
3312
|
-
(err) => logger$
|
|
3324
|
+
(err) => logger$o.warn("Failed to update highlight color:", err)
|
|
3313
3325
|
);
|
|
3314
3326
|
});
|
|
3315
3327
|
}
|
|
3316
3328
|
} catch (err) {
|
|
3317
|
-
logger$
|
|
3329
|
+
logger$o.warn("Failed to iterate highlights for color change:", err);
|
|
3318
3330
|
}
|
|
3319
3331
|
}
|
|
3320
3332
|
this.highlightCaptureCallback?.({
|
|
@@ -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() {
|
|
@@ -3355,12 +3381,15 @@ class TabManager {
|
|
|
3355
3381
|
});
|
|
3356
3382
|
})()`
|
|
3357
3383
|
).catch(
|
|
3358
|
-
(err) => logger$
|
|
3384
|
+
(err) => logger$o.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",
|
|
@@ -3582,6 +3614,10 @@ const Channels = {
|
|
|
3582
3614
|
CODEX_CANCEL_AUTH: "codex:cancel-auth",
|
|
3583
3615
|
CODEX_AUTH_STATUS: "codex:auth-status",
|
|
3584
3616
|
CODEX_DISCONNECT: "codex:disconnect",
|
|
3617
|
+
// OpenRouter OAuth
|
|
3618
|
+
OPENROUTER_START_AUTH: "openrouter:start-auth",
|
|
3619
|
+
OPENROUTER_CANCEL_AUTH: "openrouter:cancel-auth",
|
|
3620
|
+
OPENROUTER_AUTH_STATUS: "openrouter:auth-status",
|
|
3585
3621
|
// Updates
|
|
3586
3622
|
UPDATES_CHECK: "updates:check",
|
|
3587
3623
|
UPDATES_OPEN_DOWNLOAD: "updates:open-download",
|
|
@@ -3596,7 +3632,7 @@ const MAX_DIFF_BLOCKS = 500;
|
|
|
3596
3632
|
function normalizeText$2(value) {
|
|
3597
3633
|
return value.replace(/\s+/g, " ").trim();
|
|
3598
3634
|
}
|
|
3599
|
-
function truncateText(value, max = 180) {
|
|
3635
|
+
function truncateText$1(value, max = 180) {
|
|
3600
3636
|
const normalized = normalizeText$2(value);
|
|
3601
3637
|
if (normalized.length <= max) return normalized;
|
|
3602
3638
|
return `${normalized.slice(0, max - 3)}...`;
|
|
@@ -3770,10 +3806,10 @@ function diffSnapshots(oldSnap, currentContent, currentTitle, currentHeadings) {
|
|
|
3770
3806
|
addedBlocks.length,
|
|
3771
3807
|
removedBlocks.length
|
|
3772
3808
|
),
|
|
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))
|
|
3809
|
+
before: changedPairs[0] ? truncateText$1(changedPairs[0].before) : removedBlocks[0] ? truncateText$1(removedBlocks[0]) : void 0,
|
|
3810
|
+
after: changedPairs[0] ? truncateText$1(changedPairs[0].after) : addedBlocks[0] ? truncateText$1(addedBlocks[0]) : void 0,
|
|
3811
|
+
addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item)),
|
|
3812
|
+
removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item))
|
|
3777
3813
|
});
|
|
3778
3814
|
}
|
|
3779
3815
|
}
|
|
@@ -4546,7 +4582,7 @@ function errorResult(error, value) {
|
|
|
4546
4582
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4547
4583
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
4548
4584
|
}
|
|
4549
|
-
const logger$
|
|
4585
|
+
const logger$n = createLogger("Premium");
|
|
4550
4586
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
4551
4587
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
4552
4588
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4591,6 +4627,15 @@ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4591
4627
|
"research_approve_objectives",
|
|
4592
4628
|
"research_export_report"
|
|
4593
4629
|
]);
|
|
4630
|
+
const PREMIUM_FEATURES = /* @__PURE__ */ new Set([
|
|
4631
|
+
"obsidian",
|
|
4632
|
+
"devtools",
|
|
4633
|
+
"unlimited_iterations",
|
|
4634
|
+
"vault",
|
|
4635
|
+
"human_vault",
|
|
4636
|
+
"automation_kits",
|
|
4637
|
+
"research"
|
|
4638
|
+
]);
|
|
4594
4639
|
function isPremium() {
|
|
4595
4640
|
const { premium } = loadSettings();
|
|
4596
4641
|
if (premium.status !== "active" && premium.status !== "trialing") {
|
|
@@ -4629,6 +4674,22 @@ function resetPremium() {
|
|
|
4629
4674
|
function isToolGated(toolName) {
|
|
4630
4675
|
return PREMIUM_TOOLS.has(toolName) && !isPremium();
|
|
4631
4676
|
}
|
|
4677
|
+
function isFeatureGated(featureName) {
|
|
4678
|
+
return PREMIUM_FEATURES.has(featureName) && !isPremium();
|
|
4679
|
+
}
|
|
4680
|
+
function getPremiumToolGateMessage(toolName) {
|
|
4681
|
+
return `This tool (${toolName}) requires Vessel Premium. Upgrade at Settings > Premium to unlock screenshot, session management, workflow tracking, and more.`;
|
|
4682
|
+
}
|
|
4683
|
+
function assertToolUnlocked(toolName) {
|
|
4684
|
+
if (isToolGated(toolName)) {
|
|
4685
|
+
throw new Error(getPremiumToolGateMessage(toolName));
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
function assertFeatureUnlocked(featureName, featureLabel = featureName) {
|
|
4689
|
+
if (isFeatureGated(featureName)) {
|
|
4690
|
+
throw new Error(`${featureLabel} requires Vessel Premium.`);
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4632
4693
|
async function getCheckoutUrl(email) {
|
|
4633
4694
|
try {
|
|
4634
4695
|
const params = new URLSearchParams();
|
|
@@ -4688,7 +4749,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4688
4749
|
});
|
|
4689
4750
|
if (!res.ok) {
|
|
4690
4751
|
const detail = await readApiErrorDetail(res);
|
|
4691
|
-
logger$
|
|
4752
|
+
logger$n.warn(
|
|
4692
4753
|
"Verification API returned a non-OK status:",
|
|
4693
4754
|
res.status,
|
|
4694
4755
|
detail
|
|
@@ -4707,7 +4768,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4707
4768
|
setSetting("premium", updated);
|
|
4708
4769
|
return updated;
|
|
4709
4770
|
} catch (err) {
|
|
4710
|
-
logger$
|
|
4771
|
+
logger$n.warn("Verification failed:", err);
|
|
4711
4772
|
return current;
|
|
4712
4773
|
}
|
|
4713
4774
|
}
|
|
@@ -5319,7 +5380,18 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
5319
5380
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5320
5381
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5321
5382
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5322
|
-
const logger$
|
|
5383
|
+
const logger$m = createLogger("Extractor");
|
|
5384
|
+
const EXTRACTION_CACHE_TTL_MS = 1500;
|
|
5385
|
+
const MAX_EXTRACTION_CACHE_ENTRIES = 50;
|
|
5386
|
+
const extractionCache = /* @__PURE__ */ new Map();
|
|
5387
|
+
function invalidateExtractionCache(webContents) {
|
|
5388
|
+
const prefix = `${webContents.id}:`;
|
|
5389
|
+
for (const key2 of extractionCache.keys()) {
|
|
5390
|
+
if (key2.startsWith(prefix)) {
|
|
5391
|
+
extractionCache.delete(key2);
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5323
5395
|
const EMPTY_PAGE_CONTENT = {
|
|
5324
5396
|
title: "",
|
|
5325
5397
|
content: "",
|
|
@@ -6075,9 +6147,9 @@ async function executeScript(webContents, script, options = {}) {
|
|
|
6075
6147
|
const message = err instanceof Error ? err.message : String(err);
|
|
6076
6148
|
const detail = `Failed to execute page script${label} on ${url}: ${message}`;
|
|
6077
6149
|
if (options.warnOnFailure) {
|
|
6078
|
-
logger$
|
|
6150
|
+
logger$m.warn(detail);
|
|
6079
6151
|
} else {
|
|
6080
|
-
logger$
|
|
6152
|
+
logger$m.debug(detail);
|
|
6081
6153
|
}
|
|
6082
6154
|
return null;
|
|
6083
6155
|
} finally {
|
|
@@ -6186,7 +6258,7 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
6186
6258
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
6187
6259
|
}
|
|
6188
6260
|
} catch (err) {
|
|
6189
|
-
logger$
|
|
6261
|
+
logger$m.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
6190
6262
|
}
|
|
6191
6263
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
6192
6264
|
}
|
|
@@ -6206,9 +6278,14 @@ async function extractContentInner(webContents) {
|
|
|
6206
6278
|
);
|
|
6207
6279
|
}
|
|
6208
6280
|
async function extractContent(webContents) {
|
|
6281
|
+
const cacheKey = `${webContents.id}:${webContents.getURL() || ""}`;
|
|
6282
|
+
const cached = extractionCache.get(cacheKey);
|
|
6283
|
+
if (cached && Date.now() - cached.capturedAt < EXTRACTION_CACHE_TTL_MS) {
|
|
6284
|
+
return structuredClone(cached.content);
|
|
6285
|
+
}
|
|
6209
6286
|
try {
|
|
6210
6287
|
const timeoutMs = await estimateExtractionTimeout(webContents);
|
|
6211
|
-
|
|
6288
|
+
const content = await Promise.race([
|
|
6212
6289
|
extractContentInner(webContents),
|
|
6213
6290
|
new Promise(
|
|
6214
6291
|
(_, reject) => setTimeout(
|
|
@@ -6217,6 +6294,15 @@ async function extractContent(webContents) {
|
|
|
6217
6294
|
)
|
|
6218
6295
|
)
|
|
6219
6296
|
]);
|
|
6297
|
+
extractionCache.set(cacheKey, {
|
|
6298
|
+
capturedAt: Date.now(),
|
|
6299
|
+
content: structuredClone(content)
|
|
6300
|
+
});
|
|
6301
|
+
if (extractionCache.size > MAX_EXTRACTION_CACHE_ENTRIES) {
|
|
6302
|
+
const oldestKey = extractionCache.keys().next().value;
|
|
6303
|
+
if (oldestKey) extractionCache.delete(oldestKey);
|
|
6304
|
+
}
|
|
6305
|
+
return content;
|
|
6220
6306
|
} catch (err) {
|
|
6221
6307
|
const url = webContents.getURL() || "";
|
|
6222
6308
|
let domain = "unknown";
|
|
@@ -6311,6 +6397,7 @@ function attachDestroyCleanup(wc) {
|
|
|
6311
6397
|
const MAX_PERSISTED_DIFF_BURSTS = 50;
|
|
6312
6398
|
const MAX_HISTORY_DAYS = 30;
|
|
6313
6399
|
const SAVE_DEBOUNCE_MS$2 = 500;
|
|
6400
|
+
const BACKGROUND_DIFF_CAPTURE_DELAY_MS = 15e3;
|
|
6314
6401
|
function getHistoryFilePath() {
|
|
6315
6402
|
return path.join(electron.app.getPath("userData"), "vessel-page-diff-history.json");
|
|
6316
6403
|
}
|
|
@@ -6443,7 +6530,7 @@ function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
|
6443
6530
|
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
|
|
6444
6531
|
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
6445
6532
|
}
|
|
6446
|
-
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
6533
|
+
function scheduleTimerAt(wc, sendToRendererViews, dueAt, options = {}) {
|
|
6447
6534
|
attachDestroyCleanup(wc);
|
|
6448
6535
|
const wcId = wc.id;
|
|
6449
6536
|
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
@@ -6451,14 +6538,24 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
|
6451
6538
|
const timer = setTimeout(() => {
|
|
6452
6539
|
cleanupTimersForWcId(wcId);
|
|
6453
6540
|
if (wc.isDestroyed()) return;
|
|
6541
|
+
if (options.isActive && !options.isActive()) {
|
|
6542
|
+
scheduleTimerAt(
|
|
6543
|
+
wc,
|
|
6544
|
+
sendToRendererViews,
|
|
6545
|
+
Date.now() + BACKGROUND_DIFF_CAPTURE_DELAY_MS,
|
|
6546
|
+
options
|
|
6547
|
+
);
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6454
6550
|
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
6455
6551
|
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
6456
6552
|
}, Math.max(0, dueAt - Date.now()));
|
|
6457
6553
|
pendingPageSnapshotTimers.set(wcId, timer);
|
|
6458
6554
|
pendingPageSnapshotDueAt.set(wcId, dueAt);
|
|
6459
6555
|
}
|
|
6460
|
-
function notePageMutationActivity(wc, sendToRendererViews) {
|
|
6556
|
+
function notePageMutationActivity(wc, sendToRendererViews, options = {}) {
|
|
6461
6557
|
if (wc.isDestroyed()) return;
|
|
6558
|
+
if (options.isActive && !options.isActive()) return;
|
|
6462
6559
|
const wcId = wc.id;
|
|
6463
6560
|
const now = Date.now();
|
|
6464
6561
|
lastMutationActivityAt.set(wcId, now);
|
|
@@ -6466,18 +6563,19 @@ function notePageMutationActivity(wc, sendToRendererViews) {
|
|
|
6466
6563
|
if (existingDueAt == null) return;
|
|
6467
6564
|
const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
|
|
6468
6565
|
if (nextDueAt <= existingDueAt) return;
|
|
6469
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6566
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6470
6567
|
}
|
|
6471
|
-
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
6568
|
+
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0, options = {}) {
|
|
6472
6569
|
if (wc.isDestroyed()) return;
|
|
6473
6570
|
const wcId = wc.id;
|
|
6474
6571
|
const now = Date.now();
|
|
6475
|
-
const
|
|
6572
|
+
const effectiveDelayMs = options.isActive && !options.isActive() ? Math.max(delayMs, BACKGROUND_DIFF_CAPTURE_DELAY_MS) : delayMs;
|
|
6573
|
+
const nextDueAt = computeNextSnapshotDueAt(wcId, now, effectiveDelayMs);
|
|
6476
6574
|
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
6477
6575
|
if (existingDueAt != null && existingDueAt >= nextDueAt) {
|
|
6478
6576
|
return;
|
|
6479
6577
|
}
|
|
6480
|
-
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
6578
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
|
|
6481
6579
|
}
|
|
6482
6580
|
function enableClipboardShortcuts(view) {
|
|
6483
6581
|
view.webContents.on("before-input-event", (event, input) => {
|
|
@@ -6693,6 +6791,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
6693
6791
|
sidebarView.webContents.send(channel, ...args);
|
|
6694
6792
|
};
|
|
6695
6793
|
tabManager.onPageLoad((url, wc) => {
|
|
6794
|
+
const activeWc = tabManager.getActiveTab()?.view.webContents;
|
|
6795
|
+
if (activeWc?.id !== wc.id) return;
|
|
6696
6796
|
void capturePageSnapshot(url, wc, sendToRendererViews);
|
|
6697
6797
|
});
|
|
6698
6798
|
const state2 = {
|
|
@@ -7385,8 +7485,9 @@ const PROVIDERS = {
|
|
|
7385
7485
|
openrouter: {
|
|
7386
7486
|
id: "openrouter",
|
|
7387
7487
|
name: "OpenRouter",
|
|
7388
|
-
defaultModel: "
|
|
7488
|
+
defaultModel: "openrouter/free",
|
|
7389
7489
|
models: [
|
|
7490
|
+
"openrouter/free",
|
|
7390
7491
|
"anthropic/claude-sonnet-4",
|
|
7391
7492
|
"anthropic/claude-haiku-4",
|
|
7392
7493
|
"openai/gpt-4o",
|
|
@@ -7459,6 +7560,36 @@ const PROVIDERS = {
|
|
|
7459
7560
|
apiKeyHint: "Optional — only if your endpoint requires authentication"
|
|
7460
7561
|
}
|
|
7461
7562
|
};
|
|
7563
|
+
function parseModelSizeInBillions(model) {
|
|
7564
|
+
const match = model.toLowerCase().match(/(?:^|[:/_\-\s])(\d+(?:\.\d+)?)b(?:$|[:/_\-\s])/i);
|
|
7565
|
+
if (!match) return null;
|
|
7566
|
+
const parsed = Number(match[1]);
|
|
7567
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
7568
|
+
}
|
|
7569
|
+
function isLoopbackBaseUrl(baseUrl) {
|
|
7570
|
+
if (!baseUrl) return false;
|
|
7571
|
+
try {
|
|
7572
|
+
const url = new URL(baseUrl);
|
|
7573
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
7574
|
+
} catch {
|
|
7575
|
+
return false;
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
function resolveAgentToolProfile(config) {
|
|
7579
|
+
const providerId = config.id;
|
|
7580
|
+
const isLocalProvider = providerId === "ollama" || providerId === "llama_cpp" || providerId === "custom" && isLoopbackBaseUrl(config.baseUrl);
|
|
7581
|
+
if (!isLocalProvider) return "default";
|
|
7582
|
+
const sizeInBillions = parseModelSizeInBillions(config.model);
|
|
7583
|
+
if (sizeInBillions === null) {
|
|
7584
|
+
return "compact";
|
|
7585
|
+
}
|
|
7586
|
+
return sizeInBillions <= 14 ? "compact" : "default";
|
|
7587
|
+
}
|
|
7588
|
+
const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
|
|
7589
|
+
const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
7590
|
+
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7591
|
+
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7592
|
+
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7462
7593
|
const SAFE_TOOL_ALIASES = {
|
|
7463
7594
|
goto_url: "navigate",
|
|
7464
7595
|
go_to_url: "navigate",
|
|
@@ -7523,682 +7654,657 @@ function normalizeToolAlias(name) {
|
|
|
7523
7654
|
}
|
|
7524
7655
|
return name;
|
|
7525
7656
|
}
|
|
7526
|
-
function
|
|
7527
|
-
const
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7657
|
+
function stableToolSignature(name, args) {
|
|
7658
|
+
const canonicalArgs = canonicalizeArgsForTool(name, args);
|
|
7659
|
+
const sortedEntries = Object.entries(canonicalArgs).sort(
|
|
7660
|
+
([left], [right]) => left.localeCompare(right)
|
|
7661
|
+
);
|
|
7662
|
+
return JSON.stringify([name, sortedEntries]);
|
|
7531
7663
|
}
|
|
7532
|
-
function
|
|
7533
|
-
|
|
7664
|
+
function normalizeToolToken(value) {
|
|
7665
|
+
return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7666
|
+
}
|
|
7667
|
+
function canonicalizeUrlLike(value) {
|
|
7534
7668
|
try {
|
|
7535
|
-
const url = new URL(
|
|
7536
|
-
|
|
7669
|
+
const url = new URL(value.trim());
|
|
7670
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
7671
|
+
url.hostname = url.hostname.replace(/^www\./, "");
|
|
7672
|
+
url.hash = "";
|
|
7673
|
+
if (url.pathname.endsWith("/") && url.pathname !== "/") {
|
|
7674
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
7675
|
+
}
|
|
7676
|
+
return url.toString();
|
|
7677
|
+
}
|
|
7537
7678
|
} catch {
|
|
7538
|
-
return false;
|
|
7539
7679
|
}
|
|
7680
|
+
return value.trim();
|
|
7540
7681
|
}
|
|
7541
|
-
function
|
|
7542
|
-
const
|
|
7543
|
-
|
|
7544
|
-
if (
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
return "compact";
|
|
7682
|
+
function toLikelyUrl(value) {
|
|
7683
|
+
const trimmed = value.trim().replace(/^["']|["']$/g, "");
|
|
7684
|
+
if (!trimmed) return null;
|
|
7685
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
7686
|
+
if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
|
|
7687
|
+
return `https://${trimmed}`;
|
|
7548
7688
|
}
|
|
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());
|
|
7689
|
+
return null;
|
|
7584
7690
|
}
|
|
7585
|
-
function
|
|
7586
|
-
const
|
|
7587
|
-
if (!
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7691
|
+
function scalarArgsForTool(name, scalar) {
|
|
7692
|
+
const trimmed = scalar.trim();
|
|
7693
|
+
if (!trimmed) return null;
|
|
7694
|
+
if (name === "navigate") {
|
|
7695
|
+
const url = toLikelyUrl(trimmed);
|
|
7696
|
+
return url ? { url } : null;
|
|
7697
|
+
}
|
|
7698
|
+
if (name === "search") {
|
|
7699
|
+
return { query: trimmed.replace(/^["']|["']$/g, "") };
|
|
7700
|
+
}
|
|
7701
|
+
if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
|
|
7702
|
+
return { text: trimmed.replace(/^["']|["']$/g, "") };
|
|
7703
|
+
}
|
|
7704
|
+
if (name === "read_page") {
|
|
7705
|
+
const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
|
|
7706
|
+
if (mode) return { mode };
|
|
7707
|
+
}
|
|
7708
|
+
if (name === "save_bookmark") {
|
|
7709
|
+
const url = toLikelyUrl(trimmed);
|
|
7710
|
+
if (url) return { url };
|
|
7711
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
7712
|
+
if (lastSpace > 0) {
|
|
7713
|
+
const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
|
|
7714
|
+
if (maybeUrl) {
|
|
7715
|
+
return {
|
|
7716
|
+
url: maybeUrl,
|
|
7717
|
+
title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "")
|
|
7718
|
+
};
|
|
7592
7719
|
}
|
|
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;
|
|
7720
|
+
}
|
|
7604
7721
|
}
|
|
7722
|
+
return null;
|
|
7605
7723
|
}
|
|
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();
|
|
7724
|
+
function firstStringArg(args, keys) {
|
|
7725
|
+
for (const key2 of keys) {
|
|
7726
|
+
const value = args[key2];
|
|
7727
|
+
if (typeof value === "string" && value.trim()) {
|
|
7728
|
+
return value.trim();
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
return null;
|
|
7622
7732
|
}
|
|
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
|
-
);
|
|
7733
|
+
function normalizeElementTargetArgs(args) {
|
|
7734
|
+
const normalized = { ...args };
|
|
7735
|
+
if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
|
|
7736
|
+
normalized.index = Number(normalized.index.trim());
|
|
7638
7737
|
}
|
|
7639
|
-
if (
|
|
7640
|
-
|
|
7738
|
+
if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
|
|
7739
|
+
const selector = firstStringArg(normalized, [
|
|
7740
|
+
"cssSelector",
|
|
7741
|
+
"css_selector",
|
|
7742
|
+
"querySelector",
|
|
7743
|
+
"query_selector"
|
|
7744
|
+
]);
|
|
7745
|
+
if (selector) normalized.selector = selector;
|
|
7641
7746
|
}
|
|
7642
|
-
if (
|
|
7643
|
-
|
|
7747
|
+
if (typeof normalized.text !== "string" || !normalized.text.trim()) {
|
|
7748
|
+
const text = firstStringArg(normalized, [
|
|
7749
|
+
"label",
|
|
7750
|
+
"title",
|
|
7751
|
+
"name",
|
|
7752
|
+
"target",
|
|
7753
|
+
"element",
|
|
7754
|
+
"linkText",
|
|
7755
|
+
"link_text",
|
|
7756
|
+
"ariaLabel",
|
|
7757
|
+
"aria_label"
|
|
7758
|
+
]);
|
|
7759
|
+
if (text) normalized.text = text;
|
|
7644
7760
|
}
|
|
7645
|
-
return
|
|
7761
|
+
return normalized;
|
|
7646
7762
|
}
|
|
7647
|
-
function
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
);
|
|
7656
|
-
const
|
|
7657
|
-
const
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
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.`;
|
|
7763
|
+
function hasElementTarget(args) {
|
|
7764
|
+
return typeof args.index === "number" || typeof args.selector === "string" && args.selector.trim().length > 0 || typeof args.text === "string" && args.text.trim().length > 0;
|
|
7765
|
+
}
|
|
7766
|
+
function isTargetlessClickArgs(args) {
|
|
7767
|
+
return !hasElementTarget(normalizeElementTargetArgs(args));
|
|
7768
|
+
}
|
|
7769
|
+
function tryParseJsonWithCommonRepairs(raw) {
|
|
7770
|
+
const trimmed = raw.trim();
|
|
7771
|
+
if (!trimmed) return {};
|
|
7772
|
+
const candidates = /* @__PURE__ */ new Set([trimmed]);
|
|
7773
|
+
const objectMatch = trimmed.match(/\{[\s\S]*\}/);
|
|
7774
|
+
if (objectMatch?.[0]) candidates.add(objectMatch[0]);
|
|
7775
|
+
if (!trimmed.startsWith("{") && trimmed.includes(":")) {
|
|
7776
|
+
candidates.add(`{${trimmed}}`);
|
|
7698
7777
|
}
|
|
7699
|
-
|
|
7700
|
-
|
|
7778
|
+
for (const candidate of candidates) {
|
|
7779
|
+
const normalized = candidate.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
7780
|
+
if (!normalized) continue;
|
|
7781
|
+
const repaired = normalized.replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)(\s*:)/g, '$1"$2"$3').replace(/([{,]\s*)'([^']+)'(\s*:)/g, '$1"$2"$3').replace(
|
|
7782
|
+
/:\s*'([^'\\]*(?:\\.[^'\\]*)*)'/g,
|
|
7783
|
+
(_match, value) => `: ${JSON.stringify(value)}`
|
|
7784
|
+
).replace(/,\s*([}\]])/g, "$1");
|
|
7785
|
+
try {
|
|
7786
|
+
return JSON.parse(repaired);
|
|
7787
|
+
} catch {
|
|
7788
|
+
}
|
|
7701
7789
|
}
|
|
7702
|
-
|
|
7703
|
-
|
|
7790
|
+
throw new Error("invalid-json");
|
|
7791
|
+
}
|
|
7792
|
+
function parseToolArgsWithRepair(name, argsJson) {
|
|
7793
|
+
const trimmed = (argsJson || "").trim();
|
|
7794
|
+
if (!trimmed) return { args: {}, repaired: false };
|
|
7795
|
+
try {
|
|
7796
|
+
const parsed = JSON.parse(trimmed);
|
|
7797
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7798
|
+
return { args: parsed, repaired: false };
|
|
7799
|
+
}
|
|
7800
|
+
if (typeof parsed === "string") {
|
|
7801
|
+
const scalarArgs2 = scalarArgsForTool(name, parsed);
|
|
7802
|
+
return scalarArgs2 ? { args: scalarArgs2, repaired: true } : null;
|
|
7803
|
+
}
|
|
7804
|
+
return null;
|
|
7805
|
+
} catch {
|
|
7704
7806
|
}
|
|
7705
|
-
|
|
7706
|
-
|
|
7807
|
+
try {
|
|
7808
|
+
const repaired = tryParseJsonWithCommonRepairs(trimmed);
|
|
7809
|
+
if (repaired && typeof repaired === "object" && !Array.isArray(repaired)) {
|
|
7810
|
+
return { args: repaired, repaired: true };
|
|
7811
|
+
}
|
|
7812
|
+
if (typeof repaired === "string") {
|
|
7813
|
+
const scalarArgs2 = scalarArgsForTool(name, repaired);
|
|
7814
|
+
return scalarArgs2 ? { args: scalarArgs2, repaired: true } : null;
|
|
7815
|
+
}
|
|
7816
|
+
} catch {
|
|
7707
7817
|
}
|
|
7708
|
-
|
|
7709
|
-
|
|
7818
|
+
const scalarArgs = scalarArgsForTool(name, trimmed);
|
|
7819
|
+
return scalarArgs ? { args: scalarArgs, repaired: true } : null;
|
|
7820
|
+
}
|
|
7821
|
+
function coerceToolArgsForExecution(name, args) {
|
|
7822
|
+
let coerced = { ...args };
|
|
7823
|
+
if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
|
|
7824
|
+
coerced = normalizeElementTargetArgs(coerced);
|
|
7710
7825
|
}
|
|
7711
|
-
if (
|
|
7712
|
-
|
|
7826
|
+
if (name === "search") {
|
|
7827
|
+
if (typeof coerced.query !== "string" || !coerced.query.trim()) {
|
|
7828
|
+
if (typeof coerced.text === "string" && coerced.text.trim()) {
|
|
7829
|
+
coerced.query = coerced.text.trim();
|
|
7830
|
+
} else if (typeof coerced.term === "string" && coerced.term.trim()) {
|
|
7831
|
+
coerced.query = coerced.term.trim();
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7713
7834
|
}
|
|
7714
|
-
if (
|
|
7715
|
-
|
|
7835
|
+
if (name === "navigate") {
|
|
7836
|
+
if (typeof coerced.url !== "string" || !coerced.url.trim()) {
|
|
7837
|
+
if (typeof coerced.href === "string" && coerced.href.trim()) {
|
|
7838
|
+
coerced.url = coerced.href.trim();
|
|
7839
|
+
} else if (typeof coerced.link === "string" && coerced.link.trim()) {
|
|
7840
|
+
coerced.url = coerced.link.trim();
|
|
7841
|
+
} else if (typeof coerced.text === "string" && /^https?:\/\//i.test(coerced.text.trim())) {
|
|
7842
|
+
coerced.url = coerced.text.trim();
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7716
7845
|
}
|
|
7717
|
-
if (
|
|
7718
|
-
|
|
7846
|
+
if (name === "save_bookmark") {
|
|
7847
|
+
if (typeof coerced.url !== "string" || !coerced.url.trim()) {
|
|
7848
|
+
if (typeof coerced.link === "string" && coerced.link.trim()) {
|
|
7849
|
+
coerced.url = coerced.link.trim();
|
|
7850
|
+
} else if (typeof coerced.href === "string" && coerced.href.trim()) {
|
|
7851
|
+
coerced.url = coerced.href.trim();
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
if (typeof coerced.folderName !== "string" || !coerced.folderName.trim()) {
|
|
7855
|
+
if (typeof coerced.folder === "string" && coerced.folder.trim()) {
|
|
7856
|
+
coerced.folderName = coerced.folder.trim();
|
|
7857
|
+
} else if (typeof coerced.category === "string" && coerced.category.trim()) {
|
|
7858
|
+
coerced.folderName = coerced.category.trim();
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
if (coerced.folderName && typeof coerced.createFolderIfMissing === "undefined") {
|
|
7862
|
+
coerced.createFolderIfMissing = true;
|
|
7863
|
+
}
|
|
7719
7864
|
}
|
|
7720
|
-
return
|
|
7865
|
+
return coerced;
|
|
7721
7866
|
}
|
|
7722
|
-
function
|
|
7723
|
-
const
|
|
7724
|
-
if (
|
|
7725
|
-
|
|
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
|
-
}
|
|
7867
|
+
function canonicalizeArgsForTool(name, args) {
|
|
7868
|
+
const canonical = coerceToolArgsForExecution(name, args);
|
|
7869
|
+
if (typeof canonical.url === "string") {
|
|
7870
|
+
canonical.url = canonicalizeUrlLike(canonical.url);
|
|
7734
7871
|
}
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7872
|
+
if (typeof canonical.query === "string") {
|
|
7873
|
+
canonical.query = canonical.query.trim().replace(/\s+/g, " ").toLowerCase();
|
|
7874
|
+
delete canonical.text;
|
|
7739
7875
|
}
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
if (navigatedUrl) {
|
|
7743
|
-
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
7876
|
+
if (typeof canonical.text === "string") {
|
|
7877
|
+
canonical.text = canonical.text.trim().replace(/\s+/g, " ");
|
|
7744
7878
|
}
|
|
7745
|
-
return
|
|
7879
|
+
return canonical;
|
|
7746
7880
|
}
|
|
7747
|
-
function
|
|
7748
|
-
const
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
"
|
|
7756
|
-
"
|
|
7757
|
-
"
|
|
7758
|
-
"
|
|
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"
|
|
7881
|
+
function unsupportedToolHint(name) {
|
|
7882
|
+
const normalized = name.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
7883
|
+
const BOOKMARK_NAMES = [
|
|
7884
|
+
"organize_bookmark",
|
|
7885
|
+
"organize_bookmarks",
|
|
7886
|
+
"manage_bookmark",
|
|
7887
|
+
"manage_bookmarks",
|
|
7888
|
+
"add_to_bookmarks",
|
|
7889
|
+
"save_to_bookmarks",
|
|
7890
|
+
"bookmark_link",
|
|
7891
|
+
"save_link",
|
|
7892
|
+
"store_bookmark"
|
|
7766
7893
|
];
|
|
7767
|
-
if (
|
|
7768
|
-
return
|
|
7894
|
+
if (BOOKMARK_NAMES.includes(normalized) || /bookmark|save.*link|organize/.test(normalized)) {
|
|
7895
|
+
return `Error: "${name}" is not a supported tool. Use save_bookmark to save a page as a bookmark, or create_bookmark_folder to create a folder. Example: save_bookmark with {"url": "...", "title": "...", "folderName": "..."}`;
|
|
7769
7896
|
}
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
);
|
|
7775
|
-
|
|
7776
|
-
|
|
7897
|
+
return `Error: ${name} is not a supported tool. Choose one of the available browser tools instead.`;
|
|
7898
|
+
}
|
|
7899
|
+
function resolveToolCallName(rawName, args, availableToolNames) {
|
|
7900
|
+
const aliased = normalizeToolAlias(rawName);
|
|
7901
|
+
if (availableToolNames.has(aliased)) return aliased;
|
|
7902
|
+
const normalized = normalizeToolToken(rawName);
|
|
7903
|
+
if (availableToolNames.has(normalized)) return normalized;
|
|
7904
|
+
const hasUrl = typeof args.url === "string" && args.url.trim().length > 0;
|
|
7905
|
+
if (availableToolNames.has("navigate") && (hasUrl || /goto|navigate|open|visit|browser|url|link/.test(normalized))) {
|
|
7906
|
+
return "navigate";
|
|
7777
7907
|
}
|
|
7778
|
-
|
|
7779
|
-
|
|
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;
|
|
7908
|
+
if (availableToolNames.has("search") && (/search|find|lookup|query/.test(normalized) || normalized === "google" || normalized.startsWith("google_"))) {
|
|
7909
|
+
return "search";
|
|
7792
7910
|
}
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
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);
|
|
7911
|
+
if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
|
|
7912
|
+
return "scroll";
|
|
7913
|
+
}
|
|
7914
|
+
if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
|
|
7915
|
+
return "read_page";
|
|
7916
|
+
}
|
|
7917
|
+
return aliased;
|
|
7820
7918
|
}
|
|
7821
|
-
function
|
|
7822
|
-
const
|
|
7823
|
-
|
|
7824
|
-
|
|
7919
|
+
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
7920
|
+
const trimmed = text.trim();
|
|
7921
|
+
if (!trimmed) return [];
|
|
7922
|
+
const candidates = trimmed.match(
|
|
7923
|
+
/([A-Za-z0-9._ -]+)\s*\[ARGS\]\s*(\{[\s\S]*?\})(?=\s*$|\n{2,}|[A-Za-z0-9._ -]+\s*\[ARGS\])/g
|
|
7825
7924
|
);
|
|
7826
|
-
return
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
}
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
const
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7925
|
+
if (!candidates || candidates.length === 0) return [];
|
|
7926
|
+
const recovered = [];
|
|
7927
|
+
for (const candidate of candidates) {
|
|
7928
|
+
const match = candidate.match(
|
|
7929
|
+
/^\s*([A-Za-z0-9._ -]+)\s*\[ARGS\]\s*(\{[\s\S]*\})\s*$/
|
|
7930
|
+
);
|
|
7931
|
+
if (!match) continue;
|
|
7932
|
+
const rawName = match[1] ?? "";
|
|
7933
|
+
const argsJson = match[2] ?? "{}";
|
|
7934
|
+
let parsedArgs = {};
|
|
7935
|
+
try {
|
|
7936
|
+
const raw = JSON.parse(argsJson);
|
|
7937
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7938
|
+
parsedArgs = raw;
|
|
7839
7939
|
}
|
|
7840
|
-
|
|
7940
|
+
} catch {
|
|
7941
|
+
continue;
|
|
7841
7942
|
}
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7943
|
+
const resolvedName = resolveToolCallName(
|
|
7944
|
+
rawName,
|
|
7945
|
+
parsedArgs,
|
|
7946
|
+
availableToolNames
|
|
7947
|
+
);
|
|
7948
|
+
recovered.push({
|
|
7949
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
7950
|
+
name: resolvedName,
|
|
7951
|
+
argsJson
|
|
7952
|
+
});
|
|
7852
7953
|
}
|
|
7853
|
-
return
|
|
7954
|
+
return recovered;
|
|
7854
7955
|
}
|
|
7855
|
-
function
|
|
7856
|
-
const trimmed =
|
|
7857
|
-
if (!trimmed) return
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
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, "") };
|
|
7956
|
+
function recoverNarratedActionToolCalls(text, availableToolNames) {
|
|
7957
|
+
const trimmed = text.trim();
|
|
7958
|
+
if (!trimmed) return [];
|
|
7959
|
+
const recovered = [];
|
|
7960
|
+
const actionLines = trimmed.match(/^action:\s+.+$/gim) ?? [];
|
|
7961
|
+
for (const rawLine of actionLines) {
|
|
7962
|
+
const line = rawLine.replace(/^action:\s*/i, "").trim();
|
|
7963
|
+
if (!line) continue;
|
|
7964
|
+
const quotedValue = line.match(/"([^"]+)"/)?.[1]?.trim() ?? line.match(/'([^']+)'/)?.[1]?.trim() ?? "";
|
|
7965
|
+
const navigateMatch = line.match(
|
|
7966
|
+
/\b(?:navigate|open|go)\b(?:\s+(?:to|the url))?\s+(https?:\/\/[^\s)]+)\.?/i
|
|
7967
|
+
);
|
|
7968
|
+
if (navigateMatch?.[1]) {
|
|
7969
|
+
const argsJson = JSON.stringify({ url: navigateMatch[1].replace(/\.$/, "") });
|
|
7970
|
+
recovered.push({
|
|
7971
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
7972
|
+
name: resolveToolCallName("navigate", { url: navigateMatch[1] }, availableToolNames),
|
|
7973
|
+
argsJson
|
|
7974
|
+
});
|
|
7975
|
+
continue;
|
|
7879
7976
|
}
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7977
|
+
const isSearchAction = /\bsearch\b/i.test(line) || /\btype\b/i.test(line) && /\bsearch box\b/i.test(line);
|
|
7978
|
+
if (isSearchAction && quotedValue && availableToolNames.has("search")) {
|
|
7979
|
+
recovered.push({
|
|
7980
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
7981
|
+
name: "search",
|
|
7982
|
+
argsJson: JSON.stringify({ query: quotedValue })
|
|
7983
|
+
});
|
|
7984
|
+
continue;
|
|
7985
|
+
}
|
|
7986
|
+
if (/\b(?:read|scan)\b.*\bpage\b/i.test(line) && availableToolNames.has("read_page")) {
|
|
7987
|
+
recovered.push({
|
|
7988
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
7989
|
+
name: "read_page",
|
|
7990
|
+
argsJson: JSON.stringify({ mode: "visible_only" })
|
|
7991
|
+
});
|
|
7992
|
+
continue;
|
|
7993
|
+
}
|
|
7994
|
+
const toolRefMatch = line.match(
|
|
7995
|
+
/\b(?:use|call)\s+([a-z_][a-z0-9_]*)(?:\s+tool)?\b/i
|
|
7996
|
+
);
|
|
7997
|
+
if (toolRefMatch?.[1]) {
|
|
7998
|
+
const toolName = resolveToolCallName(toolRefMatch[1], {}, availableToolNames);
|
|
7999
|
+
recovered.push({
|
|
8000
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8001
|
+
name: toolName,
|
|
8002
|
+
argsJson: "{}"
|
|
8003
|
+
});
|
|
7888
8004
|
}
|
|
7889
8005
|
}
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
8006
|
+
const inlineReadMatch = trimmed.match(
|
|
8007
|
+
/\bread_?page\b\s*\(\s*mode\s*=\s*["']?([a-z_]+)["']?\s*\)/i
|
|
8008
|
+
) ?? trimmed.match(
|
|
8009
|
+
/\breadpage\b\s*\(\s*mode\s*=\s*["']?([a-z_]+)["']?\s*\)/i
|
|
8010
|
+
);
|
|
8011
|
+
if (inlineReadMatch && availableToolNames.has("read_page")) {
|
|
8012
|
+
const rawMode = (inlineReadMatch[1] || "").trim().toLowerCase();
|
|
8013
|
+
const normalizedMode = rawMode === "visibleonly" ? "visible_only" : rawMode === "resultsonly" ? "results_only" : rawMode;
|
|
8014
|
+
if (normalizedMode) {
|
|
8015
|
+
recovered.push({
|
|
8016
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8017
|
+
name: "read_page",
|
|
8018
|
+
argsJson: JSON.stringify({ mode: normalizedMode })
|
|
8019
|
+
});
|
|
8020
|
+
return recovered;
|
|
8021
|
+
}
|
|
7905
8022
|
}
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
"
|
|
7913
|
-
"
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
"aria_label"
|
|
7917
|
-
]);
|
|
7918
|
-
if (text) normalized.text = text;
|
|
8023
|
+
const inlineInspectMatch = trimmed.match(
|
|
8024
|
+
/\binspect_?element\b(?:\s+tool)?\b/i
|
|
8025
|
+
) ?? trimmed.match(/\binspectelement\b\b/i);
|
|
8026
|
+
if (inlineInspectMatch && availableToolNames.has("inspect_element")) {
|
|
8027
|
+
recovered.push({
|
|
8028
|
+
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8029
|
+
name: "inspect_element",
|
|
8030
|
+
argsJson: "{}"
|
|
8031
|
+
});
|
|
8032
|
+
return recovered;
|
|
7919
8033
|
}
|
|
7920
|
-
return
|
|
8034
|
+
return recovered;
|
|
7921
8035
|
}
|
|
7922
|
-
|
|
7923
|
-
|
|
8036
|
+
const logger$l = createLogger("OpenAIProvider");
|
|
8037
|
+
function shouldDebugAgentLoop() {
|
|
8038
|
+
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
8039
|
+
return value === "1" || value === "true";
|
|
7924
8040
|
}
|
|
7925
|
-
function
|
|
7926
|
-
|
|
8041
|
+
function previewDebugValue(value, maxLength = 800) {
|
|
8042
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
8043
|
+
if (normalized.length <= maxLength) return normalized;
|
|
8044
|
+
return `${normalized.slice(0, maxLength)}…`;
|
|
7927
8045
|
}
|
|
7928
|
-
function
|
|
7929
|
-
|
|
7930
|
-
if (!trimmed) return {};
|
|
7931
|
-
const candidates = /* @__PURE__ */ new Set([trimmed]);
|
|
7932
|
-
const objectMatch = trimmed.match(/\{[\s\S]*\}/);
|
|
7933
|
-
if (objectMatch?.[0]) candidates.add(objectMatch[0]);
|
|
7934
|
-
if (!trimmed.startsWith("{") && trimmed.includes(":")) {
|
|
7935
|
-
candidates.add(`{${trimmed}}`);
|
|
7936
|
-
}
|
|
7937
|
-
for (const candidate of candidates) {
|
|
7938
|
-
const normalized = candidate.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
7939
|
-
if (!normalized) continue;
|
|
7940
|
-
const repaired = normalized.replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)(\s*:)/g, '$1"$2"$3').replace(/([{,]\s*)'([^']+)'(\s*:)/g, '$1"$2"$3').replace(
|
|
7941
|
-
/:\s*'([^'\\]*(?:\\.[^'\\]*)*)'/g,
|
|
7942
|
-
(_match, value) => `: ${JSON.stringify(value)}`
|
|
7943
|
-
).replace(/,\s*([}\]])/g, "$1");
|
|
7944
|
-
try {
|
|
7945
|
-
return JSON.parse(repaired);
|
|
7946
|
-
} catch {
|
|
7947
|
-
}
|
|
7948
|
-
}
|
|
7949
|
-
throw new Error("invalid-json");
|
|
8046
|
+
function previewToolDebugContent(content) {
|
|
8047
|
+
return previewDebugValue(content, 500);
|
|
7950
8048
|
}
|
|
7951
|
-
function
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
}
|
|
7959
|
-
if (typeof parsed === "string") {
|
|
7960
|
-
const scalarArgs2 = scalarArgsForTool(name, parsed);
|
|
7961
|
-
return scalarArgs2 ? { args: scalarArgs2, repaired: true } : null;
|
|
7962
|
-
}
|
|
7963
|
-
return null;
|
|
7964
|
-
} catch {
|
|
7965
|
-
}
|
|
7966
|
-
try {
|
|
7967
|
-
const repaired = tryParseJsonWithCommonRepairs(trimmed);
|
|
7968
|
-
if (repaired && typeof repaired === "object" && !Array.isArray(repaired)) {
|
|
7969
|
-
return { args: repaired, repaired: true };
|
|
7970
|
-
}
|
|
7971
|
-
if (typeof repaired === "string") {
|
|
7972
|
-
const scalarArgs2 = scalarArgsForTool(name, repaired);
|
|
7973
|
-
return scalarArgs2 ? { args: scalarArgs2, repaired: true } : null;
|
|
8049
|
+
function toOpenAITools(tools) {
|
|
8050
|
+
return tools.map((t) => ({
|
|
8051
|
+
type: "function",
|
|
8052
|
+
function: {
|
|
8053
|
+
name: t.name,
|
|
8054
|
+
description: t.description ?? "",
|
|
8055
|
+
parameters: t.input_schema
|
|
7974
8056
|
}
|
|
7975
|
-
}
|
|
7976
|
-
}
|
|
7977
|
-
const scalarArgs = scalarArgsForTool(name, trimmed);
|
|
7978
|
-
return scalarArgs ? { args: scalarArgs, repaired: true } : null;
|
|
8057
|
+
}));
|
|
7979
8058
|
}
|
|
7980
|
-
function
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
8059
|
+
function agentTemperatureForProfile(profile) {
|
|
8060
|
+
return profile === "compact" ? 0.2 : void 0;
|
|
8061
|
+
}
|
|
8062
|
+
function modelLikelySupportsOpenAIReasoningEffort(model) {
|
|
8063
|
+
return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
|
|
8064
|
+
}
|
|
8065
|
+
function toOpenAIReasoningEffort(effort, providerId, model) {
|
|
8066
|
+
const supportsReasoningParam = providerId === "openrouter" || providerId === "custom" || providerId === "openai" && modelLikelySupportsOpenAIReasoningEffort(model);
|
|
8067
|
+
if (!supportsReasoningParam) return void 0;
|
|
8068
|
+
switch (effort) {
|
|
8069
|
+
case "off":
|
|
8070
|
+
if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
|
|
8071
|
+
return void 0;
|
|
7991
8072
|
}
|
|
7992
|
-
|
|
8073
|
+
return "none";
|
|
8074
|
+
case "low":
|
|
8075
|
+
return "low";
|
|
8076
|
+
case "medium":
|
|
8077
|
+
return "medium";
|
|
8078
|
+
case "high":
|
|
8079
|
+
return "high";
|
|
8080
|
+
case "max":
|
|
8081
|
+
return "xhigh";
|
|
8082
|
+
default:
|
|
8083
|
+
return void 0;
|
|
7993
8084
|
}
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8085
|
+
}
|
|
8086
|
+
function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
|
|
8087
|
+
if (profile !== "compact") return null;
|
|
8088
|
+
const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
|
|
8089
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
8090
|
+
return {
|
|
8091
|
+
role: "user",
|
|
8092
|
+
content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
|
|
8093
|
+
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 ? `
|
|
8094
|
+
${stateReminder}` : "") + (phaseReminder ? `
|
|
8095
|
+
${phaseReminder}` : "")
|
|
8096
|
+
};
|
|
8097
|
+
}
|
|
8098
|
+
function extractSingleGoalDomain(goal) {
|
|
8099
|
+
const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
|
|
8100
|
+
if (!matches || matches.length !== 1) return null;
|
|
8101
|
+
return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
|
|
8102
|
+
}
|
|
8103
|
+
function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
|
|
8104
|
+
const phaseReminder = buildPhaseReminder(userMessage, assistantText);
|
|
8105
|
+
const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
|
|
8106
|
+
const goalDomain = extractSingleGoalDomain(userMessage);
|
|
8107
|
+
const latest = (latestToolResultPreview || "").toLowerCase();
|
|
8108
|
+
const assistant = assistantText.toLowerCase();
|
|
8109
|
+
const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
|
|
8110
|
+
const lines = [
|
|
8111
|
+
`The task is still in progress: ${userMessage}`,
|
|
8112
|
+
`Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
|
|
8113
|
+
];
|
|
8114
|
+
if (alreadyOnGoalSite) {
|
|
8115
|
+
lines.push(
|
|
8116
|
+
`You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
|
|
8117
|
+
);
|
|
8004
8118
|
}
|
|
8005
|
-
if (
|
|
8006
|
-
|
|
8007
|
-
if (typeof coerced.link === "string" && coerced.link.trim()) {
|
|
8008
|
-
coerced.url = coerced.link.trim();
|
|
8009
|
-
} else if (typeof coerced.href === "string" && coerced.href.trim()) {
|
|
8010
|
-
coerced.url = coerced.href.trim();
|
|
8011
|
-
}
|
|
8012
|
-
}
|
|
8013
|
-
if (typeof coerced.folderName !== "string" || !coerced.folderName.trim()) {
|
|
8014
|
-
if (typeof coerced.folder === "string" && coerced.folder.trim()) {
|
|
8015
|
-
coerced.folderName = coerced.folder.trim();
|
|
8016
|
-
} else if (typeof coerced.category === "string" && coerced.category.trim()) {
|
|
8017
|
-
coerced.folderName = coerced.category.trim();
|
|
8018
|
-
}
|
|
8019
|
-
}
|
|
8020
|
-
if (coerced.folderName && typeof coerced.createFolderIfMissing === "undefined") {
|
|
8021
|
-
coerced.createFolderIfMissing = true;
|
|
8022
|
-
}
|
|
8119
|
+
if (stateReminder) {
|
|
8120
|
+
lines.push(stateReminder);
|
|
8023
8121
|
}
|
|
8024
|
-
|
|
8122
|
+
if (phaseReminder) {
|
|
8123
|
+
lines.push(phaseReminder);
|
|
8124
|
+
}
|
|
8125
|
+
return lines.join("\n");
|
|
8025
8126
|
}
|
|
8026
|
-
function
|
|
8027
|
-
const
|
|
8028
|
-
|
|
8029
|
-
|
|
8127
|
+
function buildPhaseReminder(userMessage, assistantText) {
|
|
8128
|
+
const goal = userMessage.toLowerCase();
|
|
8129
|
+
const text = assistantText.toLowerCase();
|
|
8130
|
+
if (!goal || !text) return "";
|
|
8131
|
+
const wantsCart = /\b(cart|bag|basket|checkout)\b/.test(goal);
|
|
8132
|
+
const wantsExplanation = /\b(explain|reason|why)\b/.test(goal);
|
|
8133
|
+
const wantsBookRecommendations = /\b(book|books|recommend|recommended|interesting|novel|fiction|nonfiction)\b/.test(
|
|
8134
|
+
goal
|
|
8135
|
+
);
|
|
8136
|
+
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);
|
|
8137
|
+
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);
|
|
8138
|
+
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);
|
|
8139
|
+
const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
|
|
8140
|
+
text
|
|
8141
|
+
);
|
|
8142
|
+
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);
|
|
8143
|
+
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(
|
|
8144
|
+
text
|
|
8145
|
+
);
|
|
8146
|
+
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(
|
|
8147
|
+
text
|
|
8148
|
+
);
|
|
8149
|
+
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(
|
|
8150
|
+
text
|
|
8151
|
+
) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
|
|
8152
|
+
text
|
|
8153
|
+
);
|
|
8154
|
+
const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
|
|
8155
|
+
text
|
|
8156
|
+
) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
|
|
8157
|
+
text
|
|
8158
|
+
);
|
|
8159
|
+
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(
|
|
8160
|
+
text
|
|
8161
|
+
);
|
|
8162
|
+
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(
|
|
8163
|
+
text
|
|
8164
|
+
);
|
|
8165
|
+
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(
|
|
8166
|
+
text
|
|
8167
|
+
);
|
|
8168
|
+
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(
|
|
8169
|
+
text
|
|
8170
|
+
) && !/(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(
|
|
8171
|
+
text
|
|
8172
|
+
);
|
|
8173
|
+
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
|
|
8174
|
+
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.`;
|
|
8030
8175
|
}
|
|
8031
|
-
if (
|
|
8032
|
-
|
|
8033
|
-
delete canonical.text;
|
|
8176
|
+
if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
|
|
8177
|
+
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.`;
|
|
8034
8178
|
}
|
|
8035
|
-
if (
|
|
8036
|
-
|
|
8179
|
+
if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
|
|
8180
|
+
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.`;
|
|
8037
8181
|
}
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
function unsupportedToolHint(name) {
|
|
8041
|
-
const normalized = name.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
|
|
8042
|
-
const BOOKMARK_NAMES = [
|
|
8043
|
-
"organize_bookmark",
|
|
8044
|
-
"organize_bookmarks",
|
|
8045
|
-
"manage_bookmark",
|
|
8046
|
-
"manage_bookmarks",
|
|
8047
|
-
"add_to_bookmarks",
|
|
8048
|
-
"save_to_bookmarks",
|
|
8049
|
-
"bookmark_link",
|
|
8050
|
-
"save_link",
|
|
8051
|
-
"store_bookmark"
|
|
8052
|
-
];
|
|
8053
|
-
if (BOOKMARK_NAMES.includes(normalized) || /bookmark|save.*link|organize/.test(normalized)) {
|
|
8054
|
-
return `Error: "${name}" is not a supported tool. Use save_bookmark to save a page as a bookmark, or create_bookmark_folder to create a folder. Example: save_bookmark with {"url": "...", "title": "...", "folderName": "..."}`;
|
|
8182
|
+
if (wantsCart && skippedSingleResultSignals && !selectedItems) {
|
|
8183
|
+
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.`;
|
|
8055
8184
|
}
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
function resolveToolCallName(rawName, args, availableToolNames) {
|
|
8059
|
-
const aliased = normalizeToolAlias(rawName);
|
|
8060
|
-
if (availableToolNames.has(aliased)) return aliased;
|
|
8061
|
-
const normalized = normalizeToolToken(rawName);
|
|
8062
|
-
if (availableToolNames.has(normalized)) return normalized;
|
|
8063
|
-
const hasUrl = typeof args.url === "string" && args.url.trim().length > 0;
|
|
8064
|
-
if (availableToolNames.has("navigate") && (hasUrl || /goto|navigate|open|visit|browser|url|link/.test(normalized))) {
|
|
8065
|
-
return "navigate";
|
|
8185
|
+
if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
|
|
8186
|
+
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.`;
|
|
8066
8187
|
}
|
|
8067
|
-
if (
|
|
8068
|
-
return
|
|
8188
|
+
if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
|
|
8189
|
+
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.`;
|
|
8069
8190
|
}
|
|
8070
|
-
if (
|
|
8071
|
-
return
|
|
8191
|
+
if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
|
|
8192
|
+
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.`;
|
|
8072
8193
|
}
|
|
8073
|
-
if (
|
|
8074
|
-
return
|
|
8194
|
+
if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
|
|
8195
|
+
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.`;
|
|
8075
8196
|
}
|
|
8076
|
-
|
|
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);
|
|
8197
|
+
if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
|
|
8198
|
+
return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
|
|
8084
8199
|
}
|
|
8200
|
+
return "";
|
|
8085
8201
|
}
|
|
8086
|
-
function
|
|
8087
|
-
const
|
|
8088
|
-
if (!
|
|
8089
|
-
const
|
|
8090
|
-
|
|
8202
|
+
function buildLatestStateReminder(toolResultPreview) {
|
|
8203
|
+
const text = toolResultPreview.trim();
|
|
8204
|
+
if (!text) return "";
|
|
8205
|
+
const stateMatch = text.match(
|
|
8206
|
+
/\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
|
|
8091
8207
|
);
|
|
8092
|
-
if (
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
);
|
|
8098
|
-
if (!match) continue;
|
|
8099
|
-
const rawName = match[1] ?? "";
|
|
8100
|
-
const argsJson = match[2] ?? "{}";
|
|
8101
|
-
let parsedArgs = {};
|
|
8102
|
-
try {
|
|
8103
|
-
const raw = JSON.parse(argsJson);
|
|
8104
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
8105
|
-
parsedArgs = raw;
|
|
8106
|
-
}
|
|
8107
|
-
} catch {
|
|
8108
|
-
continue;
|
|
8208
|
+
if (stateMatch) {
|
|
8209
|
+
const url = stateMatch[1]?.trim();
|
|
8210
|
+
const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
|
|
8211
|
+
if (url) {
|
|
8212
|
+
return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8109
8213
|
}
|
|
8110
|
-
const resolvedName = resolveToolCallName(
|
|
8111
|
-
rawName,
|
|
8112
|
-
parsedArgs,
|
|
8113
|
-
availableToolNames
|
|
8114
|
-
);
|
|
8115
|
-
recovered.push({
|
|
8116
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8117
|
-
name: resolvedName,
|
|
8118
|
-
argsJson
|
|
8119
|
-
});
|
|
8120
8214
|
}
|
|
8121
|
-
|
|
8215
|
+
const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8216
|
+
const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
8217
|
+
if (structuredUrl) {
|
|
8218
|
+
return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8219
|
+
}
|
|
8220
|
+
const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
|
|
8221
|
+
const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
|
|
8222
|
+
if (navigatedUrl) {
|
|
8223
|
+
return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
|
|
8224
|
+
}
|
|
8225
|
+
return "";
|
|
8122
8226
|
}
|
|
8123
|
-
function
|
|
8124
|
-
const trimmed = text.trim();
|
|
8125
|
-
if (!trimmed) return
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
const line = rawLine.replace(/^action:\s*/i, "").trim();
|
|
8130
|
-
if (!line) continue;
|
|
8131
|
-
const quotedValue = line.match(/"([^"]+)"/)?.[1]?.trim() ?? line.match(/'([^']+)'/)?.[1]?.trim() ?? "";
|
|
8132
|
-
const navigateMatch = line.match(
|
|
8133
|
-
/\b(?:navigate|open|go)\b(?:\s+(?:to|the url))?\s+(https?:\/\/[^\s)]+)\.?/i
|
|
8134
|
-
);
|
|
8135
|
-
if (navigateMatch?.[1]) {
|
|
8136
|
-
const argsJson = JSON.stringify({ url: navigateMatch[1].replace(/\.$/, "") });
|
|
8137
|
-
recovered.push({
|
|
8138
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8139
|
-
name: resolveToolCallName("navigate", { url: navigateMatch[1] }, availableToolNames),
|
|
8140
|
-
argsJson
|
|
8141
|
-
});
|
|
8142
|
-
continue;
|
|
8143
|
-
}
|
|
8144
|
-
const isSearchAction = /\bsearch\b/i.test(line) || /\btype\b/i.test(line) && /\bsearch box\b/i.test(line);
|
|
8145
|
-
if (isSearchAction && quotedValue && availableToolNames.has("search")) {
|
|
8146
|
-
recovered.push({
|
|
8147
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8148
|
-
name: "search",
|
|
8149
|
-
argsJson: JSON.stringify({ query: quotedValue })
|
|
8150
|
-
});
|
|
8151
|
-
continue;
|
|
8152
|
-
}
|
|
8153
|
-
if (/\b(?:read|scan)\b.*\bpage\b/i.test(line) && availableToolNames.has("read_page")) {
|
|
8154
|
-
recovered.push({
|
|
8155
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8156
|
-
name: "read_page",
|
|
8157
|
-
argsJson: JSON.stringify({ mode: "visible_only" })
|
|
8158
|
-
});
|
|
8159
|
-
continue;
|
|
8160
|
-
}
|
|
8161
|
-
const toolRefMatch = line.match(
|
|
8162
|
-
/\b(?:use|call)\s+([a-z_][a-z0-9_]*)(?:\s+tool)?\b/i
|
|
8163
|
-
);
|
|
8164
|
-
if (toolRefMatch?.[1]) {
|
|
8165
|
-
const toolName = resolveToolCallName(toolRefMatch[1], {}, availableToolNames);
|
|
8166
|
-
recovered.push({
|
|
8167
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8168
|
-
name: toolName,
|
|
8169
|
-
argsJson: "{}"
|
|
8170
|
-
});
|
|
8171
|
-
}
|
|
8227
|
+
function shouldRecoverCompactStall(text, userMessage) {
|
|
8228
|
+
const trimmed = text.trim().toLowerCase();
|
|
8229
|
+
if (!trimmed) return true;
|
|
8230
|
+
if (trimmed.length <= 160 && trimmed.includes("?")) return true;
|
|
8231
|
+
if (userMessage && buildPhaseReminder(userMessage, text)) {
|
|
8232
|
+
return true;
|
|
8172
8233
|
}
|
|
8173
|
-
const
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8234
|
+
const repetitivePlanningSignals = [
|
|
8235
|
+
"next step:",
|
|
8236
|
+
"i will now inspect",
|
|
8237
|
+
"i will now read",
|
|
8238
|
+
"i will now click",
|
|
8239
|
+
"i'll use readpage",
|
|
8240
|
+
"i'll use read_page",
|
|
8241
|
+
"i'll start by clicking",
|
|
8242
|
+
"i have clicked on five different book titles",
|
|
8243
|
+
"clicked on five different book titles",
|
|
8244
|
+
"i'll begin with",
|
|
8245
|
+
"if the selection is unclear"
|
|
8246
|
+
];
|
|
8247
|
+
if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
8248
|
+
return true;
|
|
8249
|
+
}
|
|
8250
|
+
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(
|
|
8251
|
+
trimmed
|
|
8252
|
+
) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
|
|
8253
|
+
trimmed
|
|
8177
8254
|
);
|
|
8178
|
-
if (
|
|
8179
|
-
|
|
8180
|
-
const normalizedMode = rawMode === "visibleonly" ? "visible_only" : rawMode === "resultsonly" ? "results_only" : rawMode;
|
|
8181
|
-
if (normalizedMode) {
|
|
8182
|
-
recovered.push({
|
|
8183
|
-
id: `recovered_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
8184
|
-
name: "read_page",
|
|
8185
|
-
argsJson: JSON.stringify({ mode: normalizedMode })
|
|
8186
|
-
});
|
|
8187
|
-
return recovered;
|
|
8188
|
-
}
|
|
8255
|
+
if (falseCartSuccessWithoutConfirmation) {
|
|
8256
|
+
return true;
|
|
8189
8257
|
}
|
|
8190
|
-
const
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8258
|
+
const completionSignals = [
|
|
8259
|
+
"i found",
|
|
8260
|
+
"i chose",
|
|
8261
|
+
"i selected",
|
|
8262
|
+
"i added",
|
|
8263
|
+
"here are",
|
|
8264
|
+
"these are",
|
|
8265
|
+
"recommendations",
|
|
8266
|
+
"reasoning",
|
|
8267
|
+
"why i chose",
|
|
8268
|
+
"added them to the cart"
|
|
8269
|
+
];
|
|
8270
|
+
if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
|
|
8271
|
+
return false;
|
|
8272
|
+
}
|
|
8273
|
+
return [
|
|
8274
|
+
"what are you hoping",
|
|
8275
|
+
"what would you like",
|
|
8276
|
+
"how can i help",
|
|
8277
|
+
"let me know",
|
|
8278
|
+
"are you looking for",
|
|
8279
|
+
"just browsing",
|
|
8280
|
+
"i need to",
|
|
8281
|
+
"i will",
|
|
8282
|
+
"i'll",
|
|
8283
|
+
"since i cannot see",
|
|
8284
|
+
"since i can't see",
|
|
8285
|
+
"cannot see the current page",
|
|
8286
|
+
"scroll down to",
|
|
8287
|
+
"load more results",
|
|
8288
|
+
"as placeholders",
|
|
8289
|
+
"would you like me to proceed",
|
|
8290
|
+
"action:",
|
|
8291
|
+
"one moment",
|
|
8292
|
+
"i will now navigate",
|
|
8293
|
+
"navigating to ",
|
|
8294
|
+
"this will take me",
|
|
8295
|
+
"i will use the browser"
|
|
8296
|
+
].some((pattern) => trimmed.includes(pattern));
|
|
8297
|
+
}
|
|
8298
|
+
function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
|
|
8299
|
+
return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
|
|
8300
|
+
}
|
|
8301
|
+
function logAgentLoopDebug(payload) {
|
|
8302
|
+
if (!shouldDebugAgentLoop()) return;
|
|
8303
|
+
try {
|
|
8304
|
+
logger$l.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
8305
|
+
} catch (err) {
|
|
8306
|
+
logger$l.warn("Failed to serialize debug payload:", err);
|
|
8200
8307
|
}
|
|
8201
|
-
return recovered;
|
|
8202
8308
|
}
|
|
8203
8309
|
function formatOpenAICompatErrorMessage(providerId, message) {
|
|
8204
8310
|
if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
|
|
@@ -8631,28 +8737,184 @@ async function openExternalAllowlisted(url, rule) {
|
|
|
8631
8737
|
}
|
|
8632
8738
|
await electron.shell.openExternal(parsed.toString());
|
|
8633
8739
|
}
|
|
8634
|
-
const logger$i = createLogger("CodexOAuth");
|
|
8635
|
-
const ISSUER = "https://auth.openai.com";
|
|
8636
|
-
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8637
|
-
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
8638
|
-
const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8639
|
-
const PREFERRED_PORT = 1455;
|
|
8640
|
-
const FALLBACK_PORT = 1457;
|
|
8641
|
-
let activeFlow = null;
|
|
8642
8740
|
function base64url(buffer) {
|
|
8643
8741
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8644
8742
|
}
|
|
8645
8743
|
function generatePkce() {
|
|
8646
8744
|
const codeVerifier = base64url(crypto$1.randomBytes(64));
|
|
8647
8745
|
const hash = crypto$1.createHash("sha256").update(codeVerifier).digest();
|
|
8648
|
-
|
|
8649
|
-
|
|
8746
|
+
return {
|
|
8747
|
+
codeVerifier,
|
|
8748
|
+
codeChallenge: base64url(hash)
|
|
8749
|
+
};
|
|
8650
8750
|
}
|
|
8651
8751
|
function generateState() {
|
|
8652
8752
|
return base64url(crypto$1.randomBytes(32));
|
|
8653
8753
|
}
|
|
8654
|
-
function
|
|
8655
|
-
|
|
8754
|
+
function buildCallbackUrl(port, path2) {
|
|
8755
|
+
return `http://localhost:${port}${path2}`;
|
|
8756
|
+
}
|
|
8757
|
+
async function bindServer(server, preferredPorts) {
|
|
8758
|
+
for (const port of preferredPorts) {
|
|
8759
|
+
try {
|
|
8760
|
+
await new Promise((resolve, reject) => {
|
|
8761
|
+
const onError = (err) => {
|
|
8762
|
+
server.off("listening", onListening);
|
|
8763
|
+
reject(err);
|
|
8764
|
+
};
|
|
8765
|
+
const onListening = () => {
|
|
8766
|
+
server.off("error", onError);
|
|
8767
|
+
resolve();
|
|
8768
|
+
};
|
|
8769
|
+
server.once("error", onError);
|
|
8770
|
+
server.once("listening", onListening);
|
|
8771
|
+
server.listen(port, "127.0.0.1");
|
|
8772
|
+
});
|
|
8773
|
+
return port;
|
|
8774
|
+
} catch (err) {
|
|
8775
|
+
if (err.code === "EADDRINUSE") {
|
|
8776
|
+
continue;
|
|
8777
|
+
}
|
|
8778
|
+
throw err;
|
|
8779
|
+
}
|
|
8780
|
+
}
|
|
8781
|
+
throw new Error(
|
|
8782
|
+
`Could not bind ${preferredPorts.join(", ")} callback ports`
|
|
8783
|
+
);
|
|
8784
|
+
}
|
|
8785
|
+
function createLocalPkceOAuthFlow(config) {
|
|
8786
|
+
let activeFlow = null;
|
|
8787
|
+
const cancel = () => {
|
|
8788
|
+
if (!activeFlow) return;
|
|
8789
|
+
activeFlow.server.close();
|
|
8790
|
+
clearTimeout(activeFlow.timeout);
|
|
8791
|
+
try {
|
|
8792
|
+
activeFlow.onStatus("idle");
|
|
8793
|
+
} catch {
|
|
8794
|
+
config.logger.warn(`${config.name} OAuth cancel status callback failed`);
|
|
8795
|
+
}
|
|
8796
|
+
activeFlow = null;
|
|
8797
|
+
};
|
|
8798
|
+
const start = (onStatus) => {
|
|
8799
|
+
if (activeFlow) {
|
|
8800
|
+
throw new Error(`${config.name} auth flow already in progress`);
|
|
8801
|
+
}
|
|
8802
|
+
const pkce = generatePkce();
|
|
8803
|
+
const state2 = generateState();
|
|
8804
|
+
const callbackPath = config.callbackPath(state2);
|
|
8805
|
+
return new Promise((resolve, reject) => {
|
|
8806
|
+
let settled = false;
|
|
8807
|
+
let boundPort = 0;
|
|
8808
|
+
const safeOnStatus = (status, error) => {
|
|
8809
|
+
try {
|
|
8810
|
+
onStatus(status, error);
|
|
8811
|
+
} catch {
|
|
8812
|
+
config.logger.warn(`${config.name} OAuth status callback failed`);
|
|
8813
|
+
}
|
|
8814
|
+
};
|
|
8815
|
+
const cleanup = () => {
|
|
8816
|
+
clearTimeout(activeFlow?.timeout);
|
|
8817
|
+
activeFlow?.server.close();
|
|
8818
|
+
activeFlow = null;
|
|
8819
|
+
};
|
|
8820
|
+
const wrappedResolve = (result) => {
|
|
8821
|
+
if (settled) return;
|
|
8822
|
+
settled = true;
|
|
8823
|
+
cleanup();
|
|
8824
|
+
safeOnStatus("connected");
|
|
8825
|
+
resolve(result);
|
|
8826
|
+
};
|
|
8827
|
+
const wrappedReject = (err) => {
|
|
8828
|
+
if (settled) return;
|
|
8829
|
+
settled = true;
|
|
8830
|
+
cleanup();
|
|
8831
|
+
safeOnStatus("error", err.message);
|
|
8832
|
+
reject(err);
|
|
8833
|
+
};
|
|
8834
|
+
const server = http.createServer(async (req, res) => {
|
|
8835
|
+
const url = new URL(req.url || "/", `http://localhost:${boundPort}`);
|
|
8836
|
+
if (url.pathname === callbackPath) {
|
|
8837
|
+
const authError = config.authErrorMessage?.(url) || url.searchParams.get("error");
|
|
8838
|
+
if (authError) {
|
|
8839
|
+
res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
|
|
8840
|
+
res.end(`Authorization failed: ${authError}`);
|
|
8841
|
+
wrappedReject(new Error(authError));
|
|
8842
|
+
return;
|
|
8843
|
+
}
|
|
8844
|
+
if (config.readState(url) !== state2) {
|
|
8845
|
+
res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
|
|
8846
|
+
res.end("State mismatch. Please try again.");
|
|
8847
|
+
wrappedReject(new Error("State mismatch"));
|
|
8848
|
+
return;
|
|
8849
|
+
}
|
|
8850
|
+
const code = url.searchParams.get("code");
|
|
8851
|
+
if (!code) {
|
|
8852
|
+
res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
|
|
8853
|
+
res.end("Missing authorization code.");
|
|
8854
|
+
wrappedReject(new Error("Missing authorization code"));
|
|
8855
|
+
return;
|
|
8856
|
+
}
|
|
8857
|
+
try {
|
|
8858
|
+
safeOnStatus("exchanging");
|
|
8859
|
+
const result = await config.exchangeCode({
|
|
8860
|
+
code,
|
|
8861
|
+
codeVerifier: pkce.codeVerifier,
|
|
8862
|
+
callbackUrl: buildCallbackUrl(boundPort, callbackPath),
|
|
8863
|
+
port: boundPort
|
|
8864
|
+
});
|
|
8865
|
+
res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
|
|
8866
|
+
res.end(config.successHtml(result));
|
|
8867
|
+
wrappedResolve(result);
|
|
8868
|
+
} catch (err) {
|
|
8869
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
8870
|
+
res.writeHead(400, { "Content-Type": "text/plain", Connection: "close" });
|
|
8871
|
+
res.end(`${config.name} setup failed: ${message}`);
|
|
8872
|
+
wrappedReject(err instanceof Error ? err : new Error(`${config.name} setup failed`));
|
|
8873
|
+
}
|
|
8874
|
+
return;
|
|
8875
|
+
}
|
|
8876
|
+
res.writeHead(404, { Connection: "close" });
|
|
8877
|
+
res.end("Not found");
|
|
8878
|
+
});
|
|
8879
|
+
const timeout = setTimeout(() => {
|
|
8880
|
+
wrappedReject(new Error(`${config.name} setup timed out after 5 minutes`));
|
|
8881
|
+
}, config.timeoutMs);
|
|
8882
|
+
activeFlow = {
|
|
8883
|
+
server,
|
|
8884
|
+
timeout,
|
|
8885
|
+
onStatus
|
|
8886
|
+
};
|
|
8887
|
+
bindServer(server, config.preferredPorts).then((port) => {
|
|
8888
|
+
if (settled || !activeFlow) return;
|
|
8889
|
+
boundPort = port;
|
|
8890
|
+
const callbackUrl = buildCallbackUrl(port, callbackPath);
|
|
8891
|
+
const authUrl = config.buildAuthorizeUrl({
|
|
8892
|
+
port,
|
|
8893
|
+
pkce,
|
|
8894
|
+
state: state2,
|
|
8895
|
+
callbackUrl
|
|
8896
|
+
});
|
|
8897
|
+
safeOnStatus("waiting");
|
|
8898
|
+
openExternalAllowlisted(authUrl, { hosts: [...config.openHosts] }).catch((err) => {
|
|
8899
|
+
config.logger.warn(`Failed to open ${config.name} auth URL:`, err);
|
|
8900
|
+
});
|
|
8901
|
+
}).catch(wrappedReject);
|
|
8902
|
+
});
|
|
8903
|
+
};
|
|
8904
|
+
return {
|
|
8905
|
+
start,
|
|
8906
|
+
cancel,
|
|
8907
|
+
isInProgress: () => activeFlow !== null
|
|
8908
|
+
};
|
|
8909
|
+
}
|
|
8910
|
+
const logger$k = createLogger("CodexOAuth");
|
|
8911
|
+
const ISSUER = "https://auth.openai.com";
|
|
8912
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8913
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
8914
|
+
const AUTH_TIMEOUT_MS$1 = 5 * 60 * 1e3;
|
|
8915
|
+
const PREFERRED_PORT$1 = 1455;
|
|
8916
|
+
const FALLBACK_PORT$1 = 1457;
|
|
8917
|
+
function buildAuthorizeUrl(redirectUri, pkce, state2) {
|
|
8656
8918
|
const params = new URLSearchParams({
|
|
8657
8919
|
response_type: "code",
|
|
8658
8920
|
client_id: CLIENT_ID,
|
|
@@ -8771,172 +9033,36 @@ async function refreshAccessToken(tokens) {
|
|
|
8771
9033
|
};
|
|
8772
9034
|
return refreshedTokens;
|
|
8773
9035
|
}
|
|
8774
|
-
function startServer(port, pkce, expectedState, resolve, reject) {
|
|
8775
|
-
const server = http.createServer(async (req, res) => {
|
|
8776
|
-
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
8777
|
-
if (url.pathname === "/auth/callback") {
|
|
8778
|
-
const state2 = url.searchParams.get("state");
|
|
8779
|
-
const code = url.searchParams.get("code");
|
|
8780
|
-
const error = url.searchParams.get("error");
|
|
8781
|
-
const errorDescription = url.searchParams.get("error_description");
|
|
8782
|
-
if (error) {
|
|
8783
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8784
|
-
const msg = errorDescription || error;
|
|
8785
|
-
res.end(`Authorization failed: ${msg}`);
|
|
8786
|
-
reject(new Error(msg));
|
|
8787
|
-
return;
|
|
8788
|
-
}
|
|
8789
|
-
if (state2 !== expectedState) {
|
|
8790
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8791
|
-
res.end("State mismatch. Please try again.");
|
|
8792
|
-
reject(new Error("State mismatch"));
|
|
8793
|
-
return;
|
|
8794
|
-
}
|
|
8795
|
-
if (!code) {
|
|
8796
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8797
|
-
res.end("Missing authorization code.");
|
|
8798
|
-
reject(new Error("Missing authorization code"));
|
|
8799
|
-
return;
|
|
8800
|
-
}
|
|
8801
|
-
try {
|
|
8802
|
-
activeFlow?.onStatus("exchanging");
|
|
8803
|
-
const redirectUri = `http://localhost:${activeFlow?.port ?? port}/auth/callback`;
|
|
8804
|
-
const tokens = await exchangeCodeForTokens(code, redirectUri, pkce.codeVerifier);
|
|
8805
|
-
res.writeHead(302, {
|
|
8806
|
-
Location: `/success?email=${encodeURIComponent(tokens.accountEmail || tokens.accountId)}`,
|
|
8807
|
-
Connection: "close"
|
|
8808
|
-
});
|
|
8809
|
-
res.end();
|
|
8810
|
-
resolve(tokens);
|
|
8811
|
-
} catch (err) {
|
|
8812
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8813
|
-
res.end(`Token exchange failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
8814
|
-
reject(err instanceof Error ? err : new Error("Token exchange failed"));
|
|
8815
|
-
}
|
|
8816
|
-
return;
|
|
8817
|
-
}
|
|
8818
|
-
if (url.pathname === "/success") {
|
|
8819
|
-
const email = url.searchParams.get("email") || "";
|
|
8820
|
-
res.writeHead(200, { "Content-Type": "text/html", "Connection": "close" });
|
|
8821
|
-
res.end(`<!DOCTYPE html>
|
|
8822
|
-
<html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
|
|
8823
|
-
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee}</style></head>
|
|
8824
|
-
<body><div style="text-align:center"><h1>✓ Signed In</h1>
|
|
8825
|
-
<p>Connected as ${escapeHtml(email)}</p><p>You can close this tab.</p></div></body></html>`);
|
|
8826
|
-
return;
|
|
8827
|
-
}
|
|
8828
|
-
if (url.pathname === "/cancel") {
|
|
8829
|
-
res.writeHead(200, { "Content-Type": "text/plain", "Connection": "close" });
|
|
8830
|
-
res.end("Login cancelled");
|
|
8831
|
-
reject(new Error("Login cancelled by user"));
|
|
8832
|
-
return;
|
|
8833
|
-
}
|
|
8834
|
-
res.writeHead(404, { "Connection": "close" });
|
|
8835
|
-
res.end("Not found");
|
|
8836
|
-
});
|
|
8837
|
-
return server;
|
|
8838
|
-
}
|
|
8839
9036
|
function escapeHtml(text) {
|
|
8840
9037
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8841
9038
|
}
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
continue;
|
|
8863
|
-
}
|
|
8864
|
-
throw err;
|
|
8865
|
-
}
|
|
8866
|
-
}
|
|
8867
|
-
throw new Error(
|
|
8868
|
-
`Could not bind Codex OAuth callback server to registered ports ${allowedPorts.join(", ")}`
|
|
8869
|
-
);
|
|
8870
|
-
}
|
|
9039
|
+
const codexOAuth = createLocalPkceOAuthFlow({
|
|
9040
|
+
name: "Codex",
|
|
9041
|
+
logger: logger$k,
|
|
9042
|
+
preferredPorts: [PREFERRED_PORT$1, FALLBACK_PORT$1],
|
|
9043
|
+
timeoutMs: AUTH_TIMEOUT_MS$1,
|
|
9044
|
+
callbackPath: () => "/auth/callback",
|
|
9045
|
+
readState: (url) => url.searchParams.get("state"),
|
|
9046
|
+
authErrorMessage: (url) => url.searchParams.get("error_description") || url.searchParams.get("error"),
|
|
9047
|
+
buildAuthorizeUrl: ({ callbackUrl, pkce, state: state2 }) => buildAuthorizeUrl(callbackUrl, pkce, state2),
|
|
9048
|
+
exchangeCode: ({ code, callbackUrl, codeVerifier }) => exchangeCodeForTokens(code, callbackUrl, codeVerifier),
|
|
9049
|
+
successHtml: (tokens) => {
|
|
9050
|
+
const label = escapeHtml(tokens.accountEmail || tokens.accountId);
|
|
9051
|
+
return `<!DOCTYPE html>
|
|
9052
|
+
<html><head><meta charset="utf-8"><title>Vessel — Signed In</title>
|
|
9053
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee}</style></head>
|
|
9054
|
+
<body><div style="text-align:center"><h1>Signed In</h1>
|
|
9055
|
+
<p>Connected as ${label}</p><p>You can close this tab.</p></div></body></html>`;
|
|
9056
|
+
},
|
|
9057
|
+
openHosts: ["auth.openai.com"]
|
|
9058
|
+
});
|
|
8871
9059
|
async function startCodexOAuth(onStatus) {
|
|
8872
|
-
|
|
8873
|
-
throw new Error("Auth flow already in progress");
|
|
8874
|
-
}
|
|
8875
|
-
const pkce = generatePkce();
|
|
8876
|
-
const state2 = generateState();
|
|
8877
|
-
return new Promise((resolve, reject) => {
|
|
8878
|
-
let settled = false;
|
|
8879
|
-
const safeOnStatus = (status, error) => {
|
|
8880
|
-
try {
|
|
8881
|
-
onStatus(status, error);
|
|
8882
|
-
} catch {
|
|
8883
|
-
logger$i.warn("Codex OAuth status callback failed — window may be closed");
|
|
8884
|
-
}
|
|
8885
|
-
};
|
|
8886
|
-
const wrappedResolve = (tokens) => {
|
|
8887
|
-
if (settled) return;
|
|
8888
|
-
settled = true;
|
|
8889
|
-
cleanup();
|
|
8890
|
-
safeOnStatus("connected");
|
|
8891
|
-
resolve(tokens);
|
|
8892
|
-
};
|
|
8893
|
-
const wrappedReject = (err) => {
|
|
8894
|
-
if (settled) return;
|
|
8895
|
-
settled = true;
|
|
8896
|
-
cleanup();
|
|
8897
|
-
safeOnStatus("error", err.message);
|
|
8898
|
-
reject(err);
|
|
8899
|
-
};
|
|
8900
|
-
const server = startServer(0, pkce, state2, wrappedResolve, wrappedReject);
|
|
8901
|
-
const timeout = setTimeout(() => {
|
|
8902
|
-
wrappedReject(new Error("Auth flow timed out after 5 minutes"));
|
|
8903
|
-
}, AUTH_TIMEOUT_MS);
|
|
8904
|
-
activeFlow = {
|
|
8905
|
-
state: state2,
|
|
8906
|
-
codeVerifier: pkce.codeVerifier,
|
|
8907
|
-
port: 0,
|
|
8908
|
-
server,
|
|
8909
|
-
timeout,
|
|
8910
|
-
onStatus
|
|
8911
|
-
};
|
|
8912
|
-
const cleanup = () => {
|
|
8913
|
-
if (activeFlow?.timeout) clearTimeout(activeFlow.timeout);
|
|
8914
|
-
activeFlow?.server.close();
|
|
8915
|
-
activeFlow = null;
|
|
8916
|
-
};
|
|
8917
|
-
bindServer(server).then((port) => {
|
|
8918
|
-
if (settled) return;
|
|
8919
|
-
activeFlow.port = port;
|
|
8920
|
-
const authUrl = buildAuthorizeUrl(port, pkce, state2);
|
|
8921
|
-
safeOnStatus("waiting");
|
|
8922
|
-
openExternalAllowlisted(authUrl, { hosts: ["auth.openai.com"] }).catch((err) => {
|
|
8923
|
-
logger$i.warn("Failed to open browser, user will need the URL:", err);
|
|
8924
|
-
});
|
|
8925
|
-
}).catch(wrappedReject);
|
|
8926
|
-
});
|
|
9060
|
+
return codexOAuth.start(onStatus);
|
|
8927
9061
|
}
|
|
8928
9062
|
function cancelCodexOAuth() {
|
|
8929
|
-
|
|
8930
|
-
activeFlow.server.close();
|
|
8931
|
-
if (activeFlow.timeout) clearTimeout(activeFlow.timeout);
|
|
8932
|
-
try {
|
|
8933
|
-
activeFlow.onStatus("idle");
|
|
8934
|
-
} catch {
|
|
8935
|
-
logger$i.warn("Codex OAuth cancel status callback failed — window may be closed");
|
|
8936
|
-
}
|
|
8937
|
-
activeFlow = null;
|
|
9063
|
+
codexOAuth.cancel();
|
|
8938
9064
|
}
|
|
8939
|
-
const logger$
|
|
9065
|
+
const logger$j = createLogger("CodexProvider");
|
|
8940
9066
|
const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
|
|
8941
9067
|
const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
8942
9068
|
const CODEX_CLIENT_VERSION = "0.129.0";
|
|
@@ -9001,7 +9127,7 @@ class CodexProvider {
|
|
|
9001
9127
|
async ensureFreshTokens() {
|
|
9002
9128
|
if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
|
|
9003
9129
|
try {
|
|
9004
|
-
logger$
|
|
9130
|
+
logger$j.info("Refreshing Codex access token");
|
|
9005
9131
|
const fresh = await refreshAccessToken(this.tokens);
|
|
9006
9132
|
this.tokens = fresh;
|
|
9007
9133
|
writeStoredCodexTokens(fresh);
|
|
@@ -9149,7 +9275,7 @@ class CodexProvider {
|
|
|
9149
9275
|
} catch (err) {
|
|
9150
9276
|
if (err.name !== "AbortError") {
|
|
9151
9277
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9152
|
-
logger$
|
|
9278
|
+
logger$j.error("Codex streamQuery error:", err);
|
|
9153
9279
|
onChunk(`
|
|
9154
9280
|
|
|
9155
9281
|
[Error: ${msg}]`);
|
|
@@ -9217,7 +9343,7 @@ class CodexProvider {
|
|
|
9217
9343
|
} catch (err) {
|
|
9218
9344
|
if (err.name !== "AbortError") {
|
|
9219
9345
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9220
|
-
logger$
|
|
9346
|
+
logger$j.error("Codex streamAgentQuery error:", err);
|
|
9221
9347
|
onChunk(`
|
|
9222
9348
|
|
|
9223
9349
|
[Error: ${msg}]`);
|
|
@@ -9409,7 +9535,7 @@ function createProvider(config) {
|
|
|
9409
9535
|
return new OpenAICompatProvider(normalized);
|
|
9410
9536
|
}
|
|
9411
9537
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
9412
|
-
const logger$
|
|
9538
|
+
const logger$i = createLogger("DevTrace");
|
|
9413
9539
|
let cachedFactory;
|
|
9414
9540
|
function createNoopTraceSession() {
|
|
9415
9541
|
return {
|
|
@@ -9442,7 +9568,7 @@ function loadLocalFactory() {
|
|
|
9442
9568
|
return cachedFactory;
|
|
9443
9569
|
}
|
|
9444
9570
|
} catch (err) {
|
|
9445
|
-
logger$
|
|
9571
|
+
logger$i.warn("Failed to load local trace logger:", err);
|
|
9446
9572
|
}
|
|
9447
9573
|
}
|
|
9448
9574
|
return cachedFactory;
|
|
@@ -12665,6 +12791,8 @@ const UNSORTED_ID = "unsorted";
|
|
|
12665
12791
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
12666
12792
|
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
12667
12793
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
12794
|
+
const DEFAULT_BOOKMARK_SEARCH_LIMIT = 50;
|
|
12795
|
+
const MAX_BOOKMARK_SEARCH_LIMIT = 200;
|
|
12668
12796
|
let state$2 = null;
|
|
12669
12797
|
const listeners = /* @__PURE__ */ new Set();
|
|
12670
12798
|
function cloneState(current) {
|
|
@@ -12673,6 +12801,10 @@ function cloneState(current) {
|
|
|
12673
12801
|
bookmarks: current.bookmarks.map((bookmark) => ({ ...bookmark }))
|
|
12674
12802
|
};
|
|
12675
12803
|
}
|
|
12804
|
+
function getFolderMap() {
|
|
12805
|
+
load$1();
|
|
12806
|
+
return new Map(state$2.folders.map((folder) => [folder.id, folder]));
|
|
12807
|
+
}
|
|
12676
12808
|
function getBookmarksPath() {
|
|
12677
12809
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
12678
12810
|
}
|
|
@@ -12899,13 +13031,16 @@ function listFolderOverviews() {
|
|
|
12899
13031
|
}))
|
|
12900
13032
|
];
|
|
12901
13033
|
}
|
|
12902
|
-
function searchBookmarks(query) {
|
|
13034
|
+
function searchBookmarks(query, limit = DEFAULT_BOOKMARK_SEARCH_LIMIT) {
|
|
12903
13035
|
load$1();
|
|
12904
13036
|
if (!query.trim()) return [];
|
|
13037
|
+
const foldersById = getFolderMap();
|
|
13038
|
+
const safeLimit = Math.max(
|
|
13039
|
+
1,
|
|
13040
|
+
Math.min(MAX_BOOKMARK_SEARCH_LIMIT, Math.floor(limit))
|
|
13041
|
+
);
|
|
12905
13042
|
return state$2.bookmarks.map((bookmark) => {
|
|
12906
|
-
const folder =
|
|
12907
|
-
(item) => item.id === bookmark.folderId
|
|
12908
|
-
);
|
|
13043
|
+
const folder = foldersById.get(bookmark.folderId);
|
|
12909
13044
|
const { matchedFields, score } = getBookmarkSearchMatch({
|
|
12910
13045
|
query,
|
|
12911
13046
|
title: bookmark.title,
|
|
@@ -12929,7 +13064,7 @@ function searchBookmarks(query) {
|
|
|
12929
13064
|
score: result.score
|
|
12930
13065
|
})).sort(
|
|
12931
13066
|
(a, b) => b.score - a.score || b.bookmark.savedAt.localeCompare(a.bookmark.savedAt)
|
|
12932
|
-
);
|
|
13067
|
+
).slice(0, safeLimit);
|
|
12933
13068
|
}
|
|
12934
13069
|
function createFolderWithSummary(name, summary) {
|
|
12935
13070
|
load$1();
|
|
@@ -13561,7 +13696,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
13561
13696
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
13562
13697
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
13563
13698
|
}
|
|
13564
|
-
const logger$
|
|
13699
|
+
const logger$h = createLogger("Screenshot");
|
|
13565
13700
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
13566
13701
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
13567
13702
|
async function captureScreenshot(wc) {
|
|
@@ -13583,7 +13718,7 @@ async function captureScreenshot(wc) {
|
|
|
13583
13718
|
}
|
|
13584
13719
|
}
|
|
13585
13720
|
} catch (err) {
|
|
13586
|
-
logger$
|
|
13721
|
+
logger$h.debug(
|
|
13587
13722
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
13588
13723
|
getErrorMessage(err)
|
|
13589
13724
|
);
|
|
@@ -14177,24 +14312,115 @@ function compactCurrentTabResult(text) {
|
|
|
14177
14312
|
function looksLikeRichToolResult(text) {
|
|
14178
14313
|
return text.startsWith("{") && text.includes('"__richResult":true');
|
|
14179
14314
|
}
|
|
14180
|
-
function formatCompactToolResult(name, result) {
|
|
14181
|
-
if (!result || looksLikeRichToolResult(result)) return result;
|
|
14182
|
-
switch (name) {
|
|
14183
|
-
case "current_tab":
|
|
14184
|
-
return compactCurrentTabResult(result);
|
|
14185
|
-
case "read_page":
|
|
14186
|
-
return compactReadPageResult(result);
|
|
14187
|
-
case "search":
|
|
14188
|
-
case "navigate":
|
|
14189
|
-
case "go_back":
|
|
14190
|
-
case "go_forward":
|
|
14191
|
-
case "paginate":
|
|
14192
|
-
case "wait_for_navigation":
|
|
14193
|
-
return compactSearchLikeResult(result);
|
|
14194
|
-
case "list_tabs":
|
|
14195
|
-
return limitText(result, 10, 900);
|
|
14196
|
-
default:
|
|
14197
|
-
return limitText(result, 18, 1400);
|
|
14315
|
+
function formatCompactToolResult(name, result) {
|
|
14316
|
+
if (!result || looksLikeRichToolResult(result)) return result;
|
|
14317
|
+
switch (name) {
|
|
14318
|
+
case "current_tab":
|
|
14319
|
+
return compactCurrentTabResult(result);
|
|
14320
|
+
case "read_page":
|
|
14321
|
+
return compactReadPageResult(result);
|
|
14322
|
+
case "search":
|
|
14323
|
+
case "navigate":
|
|
14324
|
+
case "go_back":
|
|
14325
|
+
case "go_forward":
|
|
14326
|
+
case "paginate":
|
|
14327
|
+
case "wait_for_navigation":
|
|
14328
|
+
return compactSearchLikeResult(result);
|
|
14329
|
+
case "list_tabs":
|
|
14330
|
+
return limitText(result, 10, 900);
|
|
14331
|
+
default:
|
|
14332
|
+
return limitText(result, 18, 1400);
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
const ADD_TO_CART_PATTERNS = [
|
|
14336
|
+
"add to cart",
|
|
14337
|
+
"add to bag",
|
|
14338
|
+
"add to basket",
|
|
14339
|
+
"add to my cart",
|
|
14340
|
+
"add to my bag",
|
|
14341
|
+
"add to my basket",
|
|
14342
|
+
"add item to cart",
|
|
14343
|
+
"add item to bag",
|
|
14344
|
+
"add item to basket"
|
|
14345
|
+
];
|
|
14346
|
+
const CART_CLICK_COOLDOWN_MS = 15e3;
|
|
14347
|
+
const CART_ADDED_TTL_MS = 30 * 6e4;
|
|
14348
|
+
const recentCartClicks = /* @__PURE__ */ new Map();
|
|
14349
|
+
const cartAddedProducts = /* @__PURE__ */ new Map();
|
|
14350
|
+
function isAddToCartText(text) {
|
|
14351
|
+
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
14352
|
+
return ADD_TO_CART_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
14353
|
+
}
|
|
14354
|
+
function recordCartClick(url) {
|
|
14355
|
+
recentCartClicks.set(url, Date.now());
|
|
14356
|
+
pruneRecentCartClicks();
|
|
14357
|
+
}
|
|
14358
|
+
function hasRecentCartClick(url) {
|
|
14359
|
+
const recent = recentCartClicks.get(url);
|
|
14360
|
+
if (!recent) return false;
|
|
14361
|
+
if (Date.now() - recent > CART_CLICK_COOLDOWN_MS) {
|
|
14362
|
+
recentCartClicks.delete(url);
|
|
14363
|
+
return false;
|
|
14364
|
+
}
|
|
14365
|
+
return true;
|
|
14366
|
+
}
|
|
14367
|
+
function isDuplicateCartClick(url, text) {
|
|
14368
|
+
return hasRecentCartClick(url) && isAddToCartText(text);
|
|
14369
|
+
}
|
|
14370
|
+
function recordProductAddedToCart(url, productName) {
|
|
14371
|
+
pruneCartAddedProducts();
|
|
14372
|
+
cartAddedProducts.set(normalizeCartProductKey(url), {
|
|
14373
|
+
title: productName || url,
|
|
14374
|
+
ts: Date.now()
|
|
14375
|
+
});
|
|
14376
|
+
}
|
|
14377
|
+
function isProductAlreadyInCart(url) {
|
|
14378
|
+
pruneCartAddedProducts();
|
|
14379
|
+
return cartAddedProducts.has(normalizeCartProductKey(url));
|
|
14380
|
+
}
|
|
14381
|
+
function getCartAddedSummary(url) {
|
|
14382
|
+
pruneCartAddedProducts();
|
|
14383
|
+
const origin = cartOrigin(url);
|
|
14384
|
+
const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
|
|
14385
|
+
if (!items) return "";
|
|
14386
|
+
const count = items.split("\n").length;
|
|
14387
|
+
return `
|
|
14388
|
+
Already in cart (${count} items):
|
|
14389
|
+
${items}`;
|
|
14390
|
+
}
|
|
14391
|
+
function clearCartClickState() {
|
|
14392
|
+
cartAddedProducts.clear();
|
|
14393
|
+
recentCartClicks.clear();
|
|
14394
|
+
}
|
|
14395
|
+
function pruneRecentCartClicks(now = Date.now()) {
|
|
14396
|
+
for (const [key2, ts] of recentCartClicks) {
|
|
14397
|
+
if (now - ts > CART_CLICK_COOLDOWN_MS) {
|
|
14398
|
+
recentCartClicks.delete(key2);
|
|
14399
|
+
}
|
|
14400
|
+
}
|
|
14401
|
+
}
|
|
14402
|
+
function normalizeCartProductKey(url) {
|
|
14403
|
+
try {
|
|
14404
|
+
const parsed = new URL(url);
|
|
14405
|
+
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
14406
|
+
return `${parsed.origin}${pathname}`;
|
|
14407
|
+
} catch {
|
|
14408
|
+
return url;
|
|
14409
|
+
}
|
|
14410
|
+
}
|
|
14411
|
+
function pruneCartAddedProducts(now = Date.now()) {
|
|
14412
|
+
for (const [key2, entry] of cartAddedProducts) {
|
|
14413
|
+
if (now - entry.ts > CART_ADDED_TTL_MS) {
|
|
14414
|
+
cartAddedProducts.delete(key2);
|
|
14415
|
+
}
|
|
14416
|
+
}
|
|
14417
|
+
}
|
|
14418
|
+
function cartOrigin(url) {
|
|
14419
|
+
if (!url) return null;
|
|
14420
|
+
try {
|
|
14421
|
+
return new URL(url).origin;
|
|
14422
|
+
} catch {
|
|
14423
|
+
return null;
|
|
14198
14424
|
}
|
|
14199
14425
|
}
|
|
14200
14426
|
const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
|
|
@@ -14460,12 +14686,15 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
14460
14686
|
class TabMutex {
|
|
14461
14687
|
queue = Promise.resolve();
|
|
14462
14688
|
enqueue(fn) {
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14689
|
+
const run = this.queue.then(fn, fn);
|
|
14690
|
+
this.queue = run.then(
|
|
14691
|
+
() => void 0,
|
|
14692
|
+
() => void 0
|
|
14693
|
+
);
|
|
14694
|
+
return run;
|
|
14466
14695
|
}
|
|
14467
14696
|
}
|
|
14468
|
-
const logger$
|
|
14697
|
+
const logger$g = createLogger("PageActions");
|
|
14469
14698
|
function getBookmarkMetadataFromArgs(args) {
|
|
14470
14699
|
return normalizeBookmarkMetadata({
|
|
14471
14700
|
intent: args.intent ?? args.intent,
|
|
@@ -14651,7 +14880,7 @@ async function executePageScript(wc, script, options) {
|
|
|
14651
14880
|
return result;
|
|
14652
14881
|
} catch (err) {
|
|
14653
14882
|
const label = options?.label ? ` (${options.label})` : "";
|
|
14654
|
-
logger$
|
|
14883
|
+
logger$g.warn(`Failed to execute page script${label}:`, err);
|
|
14655
14884
|
return null;
|
|
14656
14885
|
} finally {
|
|
14657
14886
|
if (timer) {
|
|
@@ -14752,7 +14981,7 @@ Search results snapshot:
|
|
|
14752
14981
|
${truncated}`;
|
|
14753
14982
|
}
|
|
14754
14983
|
} catch (err) {
|
|
14755
|
-
logger$
|
|
14984
|
+
logger$g.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
14756
14985
|
}
|
|
14757
14986
|
const fallback = await getPostNavSummary(wc);
|
|
14758
14987
|
return fallback ? `${fallback}
|
|
@@ -14775,7 +15004,7 @@ Page snapshot after navigation:
|
|
|
14775
15004
|
${truncated}`;
|
|
14776
15005
|
}
|
|
14777
15006
|
} catch (err) {
|
|
14778
|
-
logger$
|
|
15007
|
+
logger$g.warn("Failed to build post-click navigation summary:", err);
|
|
14779
15008
|
}
|
|
14780
15009
|
return "";
|
|
14781
15010
|
}
|
|
@@ -15269,7 +15498,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15269
15498
|
}
|
|
15270
15499
|
}
|
|
15271
15500
|
} catch (err) {
|
|
15272
|
-
logger$
|
|
15501
|
+
logger$g.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
15273
15502
|
}
|
|
15274
15503
|
if (snapshot2.url && snapshot2.url !== wc.getURL()) {
|
|
15275
15504
|
try {
|
|
@@ -15278,7 +15507,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15278
15507
|
await waitForLoad(wc, 3e3);
|
|
15279
15508
|
return;
|
|
15280
15509
|
} catch (err) {
|
|
15281
|
-
logger$
|
|
15510
|
+
logger$g.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
15282
15511
|
}
|
|
15283
15512
|
}
|
|
15284
15513
|
if (snapshot2.url) {
|
|
@@ -15286,49 +15515,13 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15286
15515
|
await wc.reload();
|
|
15287
15516
|
await waitForLoad(wc, 3e3);
|
|
15288
15517
|
} catch (err) {
|
|
15289
|
-
logger$
|
|
15518
|
+
logger$g.warn("Failed to restore locale via page reload:", err);
|
|
15290
15519
|
}
|
|
15291
15520
|
}
|
|
15292
15521
|
}
|
|
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
15522
|
let clickStreakUrl = null;
|
|
15309
15523
|
let clickStreakCount = 0;
|
|
15310
15524
|
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
15525
|
async function getProductPageTitle(wc) {
|
|
15333
15526
|
try {
|
|
15334
15527
|
const heading = await executePageScript(
|
|
@@ -15353,54 +15546,8 @@ async function getProductPageTitle(wc) {
|
|
|
15353
15546
|
}
|
|
15354
15547
|
return wc.getTitle() || "";
|
|
15355
15548
|
}
|
|
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
15549
|
function clearCartState() {
|
|
15402
|
-
|
|
15403
|
-
recentCartClicks.clear();
|
|
15550
|
+
clearCartClickState();
|
|
15404
15551
|
clickStreakUrl = null;
|
|
15405
15552
|
clickStreakCount = 0;
|
|
15406
15553
|
}
|
|
@@ -15452,7 +15599,7 @@ Go back and select a different product.`;
|
|
|
15452
15599
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15453
15600
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15454
15601
|
if (idxCartMatch) {
|
|
15455
|
-
recordCartClick(beforeUrl2
|
|
15602
|
+
recordCartClick(beforeUrl2);
|
|
15456
15603
|
}
|
|
15457
15604
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15458
15605
|
const afterUrl2 = wc.getURL();
|
|
@@ -15519,7 +15666,7 @@ Go back and select a different product.`;
|
|
|
15519
15666
|
if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
|
|
15520
15667
|
if (typeof result === "string" && result.startsWith("Error")) return result;
|
|
15521
15668
|
if (shadowCartMatch) {
|
|
15522
|
-
recordCartClick(beforeUrl2
|
|
15669
|
+
recordCartClick(beforeUrl2);
|
|
15523
15670
|
}
|
|
15524
15671
|
await waitForPotentialNavigation(wc, beforeUrl2);
|
|
15525
15672
|
const afterUrl2 = wc.getURL();
|
|
@@ -15555,7 +15702,7 @@ Note: Page did not change after click.`;
|
|
|
15555
15702
|
if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
|
|
15556
15703
|
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
15704
|
}
|
|
15558
|
-
if (!cartMatch &&
|
|
15705
|
+
if (!cartMatch && hasRecentCartClick(beforeUrl)) {
|
|
15559
15706
|
const dialogActions = await getCartDialogActions(wc);
|
|
15560
15707
|
if (dialogActions) {
|
|
15561
15708
|
return `Blocked: a cart confirmation dialog is open. Do not click background elements.
|
|
@@ -15575,7 +15722,7 @@ Click one of these dialog actions instead.`;
|
|
|
15575
15722
|
Go back and select a different product.`;
|
|
15576
15723
|
}
|
|
15577
15724
|
if (cartMatch) {
|
|
15578
|
-
recordCartClick(beforeUrl
|
|
15725
|
+
recordCartClick(beforeUrl);
|
|
15579
15726
|
}
|
|
15580
15727
|
const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
|
|
15581
15728
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
@@ -15638,7 +15785,7 @@ ${postActivationOverlayHint}`;
|
|
|
15638
15785
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
15639
15786
|
}
|
|
15640
15787
|
} catch (err) {
|
|
15641
|
-
logger$
|
|
15788
|
+
logger$g.warn("Failed href fallback after click, returning generic click result:", err);
|
|
15642
15789
|
}
|
|
15643
15790
|
}
|
|
15644
15791
|
}
|
|
@@ -15683,7 +15830,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
15683
15830
|
return result;
|
|
15684
15831
|
}
|
|
15685
15832
|
} catch (err) {
|
|
15686
|
-
logger$
|
|
15833
|
+
logger$g.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
15687
15834
|
}
|
|
15688
15835
|
return null;
|
|
15689
15836
|
}
|
|
@@ -17966,7 +18113,7 @@ async function executeAction(name, args, ctx) {
|
|
|
17966
18113
|
)
|
|
17967
18114
|
]);
|
|
17968
18115
|
} catch (err) {
|
|
17969
|
-
logger$
|
|
18116
|
+
logger$g.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
17970
18117
|
content = null;
|
|
17971
18118
|
}
|
|
17972
18119
|
if (!content || content.content.length === 0) {
|
|
@@ -17983,12 +18130,12 @@ async function executeAction(name, args, ctx) {
|
|
|
17983
18130
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
17984
18131
|
]);
|
|
17985
18132
|
} catch (err) {
|
|
17986
|
-
logger$
|
|
18133
|
+
logger$g.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
17987
18134
|
content = null;
|
|
17988
18135
|
}
|
|
17989
18136
|
}
|
|
17990
18137
|
} catch (err) {
|
|
17991
|
-
logger$
|
|
18138
|
+
logger$g.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
17992
18139
|
}
|
|
17993
18140
|
}
|
|
17994
18141
|
if (content && content.content.length > 0) {
|
|
@@ -18401,7 +18548,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
18401
18548
|
try {
|
|
18402
18549
|
page = await extractContent(wc);
|
|
18403
18550
|
} catch (err) {
|
|
18404
|
-
logger$
|
|
18551
|
+
logger$g.warn("Failed to extract content for suggest:", err);
|
|
18405
18552
|
return "Could not read page. Try navigate to a working URL.";
|
|
18406
18553
|
}
|
|
18407
18554
|
const suggestions = [];
|
|
@@ -19009,6 +19156,57 @@ function onAIStreamIdle(listener) {
|
|
|
19009
19156
|
idleListeners.add(listener);
|
|
19010
19157
|
return () => idleListeners.delete(listener);
|
|
19011
19158
|
}
|
|
19159
|
+
const MAX_PROVIDER_HISTORY_MESSAGES = 24;
|
|
19160
|
+
const MAX_PROVIDER_HISTORY_CHARS = 24e3;
|
|
19161
|
+
const MAX_PROVIDER_HISTORY_MESSAGE_CHARS = 3e3;
|
|
19162
|
+
const MAX_PROVIDER_HISTORY_SUMMARY_CHARS = 2e3;
|
|
19163
|
+
function truncateText(value, maxLength) {
|
|
19164
|
+
if (value.length <= maxLength) return value;
|
|
19165
|
+
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
|
|
19166
|
+
}
|
|
19167
|
+
function normalizeHistoryMessage(message) {
|
|
19168
|
+
return {
|
|
19169
|
+
role: message.role,
|
|
19170
|
+
content: truncateText(message.content, MAX_PROVIDER_HISTORY_MESSAGE_CHARS)
|
|
19171
|
+
};
|
|
19172
|
+
}
|
|
19173
|
+
function totalHistoryChars(history) {
|
|
19174
|
+
return history.reduce((total, message) => total + message.content.length, 0);
|
|
19175
|
+
}
|
|
19176
|
+
function summarizeOmittedHistory(history) {
|
|
19177
|
+
const snippets = history.slice(-12).map((message) => `${message.role}: ${truncateText(message.content.replace(/\s+/g, " ").trim(), 220)}`).filter((line) => line.length > "assistant: ".length);
|
|
19178
|
+
const content = truncateText(
|
|
19179
|
+
[
|
|
19180
|
+
`[Earlier conversation compacted: ${history.length} message${history.length === 1 ? "" : "s"} omitted.]`,
|
|
19181
|
+
...snippets
|
|
19182
|
+
].join("\n"),
|
|
19183
|
+
MAX_PROVIDER_HISTORY_SUMMARY_CHARS
|
|
19184
|
+
);
|
|
19185
|
+
return { role: "user", content };
|
|
19186
|
+
}
|
|
19187
|
+
function compactProviderHistory(history = []) {
|
|
19188
|
+
const normalized = history.map(normalizeHistoryMessage);
|
|
19189
|
+
if (normalized.length <= MAX_PROVIDER_HISTORY_MESSAGES && totalHistoryChars(normalized) <= MAX_PROVIDER_HISTORY_CHARS) {
|
|
19190
|
+
return normalized;
|
|
19191
|
+
}
|
|
19192
|
+
const recent = [];
|
|
19193
|
+
const recentBudget = MAX_PROVIDER_HISTORY_CHARS - MAX_PROVIDER_HISTORY_SUMMARY_CHARS;
|
|
19194
|
+
let usedChars = 0;
|
|
19195
|
+
for (let index = normalized.length - 1; index >= 0; index--) {
|
|
19196
|
+
const message = normalized[index];
|
|
19197
|
+
const nextChars = usedChars + message.content.length;
|
|
19198
|
+
if (recent.length >= MAX_PROVIDER_HISTORY_MESSAGES || nextChars > recentBudget) {
|
|
19199
|
+
break;
|
|
19200
|
+
}
|
|
19201
|
+
recent.unshift(message);
|
|
19202
|
+
usedChars = nextChars;
|
|
19203
|
+
}
|
|
19204
|
+
if (recent.length === 0 && normalized.length > 0) {
|
|
19205
|
+
recent.unshift(normalized[normalized.length - 1]);
|
|
19206
|
+
}
|
|
19207
|
+
const omitted = normalized.slice(0, normalized.length - recent.length);
|
|
19208
|
+
return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
|
|
19209
|
+
}
|
|
19012
19210
|
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
19013
19211
|
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
19014
19212
|
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
@@ -19799,7 +19997,7 @@ Exception: ${result.exceptionDetails}`);
|
|
|
19799
19997
|
}
|
|
19800
19998
|
);
|
|
19801
19999
|
}
|
|
19802
|
-
const logger$
|
|
20000
|
+
const logger$f = createLogger("VaultShared");
|
|
19803
20001
|
const ALGORITHM = "aes-256-gcm";
|
|
19804
20002
|
const IV_LENGTH = 12;
|
|
19805
20003
|
const AUTH_TAG_LENGTH = 16;
|
|
@@ -19893,7 +20091,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
19893
20091
|
cachedEntries = JSON.parse(json);
|
|
19894
20092
|
return cachedEntries;
|
|
19895
20093
|
} catch (err) {
|
|
19896
|
-
logger$
|
|
20094
|
+
logger$f.error("Failed to load vault:", err);
|
|
19897
20095
|
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
19898
20096
|
}
|
|
19899
20097
|
}
|
|
@@ -19976,7 +20174,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
19976
20174
|
} catch {
|
|
19977
20175
|
}
|
|
19978
20176
|
} catch (err) {
|
|
19979
|
-
logger$
|
|
20177
|
+
logger$f.error("Failed to write audit log:", err);
|
|
19980
20178
|
}
|
|
19981
20179
|
}
|
|
19982
20180
|
function readAuditLog2(limit = 100) {
|
|
@@ -19986,7 +20184,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
19986
20184
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19987
20185
|
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
19988
20186
|
} catch (err) {
|
|
19989
|
-
logger$
|
|
20187
|
+
logger$f.error("Failed to read audit log:", err);
|
|
19990
20188
|
return [];
|
|
19991
20189
|
}
|
|
19992
20190
|
}
|
|
@@ -20090,7 +20288,7 @@ async function requestConsent(request) {
|
|
|
20090
20288
|
}
|
|
20091
20289
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
20092
20290
|
const MAX_ENTRIES = 1e3;
|
|
20093
|
-
const logger$
|
|
20291
|
+
const logger$e = createLogger("VaultAudit");
|
|
20094
20292
|
function getAuditPath() {
|
|
20095
20293
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
20096
20294
|
}
|
|
@@ -20104,7 +20302,7 @@ function appendAuditEntry(entry) {
|
|
|
20104
20302
|
});
|
|
20105
20303
|
fs$1.chmodSync(auditPath, 384);
|
|
20106
20304
|
} catch (err) {
|
|
20107
|
-
logger$
|
|
20305
|
+
logger$e.error("Failed to write audit log:", err);
|
|
20108
20306
|
}
|
|
20109
20307
|
}
|
|
20110
20308
|
function readAuditLog$1(limit = 100) {
|
|
@@ -20114,7 +20312,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
20114
20312
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
20115
20313
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
20116
20314
|
} catch (err) {
|
|
20117
|
-
logger$
|
|
20315
|
+
logger$e.error("Failed to read audit log:", err);
|
|
20118
20316
|
return [];
|
|
20119
20317
|
}
|
|
20120
20318
|
}
|
|
@@ -20282,7 +20480,7 @@ async function requestHumanVaultConsent(request) {
|
|
|
20282
20480
|
}
|
|
20283
20481
|
let httpServer = null;
|
|
20284
20482
|
let mcpAuthToken = null;
|
|
20285
|
-
const logger$
|
|
20483
|
+
const logger$d = createLogger("MCP");
|
|
20286
20484
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
20287
20485
|
function getMcpAuthFilePath() {
|
|
20288
20486
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -20319,7 +20517,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
20319
20517
|
);
|
|
20320
20518
|
fs$1.chmodSync(filePath2, 384);
|
|
20321
20519
|
} catch (err) {
|
|
20322
|
-
logger$
|
|
20520
|
+
logger$d.warn("Failed to write auth file:", err);
|
|
20323
20521
|
}
|
|
20324
20522
|
}
|
|
20325
20523
|
function clearMcpAuthFile() {
|
|
@@ -20345,7 +20543,7 @@ function clearMcpAuthFile() {
|
|
|
20345
20543
|
);
|
|
20346
20544
|
fs$1.chmodSync(filePath2, 384);
|
|
20347
20545
|
} catch (err) {
|
|
20348
|
-
logger$
|
|
20546
|
+
logger$d.warn("Failed to clear auth file:", err);
|
|
20349
20547
|
}
|
|
20350
20548
|
}
|
|
20351
20549
|
function regenerateMcpAuthToken() {
|
|
@@ -20364,6 +20562,14 @@ function asErrorTextResponse(message) {
|
|
|
20364
20562
|
function asNoActiveTabResponse() {
|
|
20365
20563
|
return asErrorTextResponse("No active tab");
|
|
20366
20564
|
}
|
|
20565
|
+
function getPremiumToolGateResponse(toolName) {
|
|
20566
|
+
try {
|
|
20567
|
+
assertToolUnlocked(toolName);
|
|
20568
|
+
return null;
|
|
20569
|
+
} catch (error) {
|
|
20570
|
+
return asTextResponse(getErrorMessage(error));
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20367
20573
|
function asPromptResponse(text) {
|
|
20368
20574
|
return {
|
|
20369
20575
|
messages: [
|
|
@@ -20447,7 +20653,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
20447
20653
|
}
|
|
20448
20654
|
}
|
|
20449
20655
|
} catch (err) {
|
|
20450
|
-
logger$
|
|
20656
|
+
logger$d.warn("Failed to compute post-action state warning:", err);
|
|
20451
20657
|
}
|
|
20452
20658
|
return `${warning}
|
|
20453
20659
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -20466,6 +20672,8 @@ async function getPostActionState(tabManager, name) {
|
|
|
20466
20672
|
return "";
|
|
20467
20673
|
}
|
|
20468
20674
|
async function withAction(runtime2, tabManager, name, args, executor) {
|
|
20675
|
+
const premiumGate = getPremiumToolGateResponse(name);
|
|
20676
|
+
if (premiumGate) return premiumGate;
|
|
20469
20677
|
try {
|
|
20470
20678
|
const result = await runtime2.runControlledAction({
|
|
20471
20679
|
source: "mcp",
|
|
@@ -20550,7 +20758,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
20550
20758
|
}
|
|
20551
20759
|
})()
|
|
20552
20760
|
`).catch((err) => {
|
|
20553
|
-
logger$
|
|
20761
|
+
logger$d.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
20554
20762
|
return null;
|
|
20555
20763
|
});
|
|
20556
20764
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -20637,7 +20845,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
20637
20845
|
const page = await extractContent(wc);
|
|
20638
20846
|
pageType = detectPageType(page);
|
|
20639
20847
|
} catch (err) {
|
|
20640
|
-
logger$
|
|
20848
|
+
logger$d.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
20641
20849
|
}
|
|
20642
20850
|
}
|
|
20643
20851
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -21775,6 +21983,8 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
21775
21983
|
description: "Capture a screenshot of the current page. Returns a base64-encoded PNG image."
|
|
21776
21984
|
},
|
|
21777
21985
|
async () => {
|
|
21986
|
+
const premiumGate = getPremiumToolGateResponse("screenshot");
|
|
21987
|
+
if (premiumGate) return premiumGate;
|
|
21778
21988
|
const tab = tabManager.getActiveTab();
|
|
21779
21989
|
if (!tab) return asNoActiveTabResponse();
|
|
21780
21990
|
try {
|
|
@@ -22035,7 +22245,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
22035
22245
|
void 0,
|
|
22036
22246
|
h.color
|
|
22037
22247
|
).catch(
|
|
22038
|
-
(err) => logger$
|
|
22248
|
+
(err) => logger$d.warn("Failed to restore highlight after removal:", err)
|
|
22039
22249
|
);
|
|
22040
22250
|
}
|
|
22041
22251
|
}
|
|
@@ -22797,6 +23007,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
22797
23007
|
}
|
|
22798
23008
|
},
|
|
22799
23009
|
async ({ goal, steps }) => {
|
|
23010
|
+
const premiumGate = getPremiumToolGateResponse("flow_start");
|
|
23011
|
+
if (premiumGate) return premiumGate;
|
|
22800
23012
|
const normalizedSteps = coerceStringArray(steps) ?? [];
|
|
22801
23013
|
const tab = tabManager.getActiveTab();
|
|
22802
23014
|
const flow = runtime2.startFlow(
|
|
@@ -22820,6 +23032,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22820
23032
|
}
|
|
22821
23033
|
},
|
|
22822
23034
|
async ({ detail }) => {
|
|
23035
|
+
const premiumGate = getPremiumToolGateResponse("flow_advance");
|
|
23036
|
+
if (premiumGate) return premiumGate;
|
|
22823
23037
|
const flow = runtime2.advanceFlow(detail);
|
|
22824
23038
|
if (!flow) return asTextResponse("No active flow to advance");
|
|
22825
23039
|
const ctx = runtime2.getFlowContext();
|
|
@@ -22833,6 +23047,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22833
23047
|
description: "Check the current workflow progress."
|
|
22834
23048
|
},
|
|
22835
23049
|
async () => {
|
|
23050
|
+
const premiumGate = getPremiumToolGateResponse("flow_status");
|
|
23051
|
+
if (premiumGate) return premiumGate;
|
|
22836
23052
|
const flow = runtime2.getFlowState();
|
|
22837
23053
|
if (!flow) return asTextResponse("No active workflow.");
|
|
22838
23054
|
return asTextResponse(runtime2.getFlowContext());
|
|
@@ -22845,6 +23061,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22845
23061
|
description: "Clear the active workflow tracker."
|
|
22846
23062
|
},
|
|
22847
23063
|
async () => {
|
|
23064
|
+
const premiumGate = getPremiumToolGateResponse("flow_end");
|
|
23065
|
+
if (premiumGate) return premiumGate;
|
|
22848
23066
|
runtime2.clearFlow();
|
|
22849
23067
|
return asTextResponse("Workflow ended.");
|
|
22850
23068
|
}
|
|
@@ -22883,7 +23101,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22883
23101
|
try {
|
|
22884
23102
|
page = await extractContent(wc);
|
|
22885
23103
|
} catch (err) {
|
|
22886
|
-
logger$
|
|
23104
|
+
logger$d.warn("Failed to extract page while generating suggestions:", err);
|
|
22887
23105
|
return asTextResponse(
|
|
22888
23106
|
"Could not read page. Try navigate to a working URL."
|
|
22889
23107
|
);
|
|
@@ -23485,6 +23703,8 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
23485
23703
|
}
|
|
23486
23704
|
},
|
|
23487
23705
|
async ({ domain }) => {
|
|
23706
|
+
const premiumGate = getPremiumToolGateResponse("vault_status");
|
|
23707
|
+
if (premiumGate) return premiumGate;
|
|
23488
23708
|
let targetDomain = domain;
|
|
23489
23709
|
if (!targetDomain) {
|
|
23490
23710
|
const tab = tabManager.getActiveTab();
|
|
@@ -23492,7 +23712,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
23492
23712
|
try {
|
|
23493
23713
|
targetDomain = new URL(tab.state.url).hostname;
|
|
23494
23714
|
} catch (err) {
|
|
23495
|
-
logger$
|
|
23715
|
+
logger$d.warn("Failed to parse active tab URL for vault_status:", err);
|
|
23496
23716
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23497
23717
|
}
|
|
23498
23718
|
}
|
|
@@ -23551,6 +23771,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23551
23771
|
submit_after,
|
|
23552
23772
|
submit_index
|
|
23553
23773
|
}) => {
|
|
23774
|
+
const premiumGate = getPremiumToolGateResponse("vault_login");
|
|
23775
|
+
if (premiumGate) return premiumGate;
|
|
23554
23776
|
const tab = tabManager.getActiveTab();
|
|
23555
23777
|
if (!tab) return asNoActiveTabResponse();
|
|
23556
23778
|
const wc = tab.view.webContents;
|
|
@@ -23558,7 +23780,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23558
23780
|
try {
|
|
23559
23781
|
hostname = new URL(tab.state.url).hostname;
|
|
23560
23782
|
} catch (err) {
|
|
23561
|
-
logger$
|
|
23783
|
+
logger$d.warn("Failed to parse active tab URL for vault_login:", err);
|
|
23562
23784
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23563
23785
|
}
|
|
23564
23786
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -23645,6 +23867,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23645
23867
|
}
|
|
23646
23868
|
},
|
|
23647
23869
|
async ({ credential_label, code_index, submit_after, submit_index }) => {
|
|
23870
|
+
const premiumGate = getPremiumToolGateResponse("vault_totp");
|
|
23871
|
+
if (premiumGate) return premiumGate;
|
|
23648
23872
|
const tab = tabManager.getActiveTab();
|
|
23649
23873
|
if (!tab) return asNoActiveTabResponse();
|
|
23650
23874
|
const wc = tab.view.webContents;
|
|
@@ -23652,7 +23876,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23652
23876
|
try {
|
|
23653
23877
|
hostname = new URL(tab.state.url).hostname;
|
|
23654
23878
|
} catch (err) {
|
|
23655
|
-
logger$
|
|
23879
|
+
logger$d.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
23656
23880
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23657
23881
|
}
|
|
23658
23882
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -23724,6 +23948,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23724
23948
|
})
|
|
23725
23949
|
},
|
|
23726
23950
|
async ({ domain }) => {
|
|
23951
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_list");
|
|
23952
|
+
if (premiumGate) return premiumGate;
|
|
23727
23953
|
const consent = await requestHumanVaultConsent({
|
|
23728
23954
|
action: "list",
|
|
23729
23955
|
domain: domain ?? "all"
|
|
@@ -23774,6 +24000,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23774
24000
|
})
|
|
23775
24001
|
},
|
|
23776
24002
|
async ({ entry_id, username_index, password_index, submit_after, submit_index }) => {
|
|
24003
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_fill");
|
|
24004
|
+
if (premiumGate) return premiumGate;
|
|
23777
24005
|
const tab = tabManager.getActiveTab();
|
|
23778
24006
|
if (!tab) return asNoActiveTabResponse();
|
|
23779
24007
|
let hostname;
|
|
@@ -23866,6 +24094,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23866
24094
|
})
|
|
23867
24095
|
},
|
|
23868
24096
|
async ({ entry_id }) => {
|
|
24097
|
+
const premiumGate = getPremiumToolGateResponse("human_vault_remove");
|
|
24098
|
+
if (premiumGate) return premiumGate;
|
|
23869
24099
|
const entry = getEntry(entry_id);
|
|
23870
24100
|
if (!entry) {
|
|
23871
24101
|
return asErrorTextResponse(`No entry found with ID ${entry_id}.`);
|
|
@@ -23890,6 +24120,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23890
24120
|
inputSchema: zod.z.object({})
|
|
23891
24121
|
},
|
|
23892
24122
|
async () => {
|
|
24123
|
+
const premiumGate = getPremiumToolGateResponse("metrics");
|
|
24124
|
+
if (premiumGate) return premiumGate;
|
|
23893
24125
|
const m = runtime2.getMetrics();
|
|
23894
24126
|
const lines = [
|
|
23895
24127
|
`Session Metrics:`,
|
|
@@ -23992,7 +24224,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23992
24224
|
await mcpServer.connect(transport);
|
|
23993
24225
|
await transport.handleRequest(req, res);
|
|
23994
24226
|
} catch (error) {
|
|
23995
|
-
logger$
|
|
24227
|
+
logger$d.error("Error handling request:", error);
|
|
23996
24228
|
if (!res.headersSent) {
|
|
23997
24229
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
23998
24230
|
res.end(
|
|
@@ -24011,7 +24243,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
24011
24243
|
};
|
|
24012
24244
|
server.once("error", (error) => {
|
|
24013
24245
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
24014
|
-
logger$
|
|
24246
|
+
logger$d.error("Server error:", error);
|
|
24015
24247
|
clearMcpAuthFile();
|
|
24016
24248
|
setMcpHealth({
|
|
24017
24249
|
configuredPort: port,
|
|
@@ -24043,7 +24275,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
24043
24275
|
message: `MCP server listening on ${endpoint}.`
|
|
24044
24276
|
});
|
|
24045
24277
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
24046
|
-
logger$
|
|
24278
|
+
logger$d.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
24047
24279
|
}
|
|
24048
24280
|
if (mcpAuthToken) {
|
|
24049
24281
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -24082,7 +24314,7 @@ function stopMcpServer() {
|
|
|
24082
24314
|
message: "MCP server is stopped."
|
|
24083
24315
|
});
|
|
24084
24316
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
24085
|
-
logger$
|
|
24317
|
+
logger$d.info("Server stopped");
|
|
24086
24318
|
}
|
|
24087
24319
|
resolve();
|
|
24088
24320
|
});
|
|
@@ -24103,7 +24335,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
24103
24335
|
function isSafeAutomationKitId(id) {
|
|
24104
24336
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
24105
24337
|
}
|
|
24106
|
-
const logger$
|
|
24338
|
+
const logger$c = createLogger("KitRegistry");
|
|
24107
24339
|
function getUserKitsDir() {
|
|
24108
24340
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
24109
24341
|
}
|
|
@@ -24141,10 +24373,10 @@ function getInstalledKits() {
|
|
|
24141
24373
|
if (isValidKit(parsed)) {
|
|
24142
24374
|
kits.push(parsed);
|
|
24143
24375
|
} else {
|
|
24144
|
-
logger$
|
|
24376
|
+
logger$c.warn(`Skipping invalid kit file: ${file}`);
|
|
24145
24377
|
}
|
|
24146
24378
|
} catch (err) {
|
|
24147
|
-
logger$
|
|
24379
|
+
logger$c.warn(`Failed to read kit file: ${file}`, err);
|
|
24148
24380
|
}
|
|
24149
24381
|
}
|
|
24150
24382
|
return kits;
|
|
@@ -24243,7 +24475,7 @@ function assertNumber(value, name) {
|
|
|
24243
24475
|
}
|
|
24244
24476
|
}
|
|
24245
24477
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
24246
|
-
function isValidEmail(value) {
|
|
24478
|
+
function isValidEmail$1(value) {
|
|
24247
24479
|
return EMAIL_RE.test(value.trim());
|
|
24248
24480
|
}
|
|
24249
24481
|
function getActiveTabInfo(tabManager) {
|
|
@@ -24253,8 +24485,10 @@ function getActiveTabInfo(tabManager) {
|
|
|
24253
24485
|
if (wc.isDestroyed()) return null;
|
|
24254
24486
|
return { tab, wc };
|
|
24255
24487
|
}
|
|
24256
|
-
const logger$
|
|
24488
|
+
const logger$b = createLogger("Scheduler");
|
|
24257
24489
|
let jobs = [];
|
|
24490
|
+
let pollInterval = null;
|
|
24491
|
+
let alignStartTimeout = null;
|
|
24258
24492
|
let removeIdleListener = null;
|
|
24259
24493
|
let broadcastFn = null;
|
|
24260
24494
|
function getScheduledKitIds() {
|
|
@@ -24284,7 +24518,7 @@ function saveJobs() {
|
|
|
24284
24518
|
});
|
|
24285
24519
|
fs$1.chmodSync(jobsPath, 384);
|
|
24286
24520
|
} catch (err) {
|
|
24287
|
-
logger$
|
|
24521
|
+
logger$b.warn("Failed to save jobs:", err);
|
|
24288
24522
|
}
|
|
24289
24523
|
}
|
|
24290
24524
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -24343,10 +24577,12 @@ function computeNextRun(schedule, from = /* @__PURE__ */ new Date()) {
|
|
|
24343
24577
|
const next = new Date(from);
|
|
24344
24578
|
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
24345
24579
|
const daysUntil = (schedule.dayOfWeek - next.getDay() + 7) % 7;
|
|
24346
|
-
if (daysUntil === 0
|
|
24347
|
-
|
|
24580
|
+
if (daysUntil === 0) {
|
|
24581
|
+
if (next <= from) {
|
|
24582
|
+
next.setDate(next.getDate() + 7);
|
|
24583
|
+
}
|
|
24348
24584
|
} else {
|
|
24349
|
-
next.setDate(next.getDate() +
|
|
24585
|
+
next.setDate(next.getDate() + daysUntil);
|
|
24350
24586
|
}
|
|
24351
24587
|
return next;
|
|
24352
24588
|
}
|
|
@@ -24406,7 +24642,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
24406
24642
|
};
|
|
24407
24643
|
startActivity();
|
|
24408
24644
|
if (!settings2.chatProvider) {
|
|
24409
|
-
logger$
|
|
24645
|
+
logger$b.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
24410
24646
|
appendActivity(
|
|
24411
24647
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
24412
24648
|
);
|
|
@@ -24414,7 +24650,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
24414
24650
|
return;
|
|
24415
24651
|
}
|
|
24416
24652
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
24417
|
-
logger$
|
|
24653
|
+
logger$b.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
24418
24654
|
}
|
|
24419
24655
|
try {
|
|
24420
24656
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -24467,24 +24703,25 @@ function tick(windowState, runtime2) {
|
|
|
24467
24703
|
saveJobs();
|
|
24468
24704
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
24469
24705
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
24470
|
-
logger$
|
|
24706
|
+
logger$b.warn("Unexpected error firing job:", err);
|
|
24471
24707
|
}).finally(fireNext);
|
|
24472
24708
|
};
|
|
24473
24709
|
fireNext();
|
|
24474
24710
|
}
|
|
24475
24711
|
function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
24712
|
+
stopScheduler();
|
|
24476
24713
|
broadcastFn = sendToAll;
|
|
24477
24714
|
loadJobs();
|
|
24478
24715
|
if (normalizeJobs()) {
|
|
24479
24716
|
saveJobs();
|
|
24480
24717
|
}
|
|
24481
|
-
removeIdleListener?.();
|
|
24482
24718
|
removeIdleListener = onAIStreamIdle(() => tick(windowState, runtime2));
|
|
24483
24719
|
const now = /* @__PURE__ */ new Date();
|
|
24484
24720
|
const msToNextMinute = (60 - now.getSeconds()) * 1e3 - now.getMilliseconds();
|
|
24485
|
-
setTimeout(() => {
|
|
24721
|
+
alignStartTimeout = setTimeout(() => {
|
|
24722
|
+
alignStartTimeout = null;
|
|
24486
24723
|
tick(windowState, runtime2);
|
|
24487
|
-
setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24724
|
+
pollInterval = setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24488
24725
|
}, msToNextMinute);
|
|
24489
24726
|
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
|
|
24490
24727
|
assertTrustedIpcSender(event);
|
|
@@ -24546,6 +24783,20 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24546
24783
|
return true;
|
|
24547
24784
|
});
|
|
24548
24785
|
}
|
|
24786
|
+
function stopScheduler() {
|
|
24787
|
+
if (removeIdleListener) {
|
|
24788
|
+
removeIdleListener();
|
|
24789
|
+
removeIdleListener = null;
|
|
24790
|
+
}
|
|
24791
|
+
if (alignStartTimeout) {
|
|
24792
|
+
clearTimeout(alignStartTimeout);
|
|
24793
|
+
alignStartTimeout = null;
|
|
24794
|
+
}
|
|
24795
|
+
if (pollInterval) {
|
|
24796
|
+
clearInterval(pollInterval);
|
|
24797
|
+
pollInterval = null;
|
|
24798
|
+
}
|
|
24799
|
+
}
|
|
24549
24800
|
const SAVE_DEBOUNCE_MS = 250;
|
|
24550
24801
|
const PROFILE_FIELDS = [
|
|
24551
24802
|
"label",
|
|
@@ -24943,6 +25194,7 @@ function registerAutofillHandlers(windowState) {
|
|
|
24943
25194
|
}
|
|
24944
25195
|
function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
24945
25196
|
const pageEventBuckets = /* @__PURE__ */ new Map();
|
|
25197
|
+
const isActiveWebContents = (webContentsId) => windowState.tabManager.getActiveTab()?.view.webContents.id === webContentsId;
|
|
24946
25198
|
const allowPageEvent = (webContentsId) => {
|
|
24947
25199
|
const now = Date.now();
|
|
24948
25200
|
const bucket = pageEventBuckets.get(webContentsId);
|
|
@@ -24979,14 +25231,20 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
24979
25231
|
if (!wc || wc.isDestroyed()) return;
|
|
24980
25232
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24981
25233
|
if (!allowPageEvent(wc.id)) return;
|
|
24982
|
-
|
|
25234
|
+
invalidateExtractionCache(wc);
|
|
25235
|
+
notePageMutationActivity(wc, sendToRendererViews, {
|
|
25236
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25237
|
+
});
|
|
24983
25238
|
});
|
|
24984
25239
|
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
24985
25240
|
const wc = event.sender;
|
|
24986
25241
|
if (!wc || wc.isDestroyed()) return;
|
|
24987
25242
|
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24988
25243
|
if (!allowPageEvent(wc.id)) return;
|
|
24989
|
-
|
|
25244
|
+
invalidateExtractionCache(wc);
|
|
25245
|
+
schedulePageSnapshotCapture(wc, sendToRendererViews, 0, {
|
|
25246
|
+
isActive: () => isActiveWebContents(wc.id)
|
|
25247
|
+
});
|
|
24990
25248
|
});
|
|
24991
25249
|
}
|
|
24992
25250
|
function renderReportAsMarkdown(report, traces) {
|
|
@@ -25047,7 +25305,7 @@ function renderReportAsMarkdown(report, traces) {
|
|
|
25047
25305
|
}
|
|
25048
25306
|
return sections.join("\n");
|
|
25049
25307
|
}
|
|
25050
|
-
const logger$
|
|
25308
|
+
const logger$a = createLogger("ResearchIPC");
|
|
25051
25309
|
function registerResearchHandlers(getOrchestrator) {
|
|
25052
25310
|
electron.ipcMain.handle(Channels.RESEARCH_STATE_GET, () => {
|
|
25053
25311
|
return getOrchestrator().getState();
|
|
@@ -25066,7 +25324,7 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25066
25324
|
await getOrchestrator().startBrief(trimmedQuery);
|
|
25067
25325
|
return { accepted: true };
|
|
25068
25326
|
} catch (err) {
|
|
25069
|
-
logger$
|
|
25327
|
+
logger$a.error("RESEARCH_START_BRIEF failed", err);
|
|
25070
25328
|
return { accepted: false, reason: "error" };
|
|
25071
25329
|
}
|
|
25072
25330
|
}
|
|
@@ -25083,7 +25341,7 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25083
25341
|
orchestrator.confirmBrief();
|
|
25084
25342
|
return { accepted: true };
|
|
25085
25343
|
} catch (err) {
|
|
25086
|
-
logger$
|
|
25344
|
+
logger$a.error("RESEARCH_CONFIRM_BRIEF failed", err);
|
|
25087
25345
|
return { accepted: false, reason: "error" };
|
|
25088
25346
|
}
|
|
25089
25347
|
});
|
|
@@ -25104,11 +25362,11 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25104
25362
|
options.includeTraces
|
|
25105
25363
|
);
|
|
25106
25364
|
orchestrator.executeSubAgents().catch((err) => {
|
|
25107
|
-
logger$
|
|
25365
|
+
logger$a.error("Background sub-agent execution failed", err);
|
|
25108
25366
|
});
|
|
25109
25367
|
return { accepted: true };
|
|
25110
25368
|
} catch (err) {
|
|
25111
|
-
logger$
|
|
25369
|
+
logger$a.error("RESEARCH_APPROVE_OBJECTIVES failed", err);
|
|
25112
25370
|
return { accepted: false, reason: "error" };
|
|
25113
25371
|
}
|
|
25114
25372
|
}
|
|
@@ -25158,7 +25416,7 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25158
25416
|
await promises.writeFile(filePath2, markdown, "utf-8");
|
|
25159
25417
|
return { accepted: true, savedPath: filePath2 };
|
|
25160
25418
|
} catch (err) {
|
|
25161
|
-
logger$
|
|
25419
|
+
logger$a.error("RESEARCH_EXPORT_REPORT failed", err);
|
|
25162
25420
|
return { accepted: false, reason: "error" };
|
|
25163
25421
|
}
|
|
25164
25422
|
});
|
|
@@ -25253,11 +25511,42 @@ RULES:
|
|
|
25253
25511
|
4. Omit empty arrays entirely (contradictions, gaps) — do not include "contradictions": [] if there are none.
|
|
25254
25512
|
5. Do not use emojis.`;
|
|
25255
25513
|
}
|
|
25256
|
-
const logger$
|
|
25514
|
+
const logger$9 = createLogger("ResearchOrchestrator");
|
|
25257
25515
|
const MAX_THREADS = 5;
|
|
25516
|
+
const MAX_TRACE_ARGS_CHARS = 1200;
|
|
25517
|
+
const MAX_TRACE_RESULT_CHARS = 2e3;
|
|
25258
25518
|
function clone$1(value) {
|
|
25259
25519
|
return structuredClone(value);
|
|
25260
25520
|
}
|
|
25521
|
+
function truncateTraceText(value, maxLength) {
|
|
25522
|
+
if (value.length <= maxLength) return value;
|
|
25523
|
+
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
|
|
25524
|
+
}
|
|
25525
|
+
function slimTraceArgs(args) {
|
|
25526
|
+
const json = JSON.stringify(args);
|
|
25527
|
+
if (json.length <= MAX_TRACE_ARGS_CHARS) return clone$1(args);
|
|
25528
|
+
return {
|
|
25529
|
+
_truncated: true,
|
|
25530
|
+
originalChars: json.length,
|
|
25531
|
+
preview: truncateTraceText(json, MAX_TRACE_ARGS_CHARS)
|
|
25532
|
+
};
|
|
25533
|
+
}
|
|
25534
|
+
function slimTraceResult(result) {
|
|
25535
|
+
if (result.length <= MAX_TRACE_RESULT_CHARS) return result;
|
|
25536
|
+
return [
|
|
25537
|
+
`[Trace result truncated from ${result.length} chars.]`,
|
|
25538
|
+
truncateTraceText(result, MAX_TRACE_RESULT_CHARS)
|
|
25539
|
+
].join("\n");
|
|
25540
|
+
}
|
|
25541
|
+
function createTraceToolCall(tool, args, result, startedAt) {
|
|
25542
|
+
return {
|
|
25543
|
+
tool,
|
|
25544
|
+
args: slimTraceArgs(args),
|
|
25545
|
+
result: slimTraceResult(result),
|
|
25546
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25547
|
+
durationMs: Date.now() - startedAt
|
|
25548
|
+
};
|
|
25549
|
+
}
|
|
25261
25550
|
function normalizeSourceDomain(value) {
|
|
25262
25551
|
const trimmed = value.trim().toLowerCase();
|
|
25263
25552
|
if (!trimmed) return "";
|
|
@@ -25411,7 +25700,7 @@ class ResearchOrchestrator {
|
|
|
25411
25700
|
}
|
|
25412
25701
|
stopAndSynthesizeCurrentFindings() {
|
|
25413
25702
|
if (this.state.phase !== "executing") {
|
|
25414
|
-
logger$
|
|
25703
|
+
logger$9.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
|
|
25415
25704
|
return;
|
|
25416
25705
|
}
|
|
25417
25706
|
this.stopRequested = true;
|
|
@@ -25445,23 +25734,23 @@ class ResearchOrchestrator {
|
|
|
25445
25734
|
async startBrief(userQuery) {
|
|
25446
25735
|
const query = userQuery.trim();
|
|
25447
25736
|
if (!query) {
|
|
25448
|
-
logger$
|
|
25737
|
+
logger$9.warn("Ignoring empty Research Desk query");
|
|
25449
25738
|
return;
|
|
25450
25739
|
}
|
|
25451
25740
|
if (this.state.phase !== "idle") {
|
|
25452
|
-
logger$
|
|
25741
|
+
logger$9.warn("Research already in progress, ignoring startBrief");
|
|
25453
25742
|
return;
|
|
25454
25743
|
}
|
|
25455
25744
|
this.state = this.initialState();
|
|
25456
25745
|
this.state.originalQuery = query;
|
|
25457
25746
|
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25458
25747
|
this.setPhase("briefing");
|
|
25459
|
-
logger$
|
|
25748
|
+
logger$9.info(`Brief started for query: ${query.slice(0, 120)}`);
|
|
25460
25749
|
}
|
|
25461
25750
|
// ── phase: briefing → planning ─────────────────────────────────
|
|
25462
25751
|
confirmBrief() {
|
|
25463
25752
|
if (this.state.phase !== "briefing") {
|
|
25464
|
-
logger$
|
|
25753
|
+
logger$9.warn("Not in briefing phase, ignoring confirmBrief");
|
|
25465
25754
|
return;
|
|
25466
25755
|
}
|
|
25467
25756
|
this.setPhase("planning");
|
|
@@ -25469,7 +25758,7 @@ class ResearchOrchestrator {
|
|
|
25469
25758
|
// ── phase: planning → awaiting_approval ────────────────────────
|
|
25470
25759
|
setObjectives(objectives) {
|
|
25471
25760
|
if (this.state.phase !== "planning") {
|
|
25472
|
-
logger$
|
|
25761
|
+
logger$9.warn("Not in planning phase, ignoring setObjectives");
|
|
25473
25762
|
return;
|
|
25474
25763
|
}
|
|
25475
25764
|
const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
|
|
@@ -25498,11 +25787,11 @@ class ResearchOrchestrator {
|
|
|
25498
25787
|
try {
|
|
25499
25788
|
const parsed = JSON.parse(json);
|
|
25500
25789
|
if (typeof parsed.researchQuestion !== "string" || !parsed.researchQuestion.trim()) {
|
|
25501
|
-
logger$
|
|
25790
|
+
logger$9.warn("Missing researchQuestion in objectives JSON");
|
|
25502
25791
|
return false;
|
|
25503
25792
|
}
|
|
25504
25793
|
if (!Array.isArray(parsed.threads) || parsed.threads.length === 0) {
|
|
25505
|
-
logger$
|
|
25794
|
+
logger$9.warn("Missing or empty threads array in objectives JSON");
|
|
25506
25795
|
return false;
|
|
25507
25796
|
}
|
|
25508
25797
|
const threads = parsed.threads.map((t, i) => {
|
|
@@ -25520,7 +25809,7 @@ class ResearchOrchestrator {
|
|
|
25520
25809
|
};
|
|
25521
25810
|
}).filter((thread) => thread.question && thread.searchQueries.length > 0).slice(0, MAX_THREADS);
|
|
25522
25811
|
if (threads.length === 0) {
|
|
25523
|
-
logger$
|
|
25812
|
+
logger$9.warn("Objectives JSON did not contain any valid research threads");
|
|
25524
25813
|
return false;
|
|
25525
25814
|
}
|
|
25526
25815
|
const objectives = {
|
|
@@ -25531,17 +25820,17 @@ class ResearchOrchestrator {
|
|
|
25531
25820
|
totalSourceBudget: threads.reduce((sum, t) => sum + t.sourceBudget, 0)
|
|
25532
25821
|
};
|
|
25533
25822
|
this.setObjectives(objectives);
|
|
25534
|
-
logger$
|
|
25823
|
+
logger$9.info(`Parsed ${objectives.threads.length} threads from objectives`);
|
|
25535
25824
|
return true;
|
|
25536
25825
|
} catch (err) {
|
|
25537
|
-
logger$
|
|
25826
|
+
logger$9.warn("Failed to parse objectives JSON", err);
|
|
25538
25827
|
return false;
|
|
25539
25828
|
}
|
|
25540
25829
|
}
|
|
25541
25830
|
// ── phase: awaiting_approval → executing ───────────────────────
|
|
25542
25831
|
approveObjectives(mode, includeTraces) {
|
|
25543
25832
|
if (this.state.phase !== "awaiting_approval") {
|
|
25544
|
-
logger$
|
|
25833
|
+
logger$9.warn("Not awaiting approval, ignoring approveObjectives");
|
|
25545
25834
|
return;
|
|
25546
25835
|
}
|
|
25547
25836
|
if (mode) this.state.supervisionMode = mode;
|
|
@@ -25576,7 +25865,7 @@ class ResearchOrchestrator {
|
|
|
25576
25865
|
this.state.threads.map((thread) => {
|
|
25577
25866
|
if (this.state.phase !== "executing") return null;
|
|
25578
25867
|
return this.runSubAgent(thread, tabMutex).catch((err) => {
|
|
25579
|
-
logger$
|
|
25868
|
+
logger$9.error(`Sub-agent "${thread.label}" failed`, err);
|
|
25580
25869
|
return {
|
|
25581
25870
|
threadLabel: thread.label,
|
|
25582
25871
|
threadQuestion: thread.question,
|
|
@@ -25605,7 +25894,7 @@ class ResearchOrchestrator {
|
|
|
25605
25894
|
try {
|
|
25606
25895
|
await this.synthesizeReport();
|
|
25607
25896
|
} catch (err) {
|
|
25608
|
-
logger$
|
|
25897
|
+
logger$9.error("Auto-synthesis failed", err);
|
|
25609
25898
|
this.state.error = `Synthesis failed: ${String(err)}`;
|
|
25610
25899
|
this.setPhase("delivered");
|
|
25611
25900
|
}
|
|
@@ -25666,13 +25955,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25666
25955
|
title: String(args.url || "excluded source"),
|
|
25667
25956
|
reason: msg
|
|
25668
25957
|
});
|
|
25669
|
-
trace.toolCalls.push(
|
|
25670
|
-
tool: name,
|
|
25671
|
-
args,
|
|
25672
|
-
result: msg,
|
|
25673
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25674
|
-
durationMs: 0
|
|
25675
|
-
});
|
|
25958
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25676
25959
|
return msg;
|
|
25677
25960
|
}
|
|
25678
25961
|
}
|
|
@@ -25680,25 +25963,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25680
25963
|
sourcesConsumed++;
|
|
25681
25964
|
if (sourcesConsumed > thread.sourceBudget) {
|
|
25682
25965
|
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
|
-
});
|
|
25966
|
+
trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
|
|
25690
25967
|
return msg;
|
|
25691
25968
|
}
|
|
25692
25969
|
}
|
|
25693
25970
|
try {
|
|
25694
25971
|
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
|
-
});
|
|
25972
|
+
trace.toolCalls.push(createTraceToolCall(name, args, output, t0));
|
|
25702
25973
|
return output;
|
|
25703
25974
|
} catch (err) {
|
|
25704
25975
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -25713,13 +25984,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25713
25984
|
message: msg,
|
|
25714
25985
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25715
25986
|
});
|
|
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
|
-
});
|
|
25987
|
+
trace.toolCalls.push(createTraceToolCall(name, args, `Error: ${msg}`, t0));
|
|
25723
25988
|
return `Error: ${msg}`;
|
|
25724
25989
|
}
|
|
25725
25990
|
},
|
|
@@ -25740,7 +26005,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25740
26005
|
try {
|
|
25741
26006
|
this.tabManager.closeTab(tabId);
|
|
25742
26007
|
} catch (err) {
|
|
25743
|
-
logger$
|
|
26008
|
+
logger$9.warn(`Failed to close sub-agent tab ${tabId}`, err);
|
|
25744
26009
|
}
|
|
25745
26010
|
}
|
|
25746
26011
|
}
|
|
@@ -25749,7 +26014,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25749
26014
|
try {
|
|
25750
26015
|
claims = await this.extractClaimsFromTranscript(thread, transcript);
|
|
25751
26016
|
} catch (err) {
|
|
25752
|
-
logger$
|
|
26017
|
+
logger$9.warn(`Claim extraction failed for "${thread.label}"`, err);
|
|
25753
26018
|
}
|
|
25754
26019
|
}
|
|
25755
26020
|
if (this.state.phase === "executing" && this.state.includeTraces) {
|
|
@@ -25839,7 +26104,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25839
26104
|
(claim) => claim.claim && claim.sourceUrl && claim.extractedQuote
|
|
25840
26105
|
);
|
|
25841
26106
|
} catch {
|
|
25842
|
-
logger$
|
|
26107
|
+
logger$9.warn(`Failed to parse claims JSON for "${thread.label}"`);
|
|
25843
26108
|
return [];
|
|
25844
26109
|
}
|
|
25845
26110
|
}
|
|
@@ -25924,7 +26189,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25924
26189
|
objectives
|
|
25925
26190
|
};
|
|
25926
26191
|
} catch (err) {
|
|
25927
|
-
logger$
|
|
26192
|
+
logger$9.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
|
|
25928
26193
|
return buildFallbackReport(objectives, findings, String(err));
|
|
25929
26194
|
}
|
|
25930
26195
|
}
|
|
@@ -25939,15 +26204,20 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25939
26204
|
this.emit();
|
|
25940
26205
|
}
|
|
25941
26206
|
}
|
|
26207
|
+
function assertVaultUnlocked() {
|
|
26208
|
+
assertFeatureUnlocked("vault", "Agent Credential Vault");
|
|
26209
|
+
}
|
|
25942
26210
|
function registerVaultHandlers() {
|
|
25943
26211
|
electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
|
|
25944
26212
|
assertTrustedIpcSender(event);
|
|
26213
|
+
assertVaultUnlocked();
|
|
25945
26214
|
return listEntries$1();
|
|
25946
26215
|
});
|
|
25947
26216
|
electron.ipcMain.handle(
|
|
25948
26217
|
Channels.VAULT_ADD,
|
|
25949
26218
|
(event, entry) => {
|
|
25950
26219
|
assertTrustedIpcSender(event);
|
|
26220
|
+
assertVaultUnlocked();
|
|
25951
26221
|
if (!entry || typeof entry !== "object") {
|
|
25952
26222
|
throw new Error("Invalid vault entry");
|
|
25953
26223
|
}
|
|
@@ -25974,6 +26244,7 @@ function registerVaultHandlers() {
|
|
|
25974
26244
|
Channels.VAULT_UPDATE,
|
|
25975
26245
|
(event, id, updates) => {
|
|
25976
26246
|
assertTrustedIpcSender(event);
|
|
26247
|
+
assertVaultUnlocked();
|
|
25977
26248
|
assertString(id, "id");
|
|
25978
26249
|
if (!updates || typeof updates !== "object") {
|
|
25979
26250
|
throw new Error("Invalid updates");
|
|
@@ -25983,12 +26254,14 @@ function registerVaultHandlers() {
|
|
|
25983
26254
|
);
|
|
25984
26255
|
electron.ipcMain.handle(Channels.VAULT_REMOVE, (event, id) => {
|
|
25985
26256
|
assertTrustedIpcSender(event);
|
|
26257
|
+
assertVaultUnlocked();
|
|
25986
26258
|
assertString(id, "id");
|
|
25987
26259
|
trackVaultAction("credential_removed");
|
|
25988
26260
|
return removeEntry$1(id);
|
|
25989
26261
|
});
|
|
25990
26262
|
electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (event, limit) => {
|
|
25991
26263
|
assertTrustedIpcSender(event);
|
|
26264
|
+
assertVaultUnlocked();
|
|
25992
26265
|
return readAuditLog$1(limit);
|
|
25993
26266
|
});
|
|
25994
26267
|
}
|
|
@@ -26006,14 +26279,19 @@ function normalizeTags(value) {
|
|
|
26006
26279
|
const tags = value.filter((tag) => typeof tag === "string").map((tag) => tag.trim()).filter(Boolean);
|
|
26007
26280
|
return tags.length > 0 ? tags : void 0;
|
|
26008
26281
|
}
|
|
26282
|
+
function assertHumanVaultUnlocked() {
|
|
26283
|
+
assertFeatureUnlocked("human_vault", "Passwords");
|
|
26284
|
+
}
|
|
26009
26285
|
function registerHumanVaultHandlers() {
|
|
26010
26286
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (event, domain) => {
|
|
26011
26287
|
assertTrustedIpcSender(event);
|
|
26288
|
+
assertHumanVaultUnlocked();
|
|
26012
26289
|
if (domain !== void 0) assertString(domain, "domain");
|
|
26013
26290
|
return domain ? findForDomain(domain) : listEntries();
|
|
26014
26291
|
});
|
|
26015
26292
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (event, id) => {
|
|
26016
26293
|
assertTrustedIpcSender(event);
|
|
26294
|
+
assertHumanVaultUnlocked();
|
|
26017
26295
|
assertString(id, "id");
|
|
26018
26296
|
return getEntrySafe(id);
|
|
26019
26297
|
});
|
|
@@ -26021,6 +26299,7 @@ function registerHumanVaultHandlers() {
|
|
|
26021
26299
|
Channels.HUMAN_VAULT_SAVE,
|
|
26022
26300
|
(event, input) => {
|
|
26023
26301
|
assertTrustedIpcSender(event);
|
|
26302
|
+
assertHumanVaultUnlocked();
|
|
26024
26303
|
if (!input || typeof input !== "object") {
|
|
26025
26304
|
throw new Error("Invalid credential entry");
|
|
26026
26305
|
}
|
|
@@ -26052,6 +26331,7 @@ function registerHumanVaultHandlers() {
|
|
|
26052
26331
|
Channels.HUMAN_VAULT_UPDATE,
|
|
26053
26332
|
(event, id, updates) => {
|
|
26054
26333
|
assertTrustedIpcSender(event);
|
|
26334
|
+
assertHumanVaultUnlocked();
|
|
26055
26335
|
assertString(id, "id");
|
|
26056
26336
|
if (!updates || typeof updates !== "object") {
|
|
26057
26337
|
throw new Error("Invalid updates");
|
|
@@ -26095,11 +26375,13 @@ function registerHumanVaultHandlers() {
|
|
|
26095
26375
|
);
|
|
26096
26376
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (event, id) => {
|
|
26097
26377
|
assertTrustedIpcSender(event);
|
|
26378
|
+
assertHumanVaultUnlocked();
|
|
26098
26379
|
assertString(id, "id");
|
|
26099
26380
|
return removeEntry(id);
|
|
26100
26381
|
});
|
|
26101
26382
|
electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (event, limit) => {
|
|
26102
26383
|
assertTrustedIpcSender(event);
|
|
26384
|
+
assertHumanVaultUnlocked();
|
|
26103
26385
|
return readAuditLog(limit);
|
|
26104
26386
|
});
|
|
26105
26387
|
}
|
|
@@ -26646,7 +26928,7 @@ function createFindInPageBridge(tabManager, chromeView) {
|
|
|
26646
26928
|
}
|
|
26647
26929
|
};
|
|
26648
26930
|
}
|
|
26649
|
-
const logger$
|
|
26931
|
+
const logger$8 = createLogger("PrivateWindow");
|
|
26650
26932
|
const privateWindows = /* @__PURE__ */ new Set();
|
|
26651
26933
|
function layoutPrivateViews(state2) {
|
|
26652
26934
|
const { window: win, chromeView, tabManager } = state2;
|
|
@@ -26869,7 +27151,7 @@ function createPrivateWindow() {
|
|
|
26869
27151
|
privateSession.clearStorageData(),
|
|
26870
27152
|
privateSession.clearCache()
|
|
26871
27153
|
]).catch((error) => {
|
|
26872
|
-
logger$
|
|
27154
|
+
logger$8.warn("Failed to clear private browsing session:", error);
|
|
26873
27155
|
});
|
|
26874
27156
|
});
|
|
26875
27157
|
privateWindows.add(state2);
|
|
@@ -26879,7 +27161,7 @@ function createPrivateWindow() {
|
|
|
26879
27161
|
});
|
|
26880
27162
|
loadPrivateRenderer(chromeView);
|
|
26881
27163
|
win.show();
|
|
26882
|
-
logger$
|
|
27164
|
+
logger$8.info("Private browsing window opened");
|
|
26883
27165
|
return state2;
|
|
26884
27166
|
}
|
|
26885
27167
|
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
@@ -27235,6 +27517,10 @@ function registerHistoryHandlers() {
|
|
|
27235
27517
|
assertTrustedIpcSender(event);
|
|
27236
27518
|
return getState$1();
|
|
27237
27519
|
});
|
|
27520
|
+
electron.ipcMain.handle(Channels.HISTORY_LIST, (event, offset, limit) => {
|
|
27521
|
+
assertTrustedIpcSender(event);
|
|
27522
|
+
return listEntries$2(offset, limit);
|
|
27523
|
+
});
|
|
27238
27524
|
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (event, query) => {
|
|
27239
27525
|
assertTrustedIpcSender(event);
|
|
27240
27526
|
return search(query);
|
|
@@ -27389,7 +27675,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27389
27675
|
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (event, email) => {
|
|
27390
27676
|
assertTrustedIpcSender(event);
|
|
27391
27677
|
assertString(email, "email");
|
|
27392
|
-
if (!isValidEmail(email)) {
|
|
27678
|
+
if (!isValidEmail$1(email)) {
|
|
27393
27679
|
return errorResult("Invalid email format");
|
|
27394
27680
|
}
|
|
27395
27681
|
trackPremiumFunnel("activation_attempted");
|
|
@@ -27416,7 +27702,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27416
27702
|
assertString(email, "email");
|
|
27417
27703
|
assertString(code, "code");
|
|
27418
27704
|
assertString(challengeToken, "challengeToken");
|
|
27419
|
-
if (!isValidEmail(email)) {
|
|
27705
|
+
if (!isValidEmail$1(email)) {
|
|
27420
27706
|
return errorResult("Invalid email format", {
|
|
27421
27707
|
state: getPremiumState()
|
|
27422
27708
|
});
|
|
@@ -27575,7 +27861,7 @@ function registerSecurityHandlers(tabManager) {
|
|
|
27575
27861
|
tabManager.goBackToSafety(tabId);
|
|
27576
27862
|
});
|
|
27577
27863
|
}
|
|
27578
|
-
const logger$
|
|
27864
|
+
const logger$7 = createLogger("CodexIPC");
|
|
27579
27865
|
function registerCodexHandlers() {
|
|
27580
27866
|
electron.ipcMain.handle(Channels.CODEX_START_AUTH, async (event) => {
|
|
27581
27867
|
assertTrustedIpcSender(event);
|
|
@@ -27590,7 +27876,7 @@ function registerCodexHandlers() {
|
|
|
27590
27876
|
try {
|
|
27591
27877
|
wc.send(Channels.CODEX_AUTH_STATUS, { status, error: error || null });
|
|
27592
27878
|
} catch {
|
|
27593
|
-
logger$
|
|
27879
|
+
logger$7.warn("Codex auth status send failed — window may be closed");
|
|
27594
27880
|
}
|
|
27595
27881
|
};
|
|
27596
27882
|
try {
|
|
@@ -27602,7 +27888,7 @@ function registerCodexHandlers() {
|
|
|
27602
27888
|
accountId: tokens.accountId
|
|
27603
27889
|
};
|
|
27604
27890
|
} catch (err) {
|
|
27605
|
-
logger$
|
|
27891
|
+
logger$7.error("Codex auth failed:", err);
|
|
27606
27892
|
return {
|
|
27607
27893
|
ok: false,
|
|
27608
27894
|
error: err instanceof Error ? err.message : "Unknown error"
|
|
@@ -27620,6 +27906,160 @@ function registerCodexHandlers() {
|
|
|
27620
27906
|
return { ok: true };
|
|
27621
27907
|
});
|
|
27622
27908
|
}
|
|
27909
|
+
const logger$6 = createLogger("OpenRouterOAuth");
|
|
27910
|
+
const AUTH_BASE_URL = "https://openrouter.ai/auth";
|
|
27911
|
+
const KEY_EXCHANGE_URL = "https://openrouter.ai/api/v1/auth/keys";
|
|
27912
|
+
const AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
27913
|
+
const PREFERRED_PORT = 1460;
|
|
27914
|
+
const FALLBACK_PORT = 1461;
|
|
27915
|
+
async function exchangeCodeForApiKey(code, codeVerifier) {
|
|
27916
|
+
const response = await fetch(KEY_EXCHANGE_URL, {
|
|
27917
|
+
method: "POST",
|
|
27918
|
+
headers: {
|
|
27919
|
+
"Content-Type": "application/json"
|
|
27920
|
+
},
|
|
27921
|
+
body: JSON.stringify({
|
|
27922
|
+
code,
|
|
27923
|
+
code_verifier: codeVerifier,
|
|
27924
|
+
code_challenge_method: "S256"
|
|
27925
|
+
})
|
|
27926
|
+
});
|
|
27927
|
+
if (!response.ok) {
|
|
27928
|
+
let errorMsg = `OpenRouter key exchange failed: ${response.status}`;
|
|
27929
|
+
try {
|
|
27930
|
+
const payload2 = await response.json();
|
|
27931
|
+
if (typeof payload2.error === "string") {
|
|
27932
|
+
errorMsg = payload2.error;
|
|
27933
|
+
} else if (typeof payload2.message === "string") {
|
|
27934
|
+
errorMsg = payload2.message;
|
|
27935
|
+
}
|
|
27936
|
+
} catch {
|
|
27937
|
+
}
|
|
27938
|
+
throw new Error(errorMsg);
|
|
27939
|
+
}
|
|
27940
|
+
const payload = await response.json();
|
|
27941
|
+
if (typeof payload.key !== "string" || !payload.key.trim()) {
|
|
27942
|
+
throw new Error("OpenRouter did not return an API key");
|
|
27943
|
+
}
|
|
27944
|
+
return payload.key.trim();
|
|
27945
|
+
}
|
|
27946
|
+
const openRouterOAuth = createLocalPkceOAuthFlow({
|
|
27947
|
+
name: "OpenRouter",
|
|
27948
|
+
logger: logger$6,
|
|
27949
|
+
preferredPorts: [PREFERRED_PORT, FALLBACK_PORT],
|
|
27950
|
+
timeoutMs: AUTH_TIMEOUT_MS,
|
|
27951
|
+
callbackPath: (state2) => `/auth/openrouter/callback/${state2}`,
|
|
27952
|
+
readState: (url) => decodeURIComponent(url.pathname.split("/").pop() || ""),
|
|
27953
|
+
buildAuthorizeUrl: ({ callbackUrl, pkce }) => {
|
|
27954
|
+
const params = new URLSearchParams({
|
|
27955
|
+
callback_url: callbackUrl,
|
|
27956
|
+
code_challenge: pkce.codeChallenge,
|
|
27957
|
+
code_challenge_method: "S256"
|
|
27958
|
+
});
|
|
27959
|
+
return `${AUTH_BASE_URL}?${params.toString()}`;
|
|
27960
|
+
},
|
|
27961
|
+
exchangeCode: ({ code, codeVerifier }) => exchangeCodeForApiKey(code, codeVerifier),
|
|
27962
|
+
successHtml: () => `<!DOCTYPE html>
|
|
27963
|
+
<html><head><meta charset="utf-8"><title>Vessel OpenRouter Setup</title>
|
|
27964
|
+
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee}</style></head>
|
|
27965
|
+
<body><div style="text-align:center"><h1>OpenRouter connected</h1><p>Vessel is ready. You can close this tab.</p></div></body></html>`,
|
|
27966
|
+
openHosts: ["openrouter.ai"]
|
|
27967
|
+
});
|
|
27968
|
+
function startOpenRouterOAuth(onStatus) {
|
|
27969
|
+
return openRouterOAuth.start(onStatus);
|
|
27970
|
+
}
|
|
27971
|
+
function cancelOpenRouterOAuth() {
|
|
27972
|
+
openRouterOAuth.cancel();
|
|
27973
|
+
}
|
|
27974
|
+
const logger$5 = createLogger("OpenRouterIPC");
|
|
27975
|
+
function registerOpenRouterHandlers(applySettingChange) {
|
|
27976
|
+
electron.ipcMain.handle(Channels.OPENROUTER_START_AUTH, async (event) => {
|
|
27977
|
+
assertTrustedIpcSender(event);
|
|
27978
|
+
const wc = event.sender;
|
|
27979
|
+
if (!wc || wc.isDestroyed()) {
|
|
27980
|
+
return {
|
|
27981
|
+
ok: false,
|
|
27982
|
+
error: "No active window found for sender"
|
|
27983
|
+
};
|
|
27984
|
+
}
|
|
27985
|
+
const sendStatus = (status, error) => {
|
|
27986
|
+
try {
|
|
27987
|
+
wc.send(Channels.OPENROUTER_AUTH_STATUS, { status, error: error || null });
|
|
27988
|
+
} catch {
|
|
27989
|
+
logger$5.warn("OpenRouter auth status send failed - window may be closed");
|
|
27990
|
+
}
|
|
27991
|
+
};
|
|
27992
|
+
try {
|
|
27993
|
+
const apiKey = await startOpenRouterOAuth(sendStatus);
|
|
27994
|
+
const openRouterConfig = {
|
|
27995
|
+
id: "openrouter",
|
|
27996
|
+
apiKey,
|
|
27997
|
+
hasApiKey: true,
|
|
27998
|
+
model: PROVIDERS.openrouter.defaultModel,
|
|
27999
|
+
baseUrl: PROVIDERS.openrouter.defaultBaseUrl,
|
|
28000
|
+
reasoningEffort: "off"
|
|
28001
|
+
};
|
|
28002
|
+
await applySettingChange("chatProvider", openRouterConfig);
|
|
28003
|
+
return {
|
|
28004
|
+
ok: true,
|
|
28005
|
+
providerId: "openrouter",
|
|
28006
|
+
model: openRouterConfig.model
|
|
28007
|
+
};
|
|
28008
|
+
} catch (err) {
|
|
28009
|
+
logger$5.error("OpenRouter auth failed:", err);
|
|
28010
|
+
return {
|
|
28011
|
+
ok: false,
|
|
28012
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
28013
|
+
};
|
|
28014
|
+
}
|
|
28015
|
+
});
|
|
28016
|
+
electron.ipcMain.handle(Channels.OPENROUTER_CANCEL_AUTH, (event) => {
|
|
28017
|
+
assertTrustedIpcSender(event);
|
|
28018
|
+
cancelOpenRouterOAuth();
|
|
28019
|
+
return { ok: true };
|
|
28020
|
+
});
|
|
28021
|
+
}
|
|
28022
|
+
const SUPPORT_API = process.env.VESSEL_SUPPORT_API || process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
28023
|
+
const MAX_FEEDBACK_MESSAGE_LENGTH = 5e3;
|
|
28024
|
+
const FEEDBACK_REQUEST_TIMEOUT_MS = 15e3;
|
|
28025
|
+
function isValidEmail(email) {
|
|
28026
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
28027
|
+
}
|
|
28028
|
+
async function submitFeedback(payload) {
|
|
28029
|
+
const email = payload.email.trim().toLowerCase();
|
|
28030
|
+
const message = payload.message.trim();
|
|
28031
|
+
if (!isValidEmail(email)) {
|
|
28032
|
+
return errorResult("Enter a valid reply email.");
|
|
28033
|
+
}
|
|
28034
|
+
if (!message) {
|
|
28035
|
+
return errorResult("Write a feedback message before sending.");
|
|
28036
|
+
}
|
|
28037
|
+
if (message.length > MAX_FEEDBACK_MESSAGE_LENGTH) {
|
|
28038
|
+
return errorResult(
|
|
28039
|
+
`Feedback must be ${MAX_FEEDBACK_MESSAGE_LENGTH.toLocaleString()} characters or less.`
|
|
28040
|
+
);
|
|
28041
|
+
}
|
|
28042
|
+
try {
|
|
28043
|
+
const signal = AbortSignal.timeout(FEEDBACK_REQUEST_TIMEOUT_MS);
|
|
28044
|
+
const res = await fetch(`${SUPPORT_API}/feedback`, {
|
|
28045
|
+
method: "POST",
|
|
28046
|
+
headers: { "Content-Type": "application/json" },
|
|
28047
|
+
signal,
|
|
28048
|
+
body: JSON.stringify({
|
|
28049
|
+
email,
|
|
28050
|
+
message,
|
|
28051
|
+
source: payload.source
|
|
28052
|
+
})
|
|
28053
|
+
});
|
|
28054
|
+
const data = await res.json().catch(() => ({}));
|
|
28055
|
+
if (!res.ok) {
|
|
28056
|
+
return errorResult(data.error || `HTTP ${res.status}`);
|
|
28057
|
+
}
|
|
28058
|
+
return okResult();
|
|
28059
|
+
} catch (error) {
|
|
28060
|
+
return errorResult(getErrorMessage(error, "Failed to send feedback."));
|
|
28061
|
+
}
|
|
28062
|
+
}
|
|
27623
28063
|
const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
|
|
27624
28064
|
const ALLOWED_PERMISSION_TYPES = /* @__PURE__ */ new Set([
|
|
27625
28065
|
"clipboard-read",
|
|
@@ -27948,6 +28388,26 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27948
28388
|
const count = await getActiveHighlightCountSafe();
|
|
27949
28389
|
sendToRendererViews(Channels.HIGHLIGHT_COUNT_UPDATE, count);
|
|
27950
28390
|
};
|
|
28391
|
+
const applySettingChange = async (key2, value) => {
|
|
28392
|
+
const updatedSettings = setSetting(key2, value);
|
|
28393
|
+
trackSettingChanged(key2);
|
|
28394
|
+
if (key2 === "approvalMode") {
|
|
28395
|
+
runtime2.setApprovalMode(value);
|
|
28396
|
+
}
|
|
28397
|
+
if (key2 === "mcpPort") {
|
|
28398
|
+
await stopMcpServer();
|
|
28399
|
+
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
28400
|
+
}
|
|
28401
|
+
if (key2 === "chatProvider" && researchOrchestrator) {
|
|
28402
|
+
try {
|
|
28403
|
+
researchOrchestrator.setProvider(createProvider(value));
|
|
28404
|
+
} catch {
|
|
28405
|
+
}
|
|
28406
|
+
}
|
|
28407
|
+
const rendererSettings = getRendererSettings();
|
|
28408
|
+
sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
|
|
28409
|
+
return rendererSettings;
|
|
28410
|
+
};
|
|
27951
28411
|
runtime2.setUpdateListener((state2) => {
|
|
27952
28412
|
scheduleRuntimeUpdate(state2);
|
|
27953
28413
|
});
|
|
@@ -28133,7 +28593,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28133
28593
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
28134
28594
|
tabManager,
|
|
28135
28595
|
runtime2,
|
|
28136
|
-
history,
|
|
28596
|
+
compactProviderHistory(history),
|
|
28137
28597
|
researchOrchestrator
|
|
28138
28598
|
);
|
|
28139
28599
|
} catch (err) {
|
|
@@ -28285,6 +28745,12 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28285
28745
|
requireTrusted(event);
|
|
28286
28746
|
return regenerateMcpAuthToken();
|
|
28287
28747
|
});
|
|
28748
|
+
electron.ipcMain.handle(Channels.SUPPORT_SUBMIT_FEEDBACK, async (event, email, message) => {
|
|
28749
|
+
requireTrusted(event);
|
|
28750
|
+
assertString(email, "email");
|
|
28751
|
+
assertString(message, "message");
|
|
28752
|
+
return submitFeedback({ email, message, source: "settings_account" });
|
|
28753
|
+
});
|
|
28288
28754
|
electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
|
|
28289
28755
|
requireTrusted(event);
|
|
28290
28756
|
assertString(key2, "key");
|
|
@@ -28292,24 +28758,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28292
28758
|
throw new Error(`Unknown setting key: ${key2}`);
|
|
28293
28759
|
}
|
|
28294
28760
|
const settingsKey = key2;
|
|
28295
|
-
|
|
28296
|
-
trackSettingChanged(key2);
|
|
28297
|
-
if (key2 === "approvalMode") {
|
|
28298
|
-
runtime2.setApprovalMode(value);
|
|
28299
|
-
}
|
|
28300
|
-
if (key2 === "mcpPort") {
|
|
28301
|
-
await stopMcpServer();
|
|
28302
|
-
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
28303
|
-
}
|
|
28304
|
-
if (key2 === "chatProvider" && researchOrchestrator) {
|
|
28305
|
-
try {
|
|
28306
|
-
researchOrchestrator.setProvider(createProvider(value));
|
|
28307
|
-
} catch {
|
|
28308
|
-
}
|
|
28309
|
-
}
|
|
28310
|
-
const rendererSettings = getRendererSettings();
|
|
28311
|
-
sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
|
|
28312
|
-
return rendererSettings;
|
|
28761
|
+
return applySettingChange(settingsKey, value);
|
|
28313
28762
|
});
|
|
28314
28763
|
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, (event) => {
|
|
28315
28764
|
requireTrusted(event);
|
|
@@ -28498,6 +28947,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
28498
28947
|
registerHumanVaultHandlers();
|
|
28499
28948
|
registerWindowControlHandlers(mainWindow);
|
|
28500
28949
|
registerCodexHandlers();
|
|
28950
|
+
registerOpenRouterHandlers(applySettingChange);
|
|
28501
28951
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
|
|
28502
28952
|
requireTrusted(event);
|
|
28503
28953
|
return getInstalledKits();
|
|
@@ -29926,7 +30376,7 @@ async function bootstrap() {
|
|
|
29926
30376
|
);
|
|
29927
30377
|
}
|
|
29928
30378
|
};
|
|
29929
|
-
const windowState = createMainWindow((tabs, activeId) => {
|
|
30379
|
+
const windowState = createMainWindow((tabs, activeId, meta) => {
|
|
29930
30380
|
windowState.chromeView.webContents.send(
|
|
29931
30381
|
Channels.TAB_STATE_UPDATE,
|
|
29932
30382
|
tabs,
|
|
@@ -29934,7 +30384,9 @@ async function bootstrap() {
|
|
|
29934
30384
|
);
|
|
29935
30385
|
void syncActiveHighlightCount(windowState);
|
|
29936
30386
|
layoutViews(windowState);
|
|
29937
|
-
|
|
30387
|
+
if (meta.persistSession) {
|
|
30388
|
+
runtime?.onTabStateChanged();
|
|
30389
|
+
}
|
|
29938
30390
|
});
|
|
29939
30391
|
let didRevealMainWindow = false;
|
|
29940
30392
|
const revealMainWindow = () => {
|