@quanta-intellect/vessel-browser 0.1.104 → 0.1.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js CHANGED
@@ -1940,7 +1940,7 @@ function save$2() {
1940
1940
  }
1941
1941
  function emit$3() {
1942
1942
  if (!state$4) return;
1943
- const snapshot2 = { entries: [...state$4.entries] };
1943
+ const snapshot2 = listEntries$2();
1944
1944
  for (const listener of listeners$1) {
1945
1945
  listener(snapshot2);
1946
1946
  }
@@ -1949,6 +1949,17 @@ function getState$1() {
1949
1949
  load$3();
1950
1950
  return { entries: [...state$4.entries] };
1951
1951
  }
1952
+ function listEntries$2(offset = 0, limit = 200) {
1953
+ load$3();
1954
+ const safeOffset = Math.max(0, Math.floor(offset));
1955
+ const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
1956
+ return {
1957
+ entries: state$4.entries.slice(safeOffset, safeOffset + safeLimit),
1958
+ offset: safeOffset,
1959
+ limit: safeLimit,
1960
+ total: state$4.entries.length
1961
+ };
1962
+ }
1952
1963
  function subscribe$1(listener) {
1953
1964
  listeners$1.add(listener);
1954
1965
  return () => {
@@ -2851,6 +2862,7 @@ class TabManager {
2851
2862
  pageLoadCallback = null;
2852
2863
  securityStateCallback = null;
2853
2864
  closedTabs = [];
2865
+ lastSessionSignature = "";
2854
2866
  MAX_CLOSED_TABS = 20;
2855
2867
  isPrivate;
2856
2868
  sessionPartition;
@@ -2895,7 +2907,7 @@ class TabManager {
2895
2907
  this.window.contentView.addChildView(tab.view);
2896
2908
  if (background) {
2897
2909
  tab.view.setBounds({ x: 0, y: 0, width: 0, height: 0 });
2898
- this.broadcastState();
2910
+ this.broadcastState({ persistSession: true });
2899
2911
  } else {
2900
2912
  this.switchTab(id);
2901
2913
  }
@@ -2910,7 +2922,7 @@ class TabManager {
2910
2922
  }
2911
2923
  }
2912
2924
  this.activeTabId = id;
2913
- this.broadcastState();
2925
+ this.broadcastState({ persistSession: true });
2914
2926
  }
2915
2927
  closeTab(id) {
2916
2928
  const tab = this.tabs.get(id);
@@ -2942,7 +2954,7 @@ class TabManager {
2942
2954
  this.createTab();
2943
2955
  }
2944
2956
  } else {
2945
- this.broadcastState();
2957
+ this.broadcastState({ persistSession: true });
2946
2958
  }
2947
2959
  }
2948
2960
  navigateTab(id, url, postBody) {
@@ -2989,7 +3001,7 @@ class TabManager {
2989
3001
  } else {
2990
3002
  this.order.splice(firstNonPinned, 0, id);
2991
3003
  }
2992
- this.broadcastState();
3004
+ this.broadcastState({ persistSession: true });
2993
3005
  }
2994
3006
  unpinTab(id) {
2995
3007
  const tab = this.tabs.get(id);
@@ -3002,7 +3014,7 @@ class TabManager {
3002
3014
  } else {
3003
3015
  this.order.splice(firstNonPinned, 0, id);
3004
3016
  }
3005
- this.broadcastState();
3017
+ this.broadcastState({ persistSession: true });
3006
3018
  }
3007
3019
  createGroupFromTab(id, options) {
3008
3020
  const tab = this.tabs.get(id);
@@ -3026,7 +3038,7 @@ class TabManager {
3026
3038
  const previousGroupId = tab.state.groupId;
3027
3039
  tab.setGroup(groupId);
3028
3040
  this.removeGroupIfEmpty(previousGroupId);
3029
- this.broadcastState();
3041
+ this.broadcastState({ persistSession: true });
3030
3042
  }
3031
3043
  removeTabFromGroup(id) {
3032
3044
  const tab = this.tabs.get(id);
@@ -3034,20 +3046,20 @@ class TabManager {
3034
3046
  const groupId = tab.state.groupId;
3035
3047
  tab.setGroup(void 0);
3036
3048
  this.removeGroupIfEmpty(groupId);
3037
- this.broadcastState();
3049
+ this.broadcastState({ persistSession: true });
3038
3050
  }
3039
3051
  toggleGroupCollapsed(groupId) {
3040
3052
  const group = this.tabGroups.get(groupId);
3041
3053
  if (!group) return null;
3042
3054
  group.collapsed = !group.collapsed;
3043
- this.broadcastState();
3055
+ this.broadcastState({ persistSession: true });
3044
3056
  return group.collapsed;
3045
3057
  }
3046
3058
  setGroupColor(groupId, color) {
3047
3059
  const group = this.tabGroups.get(groupId);
3048
3060
  if (!group || !TAB_GROUP_COLORS.includes(color)) return;
3049
3061
  group.color = color;
3050
- this.broadcastState();
3062
+ this.broadcastState({ persistSession: true });
3051
3063
  }
3052
3064
  toggleMuted(id) {
3053
3065
  return this.tabs.get(id)?.toggleMuted() ?? null;
@@ -3207,7 +3219,7 @@ class TabManager {
3207
3219
  this.order = [];
3208
3220
  this.tabGroups.clear();
3209
3221
  this.activeTabId = null;
3210
- this.broadcastState();
3222
+ this.broadcastState({ persistSession: true });
3211
3223
  }
3212
3224
  lastReapply = /* @__PURE__ */ new Map();
3213
3225
  reapplyHighlights(url, wc) {
@@ -3340,6 +3352,20 @@ class TabManager {
3340
3352
  }
3341
3353
  this.tabGroups.delete(groupId);
3342
3354
  }
3355
+ getSessionSignature(states) {
3356
+ return JSON.stringify({
3357
+ activeTabId: this.activeTabId,
3358
+ tabs: states.map((state2) => ({
3359
+ id: state2.id,
3360
+ url: state2.url || "about:blank",
3361
+ adBlockingEnabled: state2.adBlockingEnabled,
3362
+ isPinned: state2.isPinned,
3363
+ groupId: state2.groupId,
3364
+ groupName: state2.groupName,
3365
+ groupColor: state2.groupColor
3366
+ }))
3367
+ });
3368
+ }
3343
3369
  async removeHighlightMarksForText(wc, text) {
3344
3370
  await wc.executeJavaScript(
3345
3371
  `(function() {
@@ -3358,9 +3384,12 @@ class TabManager {
3358
3384
  (err) => logger$m.warn("Failed to remove highlight marks:", err)
3359
3385
  );
3360
3386
  }
3361
- broadcastState() {
3387
+ broadcastState(meta = { persistSession: false }) {
3362
3388
  const states = this.getAllStates();
3363
- this.onStateChange(states, this.activeTabId || "");
3389
+ const sessionSignature = this.getSessionSignature(states);
3390
+ const persistSession = meta.persistSession || sessionSignature !== this.lastSessionSignature;
3391
+ this.lastSessionSignature = sessionSignature;
3392
+ this.onStateChange(states, this.activeTabId || "", { persistSession });
3364
3393
  const activeTab = this.getActiveTab();
3365
3394
  if (activeTab && this.activeTabId && this.securityStateCallback) {
3366
3395
  this.securityStateCallback(this.activeTabId, activeTab.securityState);
@@ -3422,6 +3451,8 @@ const Channels = {
3422
3451
  SETTINGS_HEALTH_GET: "settings:health:get",
3423
3452
  SETTINGS_HEALTH_UPDATE: "settings:health:update",
3424
3453
  MCP_REGENERATE_TOKEN: "mcp:regenerate-token",
3454
+ // Support
3455
+ SUPPORT_SUBMIT_FEEDBACK: "support:submit-feedback",
3425
3456
  // Bookmarks
3426
3457
  BOOKMARKS_GET: "bookmarks:get",
3427
3458
  BOOKMARKS_UPDATE: "bookmarks:update",
@@ -3493,6 +3524,7 @@ const Channels = {
3493
3524
  FIND_IN_PAGE_RESULT: "find:result",
3494
3525
  // Browsing history
3495
3526
  HISTORY_GET: "history:get",
3527
+ HISTORY_LIST: "history:list",
3496
3528
  HISTORY_SEARCH: "history:search",
3497
3529
  HISTORY_CLEAR: "history:clear",
3498
3530
  HISTORY_UPDATE: "history:update",
@@ -3596,7 +3628,7 @@ const MAX_DIFF_BLOCKS = 500;
3596
3628
  function normalizeText$2(value) {
3597
3629
  return value.replace(/\s+/g, " ").trim();
3598
3630
  }
3599
- function truncateText(value, max = 180) {
3631
+ function truncateText$1(value, max = 180) {
3600
3632
  const normalized = normalizeText$2(value);
3601
3633
  if (normalized.length <= max) return normalized;
3602
3634
  return `${normalized.slice(0, max - 3)}...`;
@@ -3770,10 +3802,10 @@ function diffSnapshots(oldSnap, currentContent, currentTitle, currentHeadings) {
3770
3802
  addedBlocks.length,
3771
3803
  removedBlocks.length
3772
3804
  ),
3773
- before: changedPairs[0] ? truncateText(changedPairs[0].before) : removedBlocks[0] ? truncateText(removedBlocks[0]) : void 0,
3774
- after: changedPairs[0] ? truncateText(changedPairs[0].after) : addedBlocks[0] ? truncateText(addedBlocks[0]) : void 0,
3775
- addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item)),
3776
- removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item))
3805
+ before: changedPairs[0] ? truncateText$1(changedPairs[0].before) : removedBlocks[0] ? truncateText$1(removedBlocks[0]) : void 0,
3806
+ after: changedPairs[0] ? truncateText$1(changedPairs[0].after) : addedBlocks[0] ? truncateText$1(addedBlocks[0]) : void 0,
3807
+ addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item)),
3808
+ removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText$1(item))
3777
3809
  });
3778
3810
  }
3779
3811
  }
@@ -4591,6 +4623,15 @@ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
4591
4623
  "research_approve_objectives",
4592
4624
  "research_export_report"
4593
4625
  ]);
4626
+ const PREMIUM_FEATURES = /* @__PURE__ */ new Set([
4627
+ "obsidian",
4628
+ "devtools",
4629
+ "unlimited_iterations",
4630
+ "vault",
4631
+ "human_vault",
4632
+ "automation_kits",
4633
+ "research"
4634
+ ]);
4594
4635
  function isPremium() {
4595
4636
  const { premium } = loadSettings();
4596
4637
  if (premium.status !== "active" && premium.status !== "trialing") {
@@ -4629,6 +4670,22 @@ function resetPremium() {
4629
4670
  function isToolGated(toolName) {
4630
4671
  return PREMIUM_TOOLS.has(toolName) && !isPremium();
4631
4672
  }
4673
+ function isFeatureGated(featureName) {
4674
+ return PREMIUM_FEATURES.has(featureName) && !isPremium();
4675
+ }
4676
+ function getPremiumToolGateMessage(toolName) {
4677
+ return `This tool (${toolName}) requires Vessel Premium. Upgrade at Settings > Premium to unlock screenshot, session management, workflow tracking, and more.`;
4678
+ }
4679
+ function assertToolUnlocked(toolName) {
4680
+ if (isToolGated(toolName)) {
4681
+ throw new Error(getPremiumToolGateMessage(toolName));
4682
+ }
4683
+ }
4684
+ function assertFeatureUnlocked(featureName, featureLabel = featureName) {
4685
+ if (isFeatureGated(featureName)) {
4686
+ throw new Error(`${featureLabel} requires Vessel Premium.`);
4687
+ }
4688
+ }
4632
4689
  async function getCheckoutUrl(email) {
4633
4690
  try {
4634
4691
  const params = new URLSearchParams();
@@ -5320,6 +5377,17 @@ const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
5320
5377
  const MUTATION_SETTLE_AFTER_MS = 1500;
5321
5378
  const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
5322
5379
  const logger$k = createLogger("Extractor");
5380
+ const EXTRACTION_CACHE_TTL_MS = 1500;
5381
+ const MAX_EXTRACTION_CACHE_ENTRIES = 50;
5382
+ const extractionCache = /* @__PURE__ */ new Map();
5383
+ function invalidateExtractionCache(webContents) {
5384
+ const prefix = `${webContents.id}:`;
5385
+ for (const key2 of extractionCache.keys()) {
5386
+ if (key2.startsWith(prefix)) {
5387
+ extractionCache.delete(key2);
5388
+ }
5389
+ }
5390
+ }
5323
5391
  const EMPTY_PAGE_CONTENT = {
5324
5392
  title: "",
5325
5393
  content: "",
@@ -6206,9 +6274,14 @@ async function extractContentInner(webContents) {
6206
6274
  );
6207
6275
  }
6208
6276
  async function extractContent(webContents) {
6277
+ const cacheKey = `${webContents.id}:${webContents.getURL() || ""}`;
6278
+ const cached = extractionCache.get(cacheKey);
6279
+ if (cached && Date.now() - cached.capturedAt < EXTRACTION_CACHE_TTL_MS) {
6280
+ return structuredClone(cached.content);
6281
+ }
6209
6282
  try {
6210
6283
  const timeoutMs = await estimateExtractionTimeout(webContents);
6211
- return await Promise.race([
6284
+ const content = await Promise.race([
6212
6285
  extractContentInner(webContents),
6213
6286
  new Promise(
6214
6287
  (_, reject) => setTimeout(
@@ -6217,6 +6290,15 @@ async function extractContent(webContents) {
6217
6290
  )
6218
6291
  )
6219
6292
  ]);
6293
+ extractionCache.set(cacheKey, {
6294
+ capturedAt: Date.now(),
6295
+ content: structuredClone(content)
6296
+ });
6297
+ if (extractionCache.size > MAX_EXTRACTION_CACHE_ENTRIES) {
6298
+ const oldestKey = extractionCache.keys().next().value;
6299
+ if (oldestKey) extractionCache.delete(oldestKey);
6300
+ }
6301
+ return content;
6220
6302
  } catch (err) {
6221
6303
  const url = webContents.getURL() || "";
6222
6304
  let domain = "unknown";
@@ -6311,6 +6393,7 @@ function attachDestroyCleanup(wc) {
6311
6393
  const MAX_PERSISTED_DIFF_BURSTS = 50;
6312
6394
  const MAX_HISTORY_DAYS = 30;
6313
6395
  const SAVE_DEBOUNCE_MS$2 = 500;
6396
+ const BACKGROUND_DIFF_CAPTURE_DELAY_MS = 15e3;
6314
6397
  function getHistoryFilePath() {
6315
6398
  return path.join(electron.app.getPath("userData"), "vessel-page-diff-history.json");
6316
6399
  }
@@ -6443,7 +6526,7 @@ function computeNextSnapshotDueAt(wcId, now, delayMs) {
6443
6526
  const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
6444
6527
  return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
6445
6528
  }
6446
- function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
6529
+ function scheduleTimerAt(wc, sendToRendererViews, dueAt, options = {}) {
6447
6530
  attachDestroyCleanup(wc);
6448
6531
  const wcId = wc.id;
6449
6532
  const existing = pendingPageSnapshotTimers.get(wcId);
@@ -6451,14 +6534,24 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
6451
6534
  const timer = setTimeout(() => {
6452
6535
  cleanupTimersForWcId(wcId);
6453
6536
  if (wc.isDestroyed()) return;
6537
+ if (options.isActive && !options.isActive()) {
6538
+ scheduleTimerAt(
6539
+ wc,
6540
+ sendToRendererViews,
6541
+ Date.now() + BACKGROUND_DIFF_CAPTURE_DELAY_MS,
6542
+ options
6543
+ );
6544
+ return;
6545
+ }
6454
6546
  lastMutationSnapshotAt.set(wcId, Date.now());
6455
6547
  void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
6456
6548
  }, Math.max(0, dueAt - Date.now()));
6457
6549
  pendingPageSnapshotTimers.set(wcId, timer);
6458
6550
  pendingPageSnapshotDueAt.set(wcId, dueAt);
6459
6551
  }
6460
- function notePageMutationActivity(wc, sendToRendererViews) {
6552
+ function notePageMutationActivity(wc, sendToRendererViews, options = {}) {
6461
6553
  if (wc.isDestroyed()) return;
6554
+ if (options.isActive && !options.isActive()) return;
6462
6555
  const wcId = wc.id;
6463
6556
  const now = Date.now();
6464
6557
  lastMutationActivityAt.set(wcId, now);
@@ -6466,18 +6559,19 @@ function notePageMutationActivity(wc, sendToRendererViews) {
6466
6559
  if (existingDueAt == null) return;
6467
6560
  const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
6468
6561
  if (nextDueAt <= existingDueAt) return;
6469
- scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
6562
+ scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
6470
6563
  }
6471
- function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
6564
+ function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0, options = {}) {
6472
6565
  if (wc.isDestroyed()) return;
6473
6566
  const wcId = wc.id;
6474
6567
  const now = Date.now();
6475
- const nextDueAt = computeNextSnapshotDueAt(wcId, now, delayMs);
6568
+ const effectiveDelayMs = options.isActive && !options.isActive() ? Math.max(delayMs, BACKGROUND_DIFF_CAPTURE_DELAY_MS) : delayMs;
6569
+ const nextDueAt = computeNextSnapshotDueAt(wcId, now, effectiveDelayMs);
6476
6570
  const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
6477
6571
  if (existingDueAt != null && existingDueAt >= nextDueAt) {
6478
6572
  return;
6479
6573
  }
6480
- scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
6574
+ scheduleTimerAt(wc, sendToRendererViews, nextDueAt, options);
6481
6575
  }
6482
6576
  function enableClipboardShortcuts(view) {
6483
6577
  view.webContents.on("before-input-event", (event, input) => {
@@ -6693,6 +6787,8 @@ function createMainWindow(onTabStateChange) {
6693
6787
  sidebarView.webContents.send(channel, ...args);
6694
6788
  };
6695
6789
  tabManager.onPageLoad((url, wc) => {
6790
+ const activeWc = tabManager.getActiveTab()?.view.webContents;
6791
+ if (activeWc?.id !== wc.id) return;
6696
6792
  void capturePageSnapshot(url, wc, sendToRendererViews);
6697
6793
  });
6698
6794
  const state2 = {
@@ -7459,6 +7555,36 @@ const PROVIDERS = {
7459
7555
  apiKeyHint: "Optional — only if your endpoint requires authentication"
7460
7556
  }
7461
7557
  };
7558
+ function parseModelSizeInBillions(model) {
7559
+ const match = model.toLowerCase().match(/(?:^|[:/_\-\s])(\d+(?:\.\d+)?)b(?:$|[:/_\-\s])/i);
7560
+ if (!match) return null;
7561
+ const parsed = Number(match[1]);
7562
+ return Number.isFinite(parsed) ? parsed : null;
7563
+ }
7564
+ function isLoopbackBaseUrl(baseUrl) {
7565
+ if (!baseUrl) return false;
7566
+ try {
7567
+ const url = new URL(baseUrl);
7568
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
7569
+ } catch {
7570
+ return false;
7571
+ }
7572
+ }
7573
+ function resolveAgentToolProfile(config) {
7574
+ const providerId = config.id;
7575
+ const isLocalProvider = providerId === "ollama" || providerId === "llama_cpp" || providerId === "custom" && isLoopbackBaseUrl(config.baseUrl);
7576
+ if (!isLocalProvider) return "default";
7577
+ const sizeInBillions = parseModelSizeInBillions(config.model);
7578
+ if (sizeInBillions === null) {
7579
+ return "compact";
7580
+ }
7581
+ return sizeInBillions <= 14 ? "compact" : "default";
7582
+ }
7583
+ const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
7584
+ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
7585
+ const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
7586
+ const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
7587
+ const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
7462
7588
  const SAFE_TOOL_ALIASES = {
7463
7589
  goto_url: "navigate",
7464
7590
  go_to_url: "navigate",
@@ -7523,399 +7649,109 @@ function normalizeToolAlias(name) {
7523
7649
  }
7524
7650
  return name;
7525
7651
  }
7526
- function parseModelSizeInBillions(model) {
7527
- const match = model.toLowerCase().match(/(?:^|[:/_\-\s])(\d+(?:\.\d+)?)b(?:$|[:/_\-\s])/i);
7528
- if (!match) return null;
7529
- const parsed = Number(match[1]);
7530
- return Number.isFinite(parsed) ? parsed : null;
7652
+ function stableToolSignature(name, args) {
7653
+ const canonicalArgs = canonicalizeArgsForTool(name, args);
7654
+ const sortedEntries = Object.entries(canonicalArgs).sort(
7655
+ ([left], [right]) => left.localeCompare(right)
7656
+ );
7657
+ return JSON.stringify([name, sortedEntries]);
7531
7658
  }
7532
- function isLoopbackBaseUrl(baseUrl) {
7533
- if (!baseUrl) return false;
7659
+ function normalizeToolToken(value) {
7660
+ return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
7661
+ }
7662
+ function canonicalizeUrlLike(value) {
7534
7663
  try {
7535
- const url = new URL(baseUrl);
7536
- return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
7664
+ const url = new URL(value.trim());
7665
+ if (url.protocol === "http:" || url.protocol === "https:") {
7666
+ url.hostname = url.hostname.replace(/^www\./, "");
7667
+ url.hash = "";
7668
+ if (url.pathname.endsWith("/") && url.pathname !== "/") {
7669
+ url.pathname = url.pathname.replace(/\/+$/, "");
7670
+ }
7671
+ return url.toString();
7672
+ }
7537
7673
  } catch {
7538
- return false;
7539
7674
  }
7675
+ return value.trim();
7540
7676
  }
7541
- function resolveAgentToolProfile(config) {
7542
- const providerId = config.id;
7543
- const isLocalProvider = providerId === "ollama" || providerId === "llama_cpp" || providerId === "custom" && isLoopbackBaseUrl(config.baseUrl);
7544
- if (!isLocalProvider) return "default";
7545
- const sizeInBillions = parseModelSizeInBillions(config.model);
7546
- if (sizeInBillions === null) {
7547
- return "compact";
7677
+ function toLikelyUrl(value) {
7678
+ const trimmed = value.trim().replace(/^["']|["']$/g, "");
7679
+ if (!trimmed) return null;
7680
+ if (/^https?:\/\//i.test(trimmed)) return trimmed;
7681
+ if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
7682
+ return `https://${trimmed}`;
7548
7683
  }
7549
- return sizeInBillions <= 14 ? "compact" : "default";
7550
- }
7551
- const MAX_CONTEXT_CONTENT_LENGTH = 6e4;
7552
- const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
7553
- const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
7554
- const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
7555
- const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
7556
- const logger$j = createLogger("OpenAIProvider");
7557
- function shouldDebugAgentLoop() {
7558
- const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
7559
- return value === "1" || value === "true";
7560
- }
7561
- function previewDebugValue(value, maxLength = 800) {
7562
- const normalized = value.replace(/\s+/g, " ").trim();
7563
- if (normalized.length <= maxLength) return normalized;
7564
- return `${normalized.slice(0, maxLength)}…`;
7565
- }
7566
- function previewToolDebugContent(content) {
7567
- return previewDebugValue(content, 500);
7568
- }
7569
- function toOpenAITools(tools) {
7570
- return tools.map((t) => ({
7571
- type: "function",
7572
- function: {
7573
- name: t.name,
7574
- description: t.description ?? "",
7575
- parameters: t.input_schema
7576
- }
7577
- }));
7578
- }
7579
- function agentTemperatureForProfile(profile) {
7580
- return profile === "compact" ? 0.2 : void 0;
7581
- }
7582
- function modelLikelySupportsOpenAIReasoningEffort(model) {
7583
- return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
7684
+ return null;
7584
7685
  }
7585
- function toOpenAIReasoningEffort(effort, providerId, model) {
7586
- const supportsReasoningParam = providerId === "openrouter" || providerId === "custom" || providerId === "openai" && modelLikelySupportsOpenAIReasoningEffort(model);
7587
- if (!supportsReasoningParam) return void 0;
7588
- switch (effort) {
7589
- case "off":
7590
- if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
7591
- return void 0;
7686
+ function scalarArgsForTool(name, scalar) {
7687
+ const trimmed = scalar.trim();
7688
+ if (!trimmed) return null;
7689
+ if (name === "navigate") {
7690
+ const url = toLikelyUrl(trimmed);
7691
+ return url ? { url } : null;
7692
+ }
7693
+ if (name === "search") {
7694
+ return { query: trimmed.replace(/^["']|["']$/g, "") };
7695
+ }
7696
+ if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
7697
+ return { text: trimmed.replace(/^["']|["']$/g, "") };
7698
+ }
7699
+ if (name === "read_page") {
7700
+ const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
7701
+ if (mode) return { mode };
7702
+ }
7703
+ if (name === "save_bookmark") {
7704
+ const url = toLikelyUrl(trimmed);
7705
+ if (url) return { url };
7706
+ const lastSpace = trimmed.lastIndexOf(" ");
7707
+ if (lastSpace > 0) {
7708
+ const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
7709
+ if (maybeUrl) {
7710
+ return {
7711
+ url: maybeUrl,
7712
+ title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "")
7713
+ };
7592
7714
  }
7593
- return "none";
7594
- case "low":
7595
- return "low";
7596
- case "medium":
7597
- return "medium";
7598
- case "high":
7599
- return "high";
7600
- case "max":
7601
- return "xhigh";
7602
- default:
7603
- return void 0;
7715
+ }
7604
7716
  }
7717
+ return null;
7605
7718
  }
7606
- function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
7607
- if (profile !== "compact") return null;
7608
- const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
7609
- const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
7610
- return {
7611
- role: "user",
7612
- content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
7613
- 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 ? `
7614
- ${stateReminder}` : "") + (phaseReminder ? `
7615
- ${phaseReminder}` : "")
7616
- };
7617
- }
7618
- function extractSingleGoalDomain(goal) {
7619
- const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
7620
- if (!matches || matches.length !== 1) return null;
7621
- return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
7719
+ function firstStringArg(args, keys) {
7720
+ for (const key2 of keys) {
7721
+ const value = args[key2];
7722
+ if (typeof value === "string" && value.trim()) {
7723
+ return value.trim();
7724
+ }
7725
+ }
7726
+ return null;
7622
7727
  }
7623
- function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
7624
- const phaseReminder = buildPhaseReminder(userMessage, assistantText);
7625
- const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
7626
- const goalDomain = extractSingleGoalDomain(userMessage);
7627
- const latest = (latestToolResultPreview || "").toLowerCase();
7628
- const assistant = assistantText.toLowerCase();
7629
- const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
7630
- const lines = [
7631
- `The task is still in progress: ${userMessage}`,
7632
- `Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
7633
- ];
7634
- if (alreadyOnGoalSite) {
7635
- lines.push(
7636
- `You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
7637
- );
7728
+ function normalizeElementTargetArgs(args) {
7729
+ const normalized = { ...args };
7730
+ if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
7731
+ normalized.index = Number(normalized.index.trim());
7638
7732
  }
7639
- if (stateReminder) {
7640
- lines.push(stateReminder);
7733
+ if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
7734
+ const selector = firstStringArg(normalized, [
7735
+ "cssSelector",
7736
+ "css_selector",
7737
+ "querySelector",
7738
+ "query_selector"
7739
+ ]);
7740
+ if (selector) normalized.selector = selector;
7641
7741
  }
7642
- if (phaseReminder) {
7643
- lines.push(phaseReminder);
7644
- }
7645
- return lines.join("\n");
7646
- }
7647
- function buildPhaseReminder(userMessage, assistantText) {
7648
- const goal = userMessage.toLowerCase();
7649
- const text = assistantText.toLowerCase();
7650
- if (!goal || !text) return "";
7651
- const wantsCart = /\b(cart|bag|basket|checkout)\b/.test(goal);
7652
- const wantsExplanation = /\b(explain|reason|why)\b/.test(goal);
7653
- const wantsBookRecommendations = /\b(book|books|recommend|recommended|interesting|novel|fiction|nonfiction)\b/.test(
7654
- goal
7655
- );
7656
- const hasFiveItemList = /(?:^|\n)\s*1\./.test(assistantText) && /(?:^|\n)\s*2\./.test(assistantText) && /(?:^|\n)\s*3\./.test(assistantText) && /(?:^|\n)\s*4\./.test(assistantText) && /(?:^|\n)\s*5\./.test(assistantText);
7657
- const selectedItems = hasFiveItemList || /i(?:'| a)?ve chosen/.test(text) || /i have chosen/.test(text) || /i selected/.test(text) || /here are the books/i.test(assistantText) || /here are the items/i.test(assistantText);
7658
- const intendsCart = /next[, ]+i will add/.test(text) || /i(?:'| a)?ll start with the first/.test(text) || /proceed systematically/.test(text) || /add (these|the chosen|the selected).*(cart|bag|basket)/.test(text);
7659
- const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
7660
- text
7661
- );
7662
- const explanationDone = /here is why i chose/.test(text) || /here are my reasons/.test(text) || /reason:/.test(text) || /reasons:/.test(text) || /why i chose/.test(text);
7663
- const listingLoopSignals = /page contains a list of books|book listings|book cards|visible book|load more results|scroll further|scroll down|inspect the visible|focus on the book listings|targeting the book images|limited to interactive elements|identify the book cards|click one of the visible book/.test(
7664
- text
7665
- );
7666
- const missedResultsSignals = /visible_only mode did not return specific book titles|did not yield a book title link|did not yield specific book titles|navigation links rather than book titles|inspect elements did not yield|inspect the page to find a specific book title|inspect the page to locate a book title|book title link from the search results/.test(
7667
- text
7668
- );
7669
- const falseCartSuccessSignals = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
7670
- text
7671
- ) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
7672
- text
7673
- );
7674
- const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
7675
- text
7676
- ) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
7677
- text
7678
- );
7679
- const selectedItemsRestartSignals = /navigate back to the search results page|search for ".*" directly in the search box|search for .* directly|page structure has shifted|refresh the page|restart search/.test(
7680
- text
7681
- );
7682
- const multiClickSelectionSignals = /i(?:'| a)?ll start by clicking on the following books|i will start by clicking on the following books|i will click on the following books|clicked on five different book titles|clicked on \d+ different book titles|clicking through the selected titles|click each of the selected titles/.test(
7683
- text
7684
- );
7685
- const staleSelectionSignals = /cannot locate the elements to click|page structure is not being reliably captured|specific titles failed|page may have changed|stale-index|no visible area|not visible/.test(
7686
- text
7687
- );
7688
- const intermediateCartDialogSignals = /(added to cart|has been added to the cart|cart confirmation)/.test(text) && /(continue shopping|search results page|return to the search results page|back button|go back)/.test(
7689
- text
7690
- ) && !/(all requested books are now in the cart|all 5 books are now in the cart|5 of 5 requested books are now in the cart)/.test(
7691
- text
7692
- );
7693
- if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
7694
- return `Progress reminder: If product results or primary results are already visible, do not keep rereading or rescrolling the same listing page. Open one promising result now. On the detail page, add that item to the cart before returning for the next unseen result.`;
7695
- }
7696
- if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
7697
- return `Progress reminder: On a results page, do not use visible_only or generic inspect_element to hunt product results. Call read_page(mode="results_only") once. If Primary Results are shown, click a listed result directly.`;
7698
- }
7699
- if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
7700
- return `Progress reminder: Do not assume an item was added just because its product page is open or you inspected it. Only treat the cart step as complete after a successful Add to Cart click followed by cart confirmation, View Cart, Continue Shopping, or the cart page itself.`;
7701
- }
7702
- if (wantsCart && skippedSingleResultSignals && !selectedItems) {
7703
- return `Progress reminder: Do not skip to a new query just because the match is not exact. If the results page shows even one plausible product result, inspect or click that result before concluding there is no match.`;
7704
- }
7705
- if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
7706
- return `Progress reminder: After an Add to Cart success, prefer the cart-confirmation dialog action Continue Shopping while more items remain. Do not click View Cart or Go to Basket yet, and do not use the browser back button while the dialog is still open.`;
7707
- }
7708
- if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
7709
- return `Progress reminder: The chosen items are already decided. Do not restart search, refresh the results page, or navigate back to browse again unless a specific saved link fails. Use the current results page or the chosen result links you already have: open one chosen result, add it to the cart, confirm success, then continue to the next chosen result.`;
7710
- }
7711
- if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
7712
- return `Progress reminder: Do not batch-click multiple results from a listing or category page. Open exactly one visible result, finish that item's Add to Cart flow, confirm success, then use Continue Shopping or go back once to choose the next unseen result. If a remembered label or index fails, trust the latest page state and refresh it with one read_page call before continuing.`;
7713
- }
7714
- if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
7715
- return `Progress reminder: You already selected the requested items. Do not restart browsing or searching unless a specific cart step fails. Continue adding the selected items to the cart one by one. Use the chosen result links you already have, add one selected item to the cart, confirm success, then continue to the next one. Do not click multiple chosen results in a row from the same listing page.`;
7716
- }
7717
- if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
7718
- return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
7719
- }
7720
- return "";
7721
- }
7722
- function buildLatestStateReminder(toolResultPreview) {
7723
- const text = toolResultPreview.trim();
7724
- if (!text) return "";
7725
- const stateMatch = text.match(
7726
- /\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
7727
- );
7728
- if (stateMatch) {
7729
- const url = stateMatch[1]?.trim();
7730
- const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
7731
- if (url) {
7732
- return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
7733
- }
7734
- }
7735
- const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
7736
- const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
7737
- if (structuredUrl) {
7738
- return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
7739
- }
7740
- const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
7741
- const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
7742
- if (navigatedUrl) {
7743
- return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
7744
- }
7745
- return "";
7746
- }
7747
- function shouldRecoverCompactStall(text, userMessage) {
7748
- const trimmed = text.trim().toLowerCase();
7749
- if (!trimmed) return true;
7750
- if (trimmed.length <= 160 && trimmed.includes("?")) return true;
7751
- if (userMessage && buildPhaseReminder(userMessage, text)) {
7752
- return true;
7753
- }
7754
- const repetitivePlanningSignals = [
7755
- "next step:",
7756
- "i will now inspect",
7757
- "i will now read",
7758
- "i will now click",
7759
- "i'll use readpage",
7760
- "i'll use read_page",
7761
- "i'll start by clicking",
7762
- "i have clicked on five different book titles",
7763
- "clicked on five different book titles",
7764
- "i'll begin with",
7765
- "if the selection is unclear"
7766
- ];
7767
- if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
7768
- return true;
7769
- }
7770
- const falseCartSuccessWithoutConfirmation = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
7771
- trimmed
7772
- ) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
7773
- trimmed
7774
- );
7775
- if (falseCartSuccessWithoutConfirmation) {
7776
- return true;
7777
- }
7778
- const completionSignals = [
7779
- "i found",
7780
- "i chose",
7781
- "i selected",
7782
- "i added",
7783
- "here are",
7784
- "these are",
7785
- "recommendations",
7786
- "reasoning",
7787
- "why i chose",
7788
- "added them to the cart"
7789
- ];
7790
- if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
7791
- return false;
7792
- }
7793
- return [
7794
- "what are you hoping",
7795
- "what would you like",
7796
- "how can i help",
7797
- "let me know",
7798
- "are you looking for",
7799
- "just browsing",
7800
- "i need to",
7801
- "i will",
7802
- "i'll",
7803
- "since i cannot see",
7804
- "since i can't see",
7805
- "cannot see the current page",
7806
- "scroll down to",
7807
- "load more results",
7808
- "as placeholders",
7809
- "would you like me to proceed",
7810
- "action:",
7811
- "one moment",
7812
- "i will now navigate",
7813
- "navigating to ",
7814
- "this will take me",
7815
- "i will use the browser"
7816
- ].some((pattern) => trimmed.includes(pattern));
7817
- }
7818
- function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
7819
- return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
7820
- }
7821
- function stableToolSignature(name, args) {
7822
- const canonicalArgs = canonicalizeArgsForTool(name, args);
7823
- const sortedEntries = Object.entries(canonicalArgs).sort(
7824
- ([left], [right]) => left.localeCompare(right)
7825
- );
7826
- return JSON.stringify([name, sortedEntries]);
7827
- }
7828
- function normalizeToolToken(value) {
7829
- return value.trim().toLowerCase().replace(/[.\s/-]+/g, "_");
7830
- }
7831
- function canonicalizeUrlLike(value) {
7832
- try {
7833
- const url = new URL(value.trim());
7834
- if (url.protocol === "http:" || url.protocol === "https:") {
7835
- url.hostname = url.hostname.replace(/^www\./, "");
7836
- url.hash = "";
7837
- if (url.pathname.endsWith("/") && url.pathname !== "/") {
7838
- url.pathname = url.pathname.replace(/\/+$/, "");
7839
- }
7840
- return url.toString();
7841
- }
7842
- } catch {
7843
- }
7844
- return value.trim();
7845
- }
7846
- function toLikelyUrl(value) {
7847
- const trimmed = value.trim().replace(/^["']|["']$/g, "");
7848
- if (!trimmed) return null;
7849
- if (/^https?:\/\//i.test(trimmed)) return trimmed;
7850
- if (/^[a-z0-9-]+\.(com|org|net|io|dev|app|ai|co|edu|gov)(\/\S*)?$/i.test(trimmed)) {
7851
- return `https://${trimmed}`;
7852
- }
7853
- return null;
7854
- }
7855
- function scalarArgsForTool(name, scalar) {
7856
- const trimmed = scalar.trim();
7857
- if (!trimmed) return null;
7858
- if (name === "navigate") {
7859
- const url = toLikelyUrl(trimmed);
7860
- return url ? { url } : null;
7861
- }
7862
- if (name === "search") {
7863
- return { query: trimmed.replace(/^["']|["']$/g, "") };
7864
- }
7865
- if (name === "click" || name === "inspect_element" || name === "scroll_to_element") {
7866
- return { text: trimmed.replace(/^["']|["']$/g, "") };
7867
- }
7868
- if (name === "read_page") {
7869
- const mode = trimmed.replace(/^["']|["']$/g, "").toLowerCase();
7870
- if (mode) return { mode };
7871
- }
7872
- if (name === "save_bookmark") {
7873
- const url = toLikelyUrl(trimmed);
7874
- if (url) return { url };
7875
- const lastSpace = trimmed.lastIndexOf(" ");
7876
- if (lastSpace > 0) {
7877
- const maybeUrl = toLikelyUrl(trimmed.slice(lastSpace + 1));
7878
- if (maybeUrl) return { url: maybeUrl, title: trimmed.slice(0, lastSpace).replace(/^["']|["']$/g, "") };
7879
- }
7880
- }
7881
- return null;
7882
- }
7883
- function firstStringArg(args, keys) {
7884
- for (const key2 of keys) {
7885
- const value = args[key2];
7886
- if (typeof value === "string" && value.trim()) {
7887
- return value.trim();
7888
- }
7889
- }
7890
- return null;
7891
- }
7892
- function normalizeElementTargetArgs(args) {
7893
- const normalized = { ...args };
7894
- if (typeof normalized.index === "string" && /^\d+$/.test(normalized.index.trim())) {
7895
- normalized.index = Number(normalized.index.trim());
7896
- }
7897
- if (typeof normalized.selector !== "string" || !normalized.selector.trim()) {
7898
- const selector = firstStringArg(normalized, [
7899
- "cssSelector",
7900
- "css_selector",
7901
- "querySelector",
7902
- "query_selector"
7903
- ]);
7904
- if (selector) normalized.selector = selector;
7905
- }
7906
- if (typeof normalized.text !== "string" || !normalized.text.trim()) {
7907
- const text = firstStringArg(normalized, [
7908
- "label",
7909
- "title",
7910
- "name",
7911
- "target",
7912
- "element",
7913
- "linkText",
7914
- "link_text",
7915
- "ariaLabel",
7916
- "aria_label"
7917
- ]);
7918
- if (text) normalized.text = text;
7742
+ if (typeof normalized.text !== "string" || !normalized.text.trim()) {
7743
+ const text = firstStringArg(normalized, [
7744
+ "label",
7745
+ "title",
7746
+ "name",
7747
+ "target",
7748
+ "element",
7749
+ "linkText",
7750
+ "link_text",
7751
+ "ariaLabel",
7752
+ "aria_label"
7753
+ ]);
7754
+ if (text) normalized.text = text;
7919
7755
  }
7920
7756
  return normalized;
7921
7757
  }
@@ -8067,21 +7903,13 @@ function resolveToolCallName(rawName, args, availableToolNames) {
8067
7903
  if (availableToolNames.has("search") && (/search|find|lookup|query/.test(normalized) || normalized === "google" || normalized.startsWith("google_"))) {
8068
7904
  return "search";
8069
7905
  }
8070
- if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
8071
- return "scroll";
8072
- }
8073
- if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
8074
- return "read_page";
8075
- }
8076
- return aliased;
8077
- }
8078
- function logAgentLoopDebug(payload) {
8079
- if (!shouldDebugAgentLoop()) return;
8080
- try {
8081
- logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
8082
- } catch (err) {
8083
- logger$j.warn("Failed to serialize debug payload:", err);
7906
+ if (availableToolNames.has("scroll") && /scroll|page_?down|page_?up/.test(normalized)) {
7907
+ return "scroll";
7908
+ }
7909
+ if (availableToolNames.has("read_page") && /read|scan|inspect|analy[sz]e|summari[sz]e/.test(normalized)) {
7910
+ return "read_page";
8084
7911
  }
7912
+ return aliased;
8085
7913
  }
8086
7914
  function recoverTextEncodedToolCalls(text, availableToolNames) {
8087
7915
  const trimmed = text.trim();
@@ -8198,7 +8026,280 @@ function recoverNarratedActionToolCalls(text, availableToolNames) {
8198
8026
  });
8199
8027
  return recovered;
8200
8028
  }
8201
- return recovered;
8029
+ return recovered;
8030
+ }
8031
+ const logger$j = createLogger("OpenAIProvider");
8032
+ function shouldDebugAgentLoop() {
8033
+ const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
8034
+ return value === "1" || value === "true";
8035
+ }
8036
+ function previewDebugValue(value, maxLength = 800) {
8037
+ const normalized = value.replace(/\s+/g, " ").trim();
8038
+ if (normalized.length <= maxLength) return normalized;
8039
+ return `${normalized.slice(0, maxLength)}…`;
8040
+ }
8041
+ function previewToolDebugContent(content) {
8042
+ return previewDebugValue(content, 500);
8043
+ }
8044
+ function toOpenAITools(tools) {
8045
+ return tools.map((t) => ({
8046
+ type: "function",
8047
+ function: {
8048
+ name: t.name,
8049
+ description: t.description ?? "",
8050
+ parameters: t.input_schema
8051
+ }
8052
+ }));
8053
+ }
8054
+ function agentTemperatureForProfile(profile) {
8055
+ return profile === "compact" ? 0.2 : void 0;
8056
+ }
8057
+ function modelLikelySupportsOpenAIReasoningEffort(model) {
8058
+ return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
8059
+ }
8060
+ function toOpenAIReasoningEffort(effort, providerId, model) {
8061
+ const supportsReasoningParam = providerId === "openrouter" || providerId === "custom" || providerId === "openai" && modelLikelySupportsOpenAIReasoningEffort(model);
8062
+ if (!supportsReasoningParam) return void 0;
8063
+ switch (effort) {
8064
+ case "off":
8065
+ if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
8066
+ return void 0;
8067
+ }
8068
+ return "none";
8069
+ case "low":
8070
+ return "low";
8071
+ case "medium":
8072
+ return "medium";
8073
+ case "high":
8074
+ return "high";
8075
+ case "max":
8076
+ return "xhigh";
8077
+ default:
8078
+ return void 0;
8079
+ }
8080
+ }
8081
+ function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
8082
+ if (profile !== "compact") return null;
8083
+ const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
8084
+ const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
8085
+ return {
8086
+ role: "user",
8087
+ content: `[System] Task reminder: Continue working on the user's original request until it is completed: ${userMessage}
8088
+ Do not ask the user what they want next unless the request is genuinely ambiguous or blocked. After navigation or page reads, keep executing the same task.` + (stateReminder ? `
8089
+ ${stateReminder}` : "") + (phaseReminder ? `
8090
+ ${phaseReminder}` : "")
8091
+ };
8092
+ }
8093
+ function extractSingleGoalDomain(goal) {
8094
+ const matches = goal.toLowerCase().match(/\b(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.(?:com|org|net|io|dev|app|ai|co|edu|gov))\b/g);
8095
+ if (!matches || matches.length !== 1) return null;
8096
+ return matches[0].replace(/^https?:\/\//, "").replace(/^www\./, "").toLowerCase();
8097
+ }
8098
+ function buildCompactRecoveryPrompt(userMessage, assistantText, latestToolResultPreview) {
8099
+ const phaseReminder = buildPhaseReminder(userMessage, assistantText);
8100
+ const stateReminder = buildLatestStateReminder(latestToolResultPreview || "");
8101
+ const goalDomain = extractSingleGoalDomain(userMessage);
8102
+ const latest = (latestToolResultPreview || "").toLowerCase();
8103
+ const assistant = assistantText.toLowerCase();
8104
+ const alreadyOnGoalSite = !!goalDomain && (latest.includes(goalDomain) || assistant.includes(`https://${goalDomain}`) || assistant.includes(`https://www.${goalDomain}`));
8105
+ const lines = [
8106
+ `The task is still in progress: ${userMessage}`,
8107
+ `Do not ask the user for permission to continue. Choose the next tool now unless the request is fully complete.`
8108
+ ];
8109
+ if (alreadyOnGoalSite) {
8110
+ lines.push(
8111
+ `You are already on the requested site (${goalDomain}). Do not navigate to the homepage again and do not restart discovery from scratch.`
8112
+ );
8113
+ }
8114
+ if (stateReminder) {
8115
+ lines.push(stateReminder);
8116
+ }
8117
+ if (phaseReminder) {
8118
+ lines.push(phaseReminder);
8119
+ }
8120
+ return lines.join("\n");
8121
+ }
8122
+ function buildPhaseReminder(userMessage, assistantText) {
8123
+ const goal = userMessage.toLowerCase();
8124
+ const text = assistantText.toLowerCase();
8125
+ if (!goal || !text) return "";
8126
+ const wantsCart = /\b(cart|bag|basket|checkout)\b/.test(goal);
8127
+ const wantsExplanation = /\b(explain|reason|why)\b/.test(goal);
8128
+ const wantsBookRecommendations = /\b(book|books|recommend|recommended|interesting|novel|fiction|nonfiction)\b/.test(
8129
+ goal
8130
+ );
8131
+ const hasFiveItemList = /(?:^|\n)\s*1\./.test(assistantText) && /(?:^|\n)\s*2\./.test(assistantText) && /(?:^|\n)\s*3\./.test(assistantText) && /(?:^|\n)\s*4\./.test(assistantText) && /(?:^|\n)\s*5\./.test(assistantText);
8132
+ const selectedItems = hasFiveItemList || /i(?:'| a)?ve chosen/.test(text) || /i have chosen/.test(text) || /i selected/.test(text) || /here are the books/i.test(assistantText) || /here are the items/i.test(assistantText);
8133
+ const intendsCart = /next[, ]+i will add/.test(text) || /i(?:'| a)?ll start with the first/.test(text) || /proceed systematically/.test(text) || /add (these|the chosen|the selected).*(cart|bag|basket)/.test(text);
8134
+ const cartDone = /(added to cart|added them to the cart|cart confirmation|view cart|checkout)/.test(
8135
+ text
8136
+ );
8137
+ const explanationDone = /here is why i chose/.test(text) || /here are my reasons/.test(text) || /reason:/.test(text) || /reasons:/.test(text) || /why i chose/.test(text);
8138
+ const listingLoopSignals = /page contains a list of books|book listings|book cards|visible book|load more results|scroll further|scroll down|inspect the visible|focus on the book listings|targeting the book images|limited to interactive elements|identify the book cards|click one of the visible book/.test(
8139
+ text
8140
+ );
8141
+ const missedResultsSignals = /visible_only mode did not return specific book titles|did not yield a book title link|did not yield specific book titles|navigation links rather than book titles|inspect elements did not yield|inspect the page to find a specific book title|inspect the page to locate a book title|book title link from the search results/.test(
8142
+ text
8143
+ );
8144
+ const falseCartSuccessSignals = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
8145
+ text
8146
+ ) && !/(cart confirmation|view cart|shopping cart|checkout|continue shopping)/.test(
8147
+ text
8148
+ );
8149
+ const skippedSingleResultSignals = /did not yield a direct match|no direct match|no matches|unavailable on powell|out of stock or unavailable/.test(
8150
+ text
8151
+ ) && /proceed to (?:add|search for) the next book|move on to the next book|next book from my list/.test(
8152
+ text
8153
+ );
8154
+ const selectedItemsRestartSignals = /navigate back to the search results page|search for ".*" directly in the search box|search for .* directly|page structure has shifted|refresh the page|restart search/.test(
8155
+ text
8156
+ );
8157
+ const multiClickSelectionSignals = /i(?:'| a)?ll start by clicking on the following books|i will start by clicking on the following books|i will click on the following books|clicked on five different book titles|clicked on \d+ different book titles|clicking through the selected titles|click each of the selected titles/.test(
8158
+ text
8159
+ );
8160
+ const staleSelectionSignals = /cannot locate the elements to click|page structure is not being reliably captured|specific titles failed|page may have changed|stale-index|no visible area|not visible/.test(
8161
+ text
8162
+ );
8163
+ const intermediateCartDialogSignals = /(added to cart|has been added to the cart|cart confirmation)/.test(text) && /(continue shopping|search results page|return to the search results page|back button|go back)/.test(
8164
+ text
8165
+ ) && !/(all requested books are now in the cart|all 5 books are now in the cart|5 of 5 requested books are now in the cart)/.test(
8166
+ text
8167
+ );
8168
+ if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && listingLoopSignals) {
8169
+ return `Progress reminder: If product results or primary results are already visible, do not keep rereading or rescrolling the same listing page. Open one promising result now. On the detail page, add that item to the cart before returning for the next unseen result.`;
8170
+ }
8171
+ if (wantsCart && wantsBookRecommendations && !selectedItems && !cartDone && missedResultsSignals) {
8172
+ return `Progress reminder: On a results page, do not use visible_only or generic inspect_element to hunt product results. Call read_page(mode="results_only") once. If Primary Results are shown, click a listed result directly.`;
8173
+ }
8174
+ if (wantsCart && falseCartSuccessSignals && !selectedItems && !explanationDone) {
8175
+ return `Progress reminder: Do not assume an item was added just because its product page is open or you inspected it. Only treat the cart step as complete after a successful Add to Cart click followed by cart confirmation, View Cart, Continue Shopping, or the cart page itself.`;
8176
+ }
8177
+ if (wantsCart && skippedSingleResultSignals && !selectedItems) {
8178
+ return `Progress reminder: Do not skip to a new query just because the match is not exact. If the results page shows even one plausible product result, inspect or click that result before concluding there is no match.`;
8179
+ }
8180
+ if (wantsCart && intermediateCartDialogSignals && !explanationDone) {
8181
+ return `Progress reminder: After an Add to Cart success, prefer the cart-confirmation dialog action Continue Shopping while more items remain. Do not click View Cart or Go to Basket yet, and do not use the browser back button while the dialog is still open.`;
8182
+ }
8183
+ if (wantsCart && selectedItems && !cartDone && selectedItemsRestartSignals) {
8184
+ return `Progress reminder: The chosen items are already decided. Do not restart search, refresh the results page, or navigate back to browse again unless a specific saved link fails. Use the current results page or the chosen result links you already have: open one chosen result, add it to the cart, confirm success, then continue to the next chosen result.`;
8185
+ }
8186
+ if (wantsCart && wantsBookRecommendations && !cartDone && (multiClickSelectionSignals || staleSelectionSignals)) {
8187
+ return `Progress reminder: Do not batch-click multiple results from a listing or category page. Open exactly one visible result, finish that item's Add to Cart flow, confirm success, then use Continue Shopping or go back once to choose the next unseen result. If a remembered label or index fails, trust the latest page state and refresh it with one read_page call before continuing.`;
8188
+ }
8189
+ if (wantsCart && selectedItems && (intendsCart || !cartDone)) {
8190
+ return `Progress reminder: You already selected the requested items. Do not restart browsing or searching unless a specific cart step fails. Continue adding the selected items to the cart one by one. Use the chosen result links you already have, add one selected item to the cart, confirm success, then continue to the next one. Do not click multiple chosen results in a row from the same listing page.`;
8191
+ }
8192
+ if (wantsCart && wantsExplanation && cartDone && !explanationDone) {
8193
+ return `Progress reminder: The cart step appears complete. Do not resume browsing. Finish by explaining why the chosen items were recommended.`;
8194
+ }
8195
+ return "";
8196
+ }
8197
+ function buildLatestStateReminder(toolResultPreview) {
8198
+ const text = toolResultPreview.trim();
8199
+ if (!text) return "";
8200
+ const stateMatch = text.match(
8201
+ /\[state:\s+url=([^,\]\n]+),\s+title=(?:"([^"]*)"|([^,\]\n]+))/i
8202
+ );
8203
+ if (stateMatch) {
8204
+ const url = stateMatch[1]?.trim();
8205
+ const title = (stateMatch[2] ?? stateMatch[3] ?? "").trim();
8206
+ if (url) {
8207
+ return `Latest browser state: URL ${url}${title ? `, title "${title}"` : ""}. Trust the latest tool result over the initial page context.`;
8208
+ }
8209
+ }
8210
+ const structuredUrl = text.match(/\*\*URL:\*\*\s*([^\n]+)/i)?.[1]?.trim();
8211
+ const structuredTitle = text.match(/\*\*Title:\*\*\s*([^\n]+)/i)?.[1]?.trim();
8212
+ if (structuredUrl) {
8213
+ return `Latest browser state: URL ${structuredUrl}${structuredTitle ? `, title "${structuredTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
8214
+ }
8215
+ const navigatedUrl = text.match(/\b(?:navigated to|went back to|went forward to|searched "[^"]+"(?: \(via search button\))? →)\s+([^\s\n]+)/i)?.[1]?.trim();
8216
+ const pageTitle = text.match(/\bPage title:\s*([^\n]+)/i)?.[1]?.trim();
8217
+ if (navigatedUrl) {
8218
+ return `Latest browser state: URL ${navigatedUrl}${pageTitle ? `, title "${pageTitle}"` : ""}. Trust the latest tool result over the initial page context.`;
8219
+ }
8220
+ return "";
8221
+ }
8222
+ function shouldRecoverCompactStall(text, userMessage) {
8223
+ const trimmed = text.trim().toLowerCase();
8224
+ if (!trimmed) return true;
8225
+ if (trimmed.length <= 160 && trimmed.includes("?")) return true;
8226
+ if (userMessage && buildPhaseReminder(userMessage, text)) {
8227
+ return true;
8228
+ }
8229
+ const repetitivePlanningSignals = [
8230
+ "next step:",
8231
+ "i will now inspect",
8232
+ "i will now read",
8233
+ "i will now click",
8234
+ "i'll use readpage",
8235
+ "i'll use read_page",
8236
+ "i'll start by clicking",
8237
+ "i have clicked on five different book titles",
8238
+ "clicked on five different book titles",
8239
+ "i'll begin with",
8240
+ "if the selection is unclear"
8241
+ ];
8242
+ if (repetitivePlanningSignals.some((pattern) => trimmed.includes(pattern))) {
8243
+ return true;
8244
+ }
8245
+ const falseCartSuccessWithoutConfirmation = /added\s+to\s+the\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+to\s+cart\s*:\s*["“”'a-z0-9 ,:&-]+|added\s+["“”'a-z0-9 ,:&-]+\s+to the cart|added\s+["“”'a-z0-9 ,:&-]+\s+to cart|added\s+.*\s+by\s+.*\s+to the cart/.test(
8246
+ trimmed
8247
+ ) && !/(cart confirmation|view cart|continue shopping|shopping cart|checkout|why i chose|here is why i chose|here are my reasons)/.test(
8248
+ trimmed
8249
+ );
8250
+ if (falseCartSuccessWithoutConfirmation) {
8251
+ return true;
8252
+ }
8253
+ const completionSignals = [
8254
+ "i found",
8255
+ "i chose",
8256
+ "i selected",
8257
+ "i added",
8258
+ "here are",
8259
+ "these are",
8260
+ "recommendations",
8261
+ "reasoning",
8262
+ "why i chose",
8263
+ "added them to the cart"
8264
+ ];
8265
+ if (completionSignals.some((pattern) => trimmed.includes(pattern))) {
8266
+ return false;
8267
+ }
8268
+ return [
8269
+ "what are you hoping",
8270
+ "what would you like",
8271
+ "how can i help",
8272
+ "let me know",
8273
+ "are you looking for",
8274
+ "just browsing",
8275
+ "i need to",
8276
+ "i will",
8277
+ "i'll",
8278
+ "since i cannot see",
8279
+ "since i can't see",
8280
+ "cannot see the current page",
8281
+ "scroll down to",
8282
+ "load more results",
8283
+ "as placeholders",
8284
+ "would you like me to proceed",
8285
+ "action:",
8286
+ "one moment",
8287
+ "i will now navigate",
8288
+ "navigating to ",
8289
+ "this will take me",
8290
+ "i will use the browser"
8291
+ ].some((pattern) => trimmed.includes(pattern));
8292
+ }
8293
+ function shouldRetryCompactToolLoop(profile, text, hasToolHistory, userMessage) {
8294
+ return profile === "compact" && hasToolHistory && shouldRecoverCompactStall(text, userMessage);
8295
+ }
8296
+ function logAgentLoopDebug(payload) {
8297
+ if (!shouldDebugAgentLoop()) return;
8298
+ try {
8299
+ logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
8300
+ } catch (err) {
8301
+ logger$j.warn("Failed to serialize debug payload:", err);
8302
+ }
8202
8303
  }
8203
8304
  function formatOpenAICompatErrorMessage(providerId, message) {
8204
8305
  if (providerId === "llama_cpp" && /(available context size|context size exceeded|exceeds the available context size|try increasing it)/i.test(
@@ -12665,6 +12766,8 @@ const UNSORTED_ID = "unsorted";
12665
12766
  const ARCHIVE_FOLDER_NAME = "Archive";
12666
12767
  const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
12667
12768
  const SAVE_DEBOUNCE_MS$1 = 250;
12769
+ const DEFAULT_BOOKMARK_SEARCH_LIMIT = 50;
12770
+ const MAX_BOOKMARK_SEARCH_LIMIT = 200;
12668
12771
  let state$2 = null;
12669
12772
  const listeners = /* @__PURE__ */ new Set();
12670
12773
  function cloneState(current) {
@@ -12673,6 +12776,10 @@ function cloneState(current) {
12673
12776
  bookmarks: current.bookmarks.map((bookmark) => ({ ...bookmark }))
12674
12777
  };
12675
12778
  }
12779
+ function getFolderMap() {
12780
+ load$1();
12781
+ return new Map(state$2.folders.map((folder) => [folder.id, folder]));
12782
+ }
12676
12783
  function getBookmarksPath() {
12677
12784
  return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
12678
12785
  }
@@ -12899,13 +13006,16 @@ function listFolderOverviews() {
12899
13006
  }))
12900
13007
  ];
12901
13008
  }
12902
- function searchBookmarks(query) {
13009
+ function searchBookmarks(query, limit = DEFAULT_BOOKMARK_SEARCH_LIMIT) {
12903
13010
  load$1();
12904
13011
  if (!query.trim()) return [];
13012
+ const foldersById = getFolderMap();
13013
+ const safeLimit = Math.max(
13014
+ 1,
13015
+ Math.min(MAX_BOOKMARK_SEARCH_LIMIT, Math.floor(limit))
13016
+ );
12905
13017
  return state$2.bookmarks.map((bookmark) => {
12906
- const folder = state$2.folders.find(
12907
- (item) => item.id === bookmark.folderId
12908
- );
13018
+ const folder = foldersById.get(bookmark.folderId);
12909
13019
  const { matchedFields, score } = getBookmarkSearchMatch({
12910
13020
  query,
12911
13021
  title: bookmark.title,
@@ -12929,7 +13039,7 @@ function searchBookmarks(query) {
12929
13039
  score: result.score
12930
13040
  })).sort(
12931
13041
  (a, b) => b.score - a.score || b.bookmark.savedAt.localeCompare(a.bookmark.savedAt)
12932
- );
13042
+ ).slice(0, safeLimit);
12933
13043
  }
12934
13044
  function createFolderWithSummary(name, summary) {
12935
13045
  load$1();
@@ -14197,6 +14307,97 @@ function formatCompactToolResult(name, result) {
14197
14307
  return limitText(result, 18, 1400);
14198
14308
  }
14199
14309
  }
14310
+ const ADD_TO_CART_PATTERNS = [
14311
+ "add to cart",
14312
+ "add to bag",
14313
+ "add to basket",
14314
+ "add to my cart",
14315
+ "add to my bag",
14316
+ "add to my basket",
14317
+ "add item to cart",
14318
+ "add item to bag",
14319
+ "add item to basket"
14320
+ ];
14321
+ const CART_CLICK_COOLDOWN_MS = 15e3;
14322
+ const CART_ADDED_TTL_MS = 30 * 6e4;
14323
+ const recentCartClicks = /* @__PURE__ */ new Map();
14324
+ const cartAddedProducts = /* @__PURE__ */ new Map();
14325
+ function isAddToCartText(text) {
14326
+ const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
14327
+ return ADD_TO_CART_PATTERNS.some((pattern) => normalized.includes(pattern));
14328
+ }
14329
+ function recordCartClick(url) {
14330
+ recentCartClicks.set(url, Date.now());
14331
+ pruneRecentCartClicks();
14332
+ }
14333
+ function hasRecentCartClick(url) {
14334
+ const recent = recentCartClicks.get(url);
14335
+ if (!recent) return false;
14336
+ if (Date.now() - recent > CART_CLICK_COOLDOWN_MS) {
14337
+ recentCartClicks.delete(url);
14338
+ return false;
14339
+ }
14340
+ return true;
14341
+ }
14342
+ function isDuplicateCartClick(url, text) {
14343
+ return hasRecentCartClick(url) && isAddToCartText(text);
14344
+ }
14345
+ function recordProductAddedToCart(url, productName) {
14346
+ pruneCartAddedProducts();
14347
+ cartAddedProducts.set(normalizeCartProductKey(url), {
14348
+ title: productName || url,
14349
+ ts: Date.now()
14350
+ });
14351
+ }
14352
+ function isProductAlreadyInCart(url) {
14353
+ pruneCartAddedProducts();
14354
+ return cartAddedProducts.has(normalizeCartProductKey(url));
14355
+ }
14356
+ function getCartAddedSummary(url) {
14357
+ pruneCartAddedProducts();
14358
+ const origin = cartOrigin(url);
14359
+ const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
14360
+ if (!items) return "";
14361
+ const count = items.split("\n").length;
14362
+ return `
14363
+ Already in cart (${count} items):
14364
+ ${items}`;
14365
+ }
14366
+ function clearCartClickState() {
14367
+ cartAddedProducts.clear();
14368
+ recentCartClicks.clear();
14369
+ }
14370
+ function pruneRecentCartClicks(now = Date.now()) {
14371
+ for (const [key2, ts] of recentCartClicks) {
14372
+ if (now - ts > CART_CLICK_COOLDOWN_MS) {
14373
+ recentCartClicks.delete(key2);
14374
+ }
14375
+ }
14376
+ }
14377
+ function normalizeCartProductKey(url) {
14378
+ try {
14379
+ const parsed = new URL(url);
14380
+ const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
14381
+ return `${parsed.origin}${pathname}`;
14382
+ } catch {
14383
+ return url;
14384
+ }
14385
+ }
14386
+ function pruneCartAddedProducts(now = Date.now()) {
14387
+ for (const [key2, entry] of cartAddedProducts) {
14388
+ if (now - entry.ts > CART_ADDED_TTL_MS) {
14389
+ cartAddedProducts.delete(key2);
14390
+ }
14391
+ }
14392
+ }
14393
+ function cartOrigin(url) {
14394
+ if (!url) return null;
14395
+ try {
14396
+ return new URL(url).origin;
14397
+ } catch {
14398
+ return null;
14399
+ }
14400
+ }
14200
14401
  const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
14201
14402
  const HUGGING_FACE_MODEL_TASKS = [
14202
14403
  {
@@ -14460,9 +14661,12 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
14460
14661
  class TabMutex {
14461
14662
  queue = Promise.resolve();
14462
14663
  enqueue(fn) {
14463
- return new Promise((resolve, reject) => {
14464
- this.queue = this.queue.then(fn).then(resolve, reject);
14465
- });
14664
+ const run = this.queue.then(fn, fn);
14665
+ this.queue = run.then(
14666
+ () => void 0,
14667
+ () => void 0
14668
+ );
14669
+ return run;
14466
14670
  }
14467
14671
  }
14468
14672
  const logger$e = createLogger("PageActions");
@@ -15290,45 +15494,9 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
15290
15494
  }
15291
15495
  }
15292
15496
  }
15293
- const ADD_TO_CART_PATTERNS = [
15294
- "add to cart",
15295
- "add to bag",
15296
- "add to basket",
15297
- "add to my cart",
15298
- "add to my bag",
15299
- "add to my basket",
15300
- "add item to cart",
15301
- "add item to bag",
15302
- "add item to basket"
15303
- ];
15304
- const recentCartClicks = /* @__PURE__ */ new Map();
15305
- const CART_CLICK_COOLDOWN_MS = 15e3;
15306
- const CART_ADDED_TTL_MS = 30 * 6e4;
15307
- const cartAddedProducts = /* @__PURE__ */ new Map();
15308
15497
  let clickStreakUrl = null;
15309
15498
  let clickStreakCount = 0;
15310
15499
  const CLICK_STREAK_THRESHOLD = 3;
15311
- function isAddToCartText(text) {
15312
- const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
15313
- return ADD_TO_CART_PATTERNS.some((p) => normalized.includes(p));
15314
- }
15315
- function recordCartClick(url, text) {
15316
- recentCartClicks.set(url, { text, ts: Date.now() });
15317
- for (const [key2, entry] of recentCartClicks) {
15318
- if (Date.now() - entry.ts > CART_CLICK_COOLDOWN_MS) {
15319
- recentCartClicks.delete(key2);
15320
- }
15321
- }
15322
- }
15323
- function isDuplicateCartClick(url, text) {
15324
- const recent = recentCartClicks.get(url);
15325
- if (!recent) return false;
15326
- if (Date.now() - recent.ts > CART_CLICK_COOLDOWN_MS) {
15327
- recentCartClicks.delete(url);
15328
- return false;
15329
- }
15330
- return isAddToCartText(text);
15331
- }
15332
15500
  async function getProductPageTitle(wc) {
15333
15501
  try {
15334
15502
  const heading = await executePageScript(
@@ -15353,54 +15521,8 @@ async function getProductPageTitle(wc) {
15353
15521
  }
15354
15522
  return wc.getTitle() || "";
15355
15523
  }
15356
- function normalizeCartProductKey(url) {
15357
- try {
15358
- const parsed = new URL(url);
15359
- const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
15360
- return `${parsed.origin}${pathname}`;
15361
- } catch {
15362
- return url;
15363
- }
15364
- }
15365
- function pruneCartAddedProducts(now = Date.now()) {
15366
- for (const [key2, entry] of cartAddedProducts) {
15367
- if (now - entry.ts > CART_ADDED_TTL_MS) {
15368
- cartAddedProducts.delete(key2);
15369
- }
15370
- }
15371
- }
15372
- function cartOrigin(url) {
15373
- if (!url) return null;
15374
- try {
15375
- return new URL(url).origin;
15376
- } catch {
15377
- return null;
15378
- }
15379
- }
15380
- function recordProductAddedToCart(url, productName) {
15381
- pruneCartAddedProducts();
15382
- cartAddedProducts.set(normalizeCartProductKey(url), {
15383
- title: productName || url,
15384
- ts: Date.now()
15385
- });
15386
- }
15387
- function isProductAlreadyInCart(url) {
15388
- pruneCartAddedProducts();
15389
- return cartAddedProducts.has(normalizeCartProductKey(url));
15390
- }
15391
- function getCartAddedSummary(url) {
15392
- pruneCartAddedProducts();
15393
- const origin = cartOrigin(url);
15394
- const items = Array.from(cartAddedProducts.entries()).filter(([key2]) => !origin || key2.startsWith(`${origin}/`)).map(([_path, info]) => `- ${info.title}`).join("\n");
15395
- if (!items) return "";
15396
- const count = items.split("\n").length;
15397
- return `
15398
- Already in cart (${count} items):
15399
- ${items}`;
15400
- }
15401
15524
  function clearCartState() {
15402
- cartAddedProducts.clear();
15403
- recentCartClicks.clear();
15525
+ clearCartClickState();
15404
15526
  clickStreakUrl = null;
15405
15527
  clickStreakCount = 0;
15406
15528
  }
@@ -15452,7 +15574,7 @@ Go back and select a different product.`;
15452
15574
  if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
15453
15575
  if (typeof result === "string" && result.startsWith("Error")) return result;
15454
15576
  if (idxCartMatch) {
15455
- recordCartClick(beforeUrl2, idxLabel);
15577
+ recordCartClick(beforeUrl2);
15456
15578
  }
15457
15579
  await waitForPotentialNavigation(wc, beforeUrl2);
15458
15580
  const afterUrl2 = wc.getURL();
@@ -15519,7 +15641,7 @@ Go back and select a different product.`;
15519
15641
  if (result === PAGE_SCRIPT_TIMEOUT) return pageBusyError("click");
15520
15642
  if (typeof result === "string" && result.startsWith("Error")) return result;
15521
15643
  if (shadowCartMatch) {
15522
- recordCartClick(beforeUrl2, shadowLabel);
15644
+ recordCartClick(beforeUrl2);
15523
15645
  }
15524
15646
  await waitForPotentialNavigation(wc, beforeUrl2);
15525
15647
  const afterUrl2 = wc.getURL();
@@ -15555,7 +15677,7 @@ Note: Page did not change after click.`;
15555
15677
  if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
15556
15678
  return `Blocked: "${elInfo.text}" was already clicked on this page. The item is in your cart. Call read_page to see available actions (e.g. View Cart, Continue Shopping).`;
15557
15679
  }
15558
- if (!cartMatch && recentCartClicks.has(beforeUrl)) {
15680
+ if (!cartMatch && hasRecentCartClick(beforeUrl)) {
15559
15681
  const dialogActions = await getCartDialogActions(wc);
15560
15682
  if (dialogActions) {
15561
15683
  return `Blocked: a cart confirmation dialog is open. Do not click background elements.
@@ -15575,7 +15697,7 @@ Click one of these dialog actions instead.`;
15575
15697
  Go back and select a different product.`;
15576
15698
  }
15577
15699
  if (cartMatch) {
15578
- recordCartClick(beforeUrl, elInfo.text);
15700
+ recordCartClick(beforeUrl);
15579
15701
  }
15580
15702
  const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
15581
15703
  const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
@@ -19009,6 +19131,57 @@ function onAIStreamIdle(listener) {
19009
19131
  idleListeners.add(listener);
19010
19132
  return () => idleListeners.delete(listener);
19011
19133
  }
19134
+ const MAX_PROVIDER_HISTORY_MESSAGES = 24;
19135
+ const MAX_PROVIDER_HISTORY_CHARS = 24e3;
19136
+ const MAX_PROVIDER_HISTORY_MESSAGE_CHARS = 3e3;
19137
+ const MAX_PROVIDER_HISTORY_SUMMARY_CHARS = 2e3;
19138
+ function truncateText(value, maxLength) {
19139
+ if (value.length <= maxLength) return value;
19140
+ return `${value.slice(0, maxLength - 3).trimEnd()}...`;
19141
+ }
19142
+ function normalizeHistoryMessage(message) {
19143
+ return {
19144
+ role: message.role,
19145
+ content: truncateText(message.content, MAX_PROVIDER_HISTORY_MESSAGE_CHARS)
19146
+ };
19147
+ }
19148
+ function totalHistoryChars(history) {
19149
+ return history.reduce((total, message) => total + message.content.length, 0);
19150
+ }
19151
+ function summarizeOmittedHistory(history) {
19152
+ const snippets = history.slice(-12).map((message) => `${message.role}: ${truncateText(message.content.replace(/\s+/g, " ").trim(), 220)}`).filter((line) => line.length > "assistant: ".length);
19153
+ const content = truncateText(
19154
+ [
19155
+ `[Earlier conversation compacted: ${history.length} message${history.length === 1 ? "" : "s"} omitted.]`,
19156
+ ...snippets
19157
+ ].join("\n"),
19158
+ MAX_PROVIDER_HISTORY_SUMMARY_CHARS
19159
+ );
19160
+ return { role: "user", content };
19161
+ }
19162
+ function compactProviderHistory(history = []) {
19163
+ const normalized = history.map(normalizeHistoryMessage);
19164
+ if (normalized.length <= MAX_PROVIDER_HISTORY_MESSAGES && totalHistoryChars(normalized) <= MAX_PROVIDER_HISTORY_CHARS) {
19165
+ return normalized;
19166
+ }
19167
+ const recent = [];
19168
+ const recentBudget = MAX_PROVIDER_HISTORY_CHARS - MAX_PROVIDER_HISTORY_SUMMARY_CHARS;
19169
+ let usedChars = 0;
19170
+ for (let index = normalized.length - 1; index >= 0; index--) {
19171
+ const message = normalized[index];
19172
+ const nextChars = usedChars + message.content.length;
19173
+ if (recent.length >= MAX_PROVIDER_HISTORY_MESSAGES || nextChars > recentBudget) {
19174
+ break;
19175
+ }
19176
+ recent.unshift(message);
19177
+ usedChars = nextChars;
19178
+ }
19179
+ if (recent.length === 0 && normalized.length > 0) {
19180
+ recent.unshift(normalized[normalized.length - 1]);
19181
+ }
19182
+ const omitted = normalized.slice(0, normalized.length - recent.length);
19183
+ return omitted.length > 0 ? [summarizeOmittedHistory(omitted), ...recent] : recent;
19184
+ }
19012
19185
  const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
19013
19186
  const DEFAULT_NOTE_FOLDER = "Vessel/Research";
19014
19187
  const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
@@ -20364,6 +20537,14 @@ function asErrorTextResponse(message) {
20364
20537
  function asNoActiveTabResponse() {
20365
20538
  return asErrorTextResponse("No active tab");
20366
20539
  }
20540
+ function getPremiumToolGateResponse(toolName) {
20541
+ try {
20542
+ assertToolUnlocked(toolName);
20543
+ return null;
20544
+ } catch (error) {
20545
+ return asTextResponse(getErrorMessage(error));
20546
+ }
20547
+ }
20367
20548
  function asPromptResponse(text) {
20368
20549
  return {
20369
20550
  messages: [
@@ -20466,6 +20647,8 @@ async function getPostActionState(tabManager, name) {
20466
20647
  return "";
20467
20648
  }
20468
20649
  async function withAction(runtime2, tabManager, name, args, executor) {
20650
+ const premiumGate = getPremiumToolGateResponse(name);
20651
+ if (premiumGate) return premiumGate;
20469
20652
  try {
20470
20653
  const result = await runtime2.runControlledAction({
20471
20654
  source: "mcp",
@@ -21775,6 +21958,8 @@ ${buildScopedContext(pageContent, mode)}`;
21775
21958
  description: "Capture a screenshot of the current page. Returns a base64-encoded PNG image."
21776
21959
  },
21777
21960
  async () => {
21961
+ const premiumGate = getPremiumToolGateResponse("screenshot");
21962
+ if (premiumGate) return premiumGate;
21778
21963
  const tab = tabManager.getActiveTab();
21779
21964
  if (!tab) return asNoActiveTabResponse();
21780
21965
  try {
@@ -22797,6 +22982,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
22797
22982
  }
22798
22983
  },
22799
22984
  async ({ goal, steps }) => {
22985
+ const premiumGate = getPremiumToolGateResponse("flow_start");
22986
+ if (premiumGate) return premiumGate;
22800
22987
  const normalizedSteps = coerceStringArray(steps) ?? [];
22801
22988
  const tab = tabManager.getActiveTab();
22802
22989
  const flow = runtime2.startFlow(
@@ -22820,6 +23007,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
22820
23007
  }
22821
23008
  },
22822
23009
  async ({ detail }) => {
23010
+ const premiumGate = getPremiumToolGateResponse("flow_advance");
23011
+ if (premiumGate) return premiumGate;
22823
23012
  const flow = runtime2.advanceFlow(detail);
22824
23013
  if (!flow) return asTextResponse("No active flow to advance");
22825
23014
  const ctx = runtime2.getFlowContext();
@@ -22833,6 +23022,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
22833
23022
  description: "Check the current workflow progress."
22834
23023
  },
22835
23024
  async () => {
23025
+ const premiumGate = getPremiumToolGateResponse("flow_status");
23026
+ if (premiumGate) return premiumGate;
22836
23027
  const flow = runtime2.getFlowState();
22837
23028
  if (!flow) return asTextResponse("No active workflow.");
22838
23029
  return asTextResponse(runtime2.getFlowContext());
@@ -22845,6 +23036,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
22845
23036
  description: "Clear the active workflow tracker."
22846
23037
  },
22847
23038
  async () => {
23039
+ const premiumGate = getPremiumToolGateResponse("flow_end");
23040
+ if (premiumGate) return premiumGate;
22848
23041
  runtime2.clearFlow();
22849
23042
  return asTextResponse("Workflow ended.");
22850
23043
  }
@@ -23485,6 +23678,8 @@ ${JSON.stringify(tableJson, null, 2)}`;
23485
23678
  }
23486
23679
  },
23487
23680
  async ({ domain }) => {
23681
+ const premiumGate = getPremiumToolGateResponse("vault_status");
23682
+ if (premiumGate) return premiumGate;
23488
23683
  let targetDomain = domain;
23489
23684
  if (!targetDomain) {
23490
23685
  const tab = tabManager.getActiveTab();
@@ -23551,6 +23746,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23551
23746
  submit_after,
23552
23747
  submit_index
23553
23748
  }) => {
23749
+ const premiumGate = getPremiumToolGateResponse("vault_login");
23750
+ if (premiumGate) return premiumGate;
23554
23751
  const tab = tabManager.getActiveTab();
23555
23752
  if (!tab) return asNoActiveTabResponse();
23556
23753
  const wc = tab.view.webContents;
@@ -23645,6 +23842,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23645
23842
  }
23646
23843
  },
23647
23844
  async ({ credential_label, code_index, submit_after, submit_index }) => {
23845
+ const premiumGate = getPremiumToolGateResponse("vault_totp");
23846
+ if (premiumGate) return premiumGate;
23648
23847
  const tab = tabManager.getActiveTab();
23649
23848
  if (!tab) return asNoActiveTabResponse();
23650
23849
  const wc = tab.view.webContents;
@@ -23724,6 +23923,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23724
23923
  })
23725
23924
  },
23726
23925
  async ({ domain }) => {
23926
+ const premiumGate = getPremiumToolGateResponse("human_vault_list");
23927
+ if (premiumGate) return premiumGate;
23727
23928
  const consent = await requestHumanVaultConsent({
23728
23929
  action: "list",
23729
23930
  domain: domain ?? "all"
@@ -23774,6 +23975,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23774
23975
  })
23775
23976
  },
23776
23977
  async ({ entry_id, username_index, password_index, submit_after, submit_index }) => {
23978
+ const premiumGate = getPremiumToolGateResponse("human_vault_fill");
23979
+ if (premiumGate) return premiumGate;
23777
23980
  const tab = tabManager.getActiveTab();
23778
23981
  if (!tab) return asNoActiveTabResponse();
23779
23982
  let hostname;
@@ -23866,6 +24069,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23866
24069
  })
23867
24070
  },
23868
24071
  async ({ entry_id }) => {
24072
+ const premiumGate = getPremiumToolGateResponse("human_vault_remove");
24073
+ if (premiumGate) return premiumGate;
23869
24074
  const entry = getEntry(entry_id);
23870
24075
  if (!entry) {
23871
24076
  return asErrorTextResponse(`No entry found with ID ${entry_id}.`);
@@ -23890,6 +24095,8 @@ Use vault_login to fill the login form. Credentials are filled directly — you
23890
24095
  inputSchema: zod.z.object({})
23891
24096
  },
23892
24097
  async () => {
24098
+ const premiumGate = getPremiumToolGateResponse("metrics");
24099
+ if (premiumGate) return premiumGate;
23893
24100
  const m = runtime2.getMetrics();
23894
24101
  const lines = [
23895
24102
  `Session Metrics:`,
@@ -24243,7 +24450,7 @@ function assertNumber(value, name) {
24243
24450
  }
24244
24451
  }
24245
24452
  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
24246
- function isValidEmail(value) {
24453
+ function isValidEmail$1(value) {
24247
24454
  return EMAIL_RE.test(value.trim());
24248
24455
  }
24249
24456
  function getActiveTabInfo(tabManager) {
@@ -24255,6 +24462,8 @@ function getActiveTabInfo(tabManager) {
24255
24462
  }
24256
24463
  const logger$9 = createLogger("Scheduler");
24257
24464
  let jobs = [];
24465
+ let pollInterval = null;
24466
+ let alignStartTimeout = null;
24258
24467
  let removeIdleListener = null;
24259
24468
  let broadcastFn = null;
24260
24469
  function getScheduledKitIds() {
@@ -24343,10 +24552,12 @@ function computeNextRun(schedule, from = /* @__PURE__ */ new Date()) {
24343
24552
  const next = new Date(from);
24344
24553
  next.setHours(schedule.hour, schedule.minute, 0, 0);
24345
24554
  const daysUntil = (schedule.dayOfWeek - next.getDay() + 7) % 7;
24346
- if (daysUntil === 0 && next <= from) {
24347
- next.setDate(next.getDate() + 7);
24555
+ if (daysUntil === 0) {
24556
+ if (next <= from) {
24557
+ next.setDate(next.getDate() + 7);
24558
+ }
24348
24559
  } else {
24349
- next.setDate(next.getDate() + (daysUntil || 7));
24560
+ next.setDate(next.getDate() + daysUntil);
24350
24561
  }
24351
24562
  return next;
24352
24563
  }
@@ -24473,18 +24684,19 @@ function tick(windowState, runtime2) {
24473
24684
  fireNext();
24474
24685
  }
24475
24686
  function registerScheduleHandlers(windowState, runtime2, sendToAll) {
24687
+ stopScheduler();
24476
24688
  broadcastFn = sendToAll;
24477
24689
  loadJobs();
24478
24690
  if (normalizeJobs()) {
24479
24691
  saveJobs();
24480
24692
  }
24481
- removeIdleListener?.();
24482
24693
  removeIdleListener = onAIStreamIdle(() => tick(windowState, runtime2));
24483
24694
  const now = /* @__PURE__ */ new Date();
24484
24695
  const msToNextMinute = (60 - now.getSeconds()) * 1e3 - now.getMilliseconds();
24485
- setTimeout(() => {
24696
+ alignStartTimeout = setTimeout(() => {
24697
+ alignStartTimeout = null;
24486
24698
  tick(windowState, runtime2);
24487
- setInterval(() => tick(windowState, runtime2), 6e4);
24699
+ pollInterval = setInterval(() => tick(windowState, runtime2), 6e4);
24488
24700
  }, msToNextMinute);
24489
24701
  electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
24490
24702
  assertTrustedIpcSender(event);
@@ -24546,6 +24758,20 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
24546
24758
  return true;
24547
24759
  });
24548
24760
  }
24761
+ function stopScheduler() {
24762
+ if (removeIdleListener) {
24763
+ removeIdleListener();
24764
+ removeIdleListener = null;
24765
+ }
24766
+ if (alignStartTimeout) {
24767
+ clearTimeout(alignStartTimeout);
24768
+ alignStartTimeout = null;
24769
+ }
24770
+ if (pollInterval) {
24771
+ clearInterval(pollInterval);
24772
+ pollInterval = null;
24773
+ }
24774
+ }
24549
24775
  const SAVE_DEBOUNCE_MS = 250;
24550
24776
  const PROFILE_FIELDS = [
24551
24777
  "label",
@@ -24943,6 +25169,7 @@ function registerAutofillHandlers(windowState) {
24943
25169
  }
24944
25170
  function registerPageDiffHandlers(windowState, sendToRendererViews) {
24945
25171
  const pageEventBuckets = /* @__PURE__ */ new Map();
25172
+ const isActiveWebContents = (webContentsId) => windowState.tabManager.getActiveTab()?.view.webContents.id === webContentsId;
24946
25173
  const allowPageEvent = (webContentsId) => {
24947
25174
  const now = Date.now();
24948
25175
  const bucket = pageEventBuckets.get(webContentsId);
@@ -24979,14 +25206,20 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
24979
25206
  if (!wc || wc.isDestroyed()) return;
24980
25207
  if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
24981
25208
  if (!allowPageEvent(wc.id)) return;
24982
- notePageMutationActivity(wc, sendToRendererViews);
25209
+ invalidateExtractionCache(wc);
25210
+ notePageMutationActivity(wc, sendToRendererViews, {
25211
+ isActive: () => isActiveWebContents(wc.id)
25212
+ });
24983
25213
  });
24984
25214
  electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
24985
25215
  const wc = event.sender;
24986
25216
  if (!wc || wc.isDestroyed()) return;
24987
25217
  if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
24988
25218
  if (!allowPageEvent(wc.id)) return;
24989
- schedulePageSnapshotCapture(wc, sendToRendererViews);
25219
+ invalidateExtractionCache(wc);
25220
+ schedulePageSnapshotCapture(wc, sendToRendererViews, 0, {
25221
+ isActive: () => isActiveWebContents(wc.id)
25222
+ });
24990
25223
  });
24991
25224
  }
24992
25225
  function renderReportAsMarkdown(report, traces) {
@@ -25255,9 +25488,40 @@ RULES:
25255
25488
  }
25256
25489
  const logger$7 = createLogger("ResearchOrchestrator");
25257
25490
  const MAX_THREADS = 5;
25491
+ const MAX_TRACE_ARGS_CHARS = 1200;
25492
+ const MAX_TRACE_RESULT_CHARS = 2e3;
25258
25493
  function clone$1(value) {
25259
25494
  return structuredClone(value);
25260
25495
  }
25496
+ function truncateTraceText(value, maxLength) {
25497
+ if (value.length <= maxLength) return value;
25498
+ return `${value.slice(0, maxLength - 3).trimEnd()}...`;
25499
+ }
25500
+ function slimTraceArgs(args) {
25501
+ const json = JSON.stringify(args);
25502
+ if (json.length <= MAX_TRACE_ARGS_CHARS) return clone$1(args);
25503
+ return {
25504
+ _truncated: true,
25505
+ originalChars: json.length,
25506
+ preview: truncateTraceText(json, MAX_TRACE_ARGS_CHARS)
25507
+ };
25508
+ }
25509
+ function slimTraceResult(result) {
25510
+ if (result.length <= MAX_TRACE_RESULT_CHARS) return result;
25511
+ return [
25512
+ `[Trace result truncated from ${result.length} chars.]`,
25513
+ truncateTraceText(result, MAX_TRACE_RESULT_CHARS)
25514
+ ].join("\n");
25515
+ }
25516
+ function createTraceToolCall(tool, args, result, startedAt) {
25517
+ return {
25518
+ tool,
25519
+ args: slimTraceArgs(args),
25520
+ result: slimTraceResult(result),
25521
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25522
+ durationMs: Date.now() - startedAt
25523
+ };
25524
+ }
25261
25525
  function normalizeSourceDomain(value) {
25262
25526
  const trimmed = value.trim().toLowerCase();
25263
25527
  if (!trimmed) return "";
@@ -25666,13 +25930,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
25666
25930
  title: String(args.url || "excluded source"),
25667
25931
  reason: msg
25668
25932
  });
25669
- trace.toolCalls.push({
25670
- tool: name,
25671
- args,
25672
- result: msg,
25673
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25674
- durationMs: 0
25675
- });
25933
+ trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
25676
25934
  return msg;
25677
25935
  }
25678
25936
  }
@@ -25680,25 +25938,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
25680
25938
  sourcesConsumed++;
25681
25939
  if (sourcesConsumed > thread.sourceBudget) {
25682
25940
  const msg = `Source budget (${thread.sourceBudget}) exceeded. Summarize findings and stop.`;
25683
- trace.toolCalls.push({
25684
- tool: name,
25685
- args,
25686
- result: msg,
25687
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25688
- durationMs: 0
25689
- });
25941
+ trace.toolCalls.push(createTraceToolCall(name, args, msg, t0));
25690
25942
  return msg;
25691
25943
  }
25692
25944
  }
25693
25945
  try {
25694
25946
  const output = await executeAction(name, args, actionCtx);
25695
- trace.toolCalls.push({
25696
- tool: name,
25697
- args,
25698
- result: output,
25699
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25700
- durationMs: Date.now() - t0
25701
- });
25947
+ trace.toolCalls.push(createTraceToolCall(name, args, output, t0));
25702
25948
  return output;
25703
25949
  } catch (err) {
25704
25950
  const msg = err instanceof Error ? err.message : String(err);
@@ -25713,13 +25959,7 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
25713
25959
  message: msg,
25714
25960
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
25715
25961
  });
25716
- trace.toolCalls.push({
25717
- tool: name,
25718
- args,
25719
- result: `Error: ${msg}`,
25720
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25721
- durationMs: Date.now() - t0
25722
- });
25962
+ trace.toolCalls.push(createTraceToolCall(name, args, `Error: ${msg}`, t0));
25723
25963
  return `Error: ${msg}`;
25724
25964
  }
25725
25965
  },
@@ -25939,15 +26179,20 @@ ${transcript.slice(0, 32e3)}`;
25939
26179
  this.emit();
25940
26180
  }
25941
26181
  }
26182
+ function assertVaultUnlocked() {
26183
+ assertFeatureUnlocked("vault", "Agent Credential Vault");
26184
+ }
25942
26185
  function registerVaultHandlers() {
25943
26186
  electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
25944
26187
  assertTrustedIpcSender(event);
26188
+ assertVaultUnlocked();
25945
26189
  return listEntries$1();
25946
26190
  });
25947
26191
  electron.ipcMain.handle(
25948
26192
  Channels.VAULT_ADD,
25949
26193
  (event, entry) => {
25950
26194
  assertTrustedIpcSender(event);
26195
+ assertVaultUnlocked();
25951
26196
  if (!entry || typeof entry !== "object") {
25952
26197
  throw new Error("Invalid vault entry");
25953
26198
  }
@@ -25974,6 +26219,7 @@ function registerVaultHandlers() {
25974
26219
  Channels.VAULT_UPDATE,
25975
26220
  (event, id, updates) => {
25976
26221
  assertTrustedIpcSender(event);
26222
+ assertVaultUnlocked();
25977
26223
  assertString(id, "id");
25978
26224
  if (!updates || typeof updates !== "object") {
25979
26225
  throw new Error("Invalid updates");
@@ -25983,12 +26229,14 @@ function registerVaultHandlers() {
25983
26229
  );
25984
26230
  electron.ipcMain.handle(Channels.VAULT_REMOVE, (event, id) => {
25985
26231
  assertTrustedIpcSender(event);
26232
+ assertVaultUnlocked();
25986
26233
  assertString(id, "id");
25987
26234
  trackVaultAction("credential_removed");
25988
26235
  return removeEntry$1(id);
25989
26236
  });
25990
26237
  electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (event, limit) => {
25991
26238
  assertTrustedIpcSender(event);
26239
+ assertVaultUnlocked();
25992
26240
  return readAuditLog$1(limit);
25993
26241
  });
25994
26242
  }
@@ -26006,14 +26254,19 @@ function normalizeTags(value) {
26006
26254
  const tags = value.filter((tag) => typeof tag === "string").map((tag) => tag.trim()).filter(Boolean);
26007
26255
  return tags.length > 0 ? tags : void 0;
26008
26256
  }
26257
+ function assertHumanVaultUnlocked() {
26258
+ assertFeatureUnlocked("human_vault", "Passwords");
26259
+ }
26009
26260
  function registerHumanVaultHandlers() {
26010
26261
  electron.ipcMain.handle(Channels.HUMAN_VAULT_LIST, (event, domain) => {
26011
26262
  assertTrustedIpcSender(event);
26263
+ assertHumanVaultUnlocked();
26012
26264
  if (domain !== void 0) assertString(domain, "domain");
26013
26265
  return domain ? findForDomain(domain) : listEntries();
26014
26266
  });
26015
26267
  electron.ipcMain.handle(Channels.HUMAN_VAULT_GET, (event, id) => {
26016
26268
  assertTrustedIpcSender(event);
26269
+ assertHumanVaultUnlocked();
26017
26270
  assertString(id, "id");
26018
26271
  return getEntrySafe(id);
26019
26272
  });
@@ -26021,6 +26274,7 @@ function registerHumanVaultHandlers() {
26021
26274
  Channels.HUMAN_VAULT_SAVE,
26022
26275
  (event, input) => {
26023
26276
  assertTrustedIpcSender(event);
26277
+ assertHumanVaultUnlocked();
26024
26278
  if (!input || typeof input !== "object") {
26025
26279
  throw new Error("Invalid credential entry");
26026
26280
  }
@@ -26052,6 +26306,7 @@ function registerHumanVaultHandlers() {
26052
26306
  Channels.HUMAN_VAULT_UPDATE,
26053
26307
  (event, id, updates) => {
26054
26308
  assertTrustedIpcSender(event);
26309
+ assertHumanVaultUnlocked();
26055
26310
  assertString(id, "id");
26056
26311
  if (!updates || typeof updates !== "object") {
26057
26312
  throw new Error("Invalid updates");
@@ -26095,11 +26350,13 @@ function registerHumanVaultHandlers() {
26095
26350
  );
26096
26351
  electron.ipcMain.handle(Channels.HUMAN_VAULT_REMOVE, (event, id) => {
26097
26352
  assertTrustedIpcSender(event);
26353
+ assertHumanVaultUnlocked();
26098
26354
  assertString(id, "id");
26099
26355
  return removeEntry(id);
26100
26356
  });
26101
26357
  electron.ipcMain.handle(Channels.HUMAN_VAULT_AUDIT_LOG, (event, limit) => {
26102
26358
  assertTrustedIpcSender(event);
26359
+ assertHumanVaultUnlocked();
26103
26360
  return readAuditLog(limit);
26104
26361
  });
26105
26362
  }
@@ -27235,6 +27492,10 @@ function registerHistoryHandlers() {
27235
27492
  assertTrustedIpcSender(event);
27236
27493
  return getState$1();
27237
27494
  });
27495
+ electron.ipcMain.handle(Channels.HISTORY_LIST, (event, offset, limit) => {
27496
+ assertTrustedIpcSender(event);
27497
+ return listEntries$2(offset, limit);
27498
+ });
27238
27499
  electron.ipcMain.handle(Channels.HISTORY_SEARCH, (event, query) => {
27239
27500
  assertTrustedIpcSender(event);
27240
27501
  return search(query);
@@ -27389,7 +27650,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
27389
27650
  electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (event, email) => {
27390
27651
  assertTrustedIpcSender(event);
27391
27652
  assertString(email, "email");
27392
- if (!isValidEmail(email)) {
27653
+ if (!isValidEmail$1(email)) {
27393
27654
  return errorResult("Invalid email format");
27394
27655
  }
27395
27656
  trackPremiumFunnel("activation_attempted");
@@ -27416,7 +27677,7 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
27416
27677
  assertString(email, "email");
27417
27678
  assertString(code, "code");
27418
27679
  assertString(challengeToken, "challengeToken");
27419
- if (!isValidEmail(email)) {
27680
+ if (!isValidEmail$1(email)) {
27420
27681
  return errorResult("Invalid email format", {
27421
27682
  state: getPremiumState()
27422
27683
  });
@@ -27620,6 +27881,47 @@ function registerCodexHandlers() {
27620
27881
  return { ok: true };
27621
27882
  });
27622
27883
  }
27884
+ const SUPPORT_API = process.env.VESSEL_SUPPORT_API || process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
27885
+ const MAX_FEEDBACK_MESSAGE_LENGTH = 5e3;
27886
+ const FEEDBACK_REQUEST_TIMEOUT_MS = 15e3;
27887
+ function isValidEmail(email) {
27888
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
27889
+ }
27890
+ async function submitFeedback(payload) {
27891
+ const email = payload.email.trim().toLowerCase();
27892
+ const message = payload.message.trim();
27893
+ if (!isValidEmail(email)) {
27894
+ return errorResult("Enter a valid reply email.");
27895
+ }
27896
+ if (!message) {
27897
+ return errorResult("Write a feedback message before sending.");
27898
+ }
27899
+ if (message.length > MAX_FEEDBACK_MESSAGE_LENGTH) {
27900
+ return errorResult(
27901
+ `Feedback must be ${MAX_FEEDBACK_MESSAGE_LENGTH.toLocaleString()} characters or less.`
27902
+ );
27903
+ }
27904
+ try {
27905
+ const signal = AbortSignal.timeout(FEEDBACK_REQUEST_TIMEOUT_MS);
27906
+ const res = await fetch(`${SUPPORT_API}/feedback`, {
27907
+ method: "POST",
27908
+ headers: { "Content-Type": "application/json" },
27909
+ signal,
27910
+ body: JSON.stringify({
27911
+ email,
27912
+ message,
27913
+ source: payload.source
27914
+ })
27915
+ });
27916
+ const data = await res.json().catch(() => ({}));
27917
+ if (!res.ok) {
27918
+ return errorResult(data.error || `HTTP ${res.status}`);
27919
+ }
27920
+ return okResult();
27921
+ } catch (error) {
27922
+ return errorResult(getErrorMessage(error, "Failed to send feedback."));
27923
+ }
27924
+ }
27623
27925
  const filePath = () => path$1.join(electron.app.getPath("userData"), "vessel-permissions.json");
27624
27926
  const ALLOWED_PERMISSION_TYPES = /* @__PURE__ */ new Set([
27625
27927
  "clipboard-read",
@@ -28133,7 +28435,7 @@ function registerIpcHandlers(windowState, runtime2) {
28133
28435
  () => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
28134
28436
  tabManager,
28135
28437
  runtime2,
28136
- history,
28438
+ compactProviderHistory(history),
28137
28439
  researchOrchestrator
28138
28440
  );
28139
28441
  } catch (err) {
@@ -28285,6 +28587,12 @@ function registerIpcHandlers(windowState, runtime2) {
28285
28587
  requireTrusted(event);
28286
28588
  return regenerateMcpAuthToken();
28287
28589
  });
28590
+ electron.ipcMain.handle(Channels.SUPPORT_SUBMIT_FEEDBACK, async (event, email, message) => {
28591
+ requireTrusted(event);
28592
+ assertString(email, "email");
28593
+ assertString(message, "message");
28594
+ return submitFeedback({ email, message, source: "settings_account" });
28595
+ });
28288
28596
  electron.ipcMain.handle(Channels.SETTINGS_SET, async (event, key2, value) => {
28289
28597
  requireTrusted(event);
28290
28598
  assertString(key2, "key");
@@ -29926,7 +30234,7 @@ async function bootstrap() {
29926
30234
  );
29927
30235
  }
29928
30236
  };
29929
- const windowState = createMainWindow((tabs, activeId) => {
30237
+ const windowState = createMainWindow((tabs, activeId, meta) => {
29930
30238
  windowState.chromeView.webContents.send(
29931
30239
  Channels.TAB_STATE_UPDATE,
29932
30240
  tabs,
@@ -29934,7 +30242,9 @@ async function bootstrap() {
29934
30242
  );
29935
30243
  void syncActiveHighlightCount(windowState);
29936
30244
  layoutViews(windowState);
29937
- runtime?.onTabStateChanged();
30245
+ if (meta.persistSession) {
30246
+ runtime?.onTabStateChanged();
30247
+ }
29938
30248
  });
29939
30249
  let didRevealMainWindow = false;
29940
30250
  const revealMainWindow = () => {