@quanta-intellect/vessel-browser 0.1.63 → 0.1.67

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
@@ -14,6 +14,42 @@ const http = require("node:http");
14
14
  const os = require("node:os");
15
15
  const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
16
16
  const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
17
+ function getEnvFlag(name) {
18
+ const globalProcess = typeof globalThis === "object" && "process" in globalThis ? globalThis.process : void 0;
19
+ return globalProcess?.env?.[name];
20
+ }
21
+ function isDebugEnabled() {
22
+ const value = getEnvFlag("VESSEL_DEBUG")?.trim().toLowerCase();
23
+ return value === "1" || value === "true" || value === "yes" || value === "on";
24
+ }
25
+ function writeLog(level, scope, args) {
26
+ if (level === "debug" && !isDebugEnabled()) {
27
+ return;
28
+ }
29
+ const prefix = `[Vessel ${scope}]`;
30
+ switch (level) {
31
+ case "debug":
32
+ console.debug(prefix, ...args);
33
+ return;
34
+ case "info":
35
+ console.info(prefix, ...args);
36
+ return;
37
+ case "warn":
38
+ console.warn(prefix, ...args);
39
+ return;
40
+ case "error":
41
+ console.error(prefix, ...args);
42
+ return;
43
+ }
44
+ }
45
+ function createLogger(scope) {
46
+ return {
47
+ debug: (...args) => writeLog("debug", scope, args),
48
+ info: (...args) => writeLog("info", scope, args),
49
+ warn: (...args) => writeLog("warn", scope, args),
50
+ error: (...args) => writeLog("error", scope, args)
51
+ };
52
+ }
17
53
  const defaults = {
18
54
  defaultUrl: "https://start.duckduckgo.com",
19
55
  theme: "dark",
@@ -40,6 +76,7 @@ const defaults = {
40
76
  };
41
77
  const SAVE_DEBOUNCE_MS$5 = 150;
42
78
  const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
79
+ const logger$j = createLogger("Settings");
43
80
  const SETTABLE_KEYS = new Set(Object.keys(defaults));
44
81
  let settings = null;
45
82
  let settingsIssues = [];
@@ -185,7 +222,7 @@ function persistNow() {
185
222
  getSettingsPath(),
186
223
  JSON.stringify(buildPersistedSettings(settings), null, 2)
187
224
  )
188
- ).catch((err) => console.error("[Vessel] Failed to save settings:", err));
225
+ ).catch((err) => logger$j.error("Failed to save settings:", err));
189
226
  }
190
227
  function saveSettings() {
191
228
  saveDirty = true;
@@ -289,6 +326,7 @@ function assertPermittedNavigationURL(url) {
289
326
  }
290
327
  const MAX_CUSTOM_HISTORY = 50;
291
328
  const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
329
+ const logger$i = createLogger("Tab");
292
330
  class Tab {
293
331
  id;
294
332
  view;
@@ -329,7 +367,7 @@ class Tab {
329
367
  guardedLoadURL(url, options) {
330
368
  const blockReason = this.getNavigationBlockReason(url);
331
369
  if (blockReason) {
332
- console.warn(`[Tab] ${blockReason}`);
370
+ logger$i.warn(blockReason);
333
371
  return blockReason;
334
372
  }
335
373
  void this.view.webContents.loadURL(url, options);
@@ -390,7 +428,7 @@ class Tab {
390
428
  wc.setWindowOpenHandler(({ url, disposition }) => {
391
429
  const error = this.getNavigationBlockReason(url);
392
430
  if (error) {
393
- console.warn(`[Tab] ${error}`);
431
+ logger$i.warn(error);
394
432
  return { action: "deny" };
395
433
  }
396
434
  this.onOpenUrl?.({
@@ -404,7 +442,7 @@ class Tab {
404
442
  const error = this.getNavigationBlockReason(url);
405
443
  if (!error) return;
406
444
  event.preventDefault();
407
- console.warn(`[Tab] ${context}: ${error}`);
445
+ logger$i.warn(`${context}: ${error}`);
408
446
  };
409
447
  wc.on("will-navigate", (event, url) => {
410
448
  blockNavigation(event, url, "Blocked top-level navigation");
@@ -468,8 +506,7 @@ class Tab {
468
506
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
469
507
  ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
470
508
  ::-webkit-scrollbar-corner { background: transparent; }
471
- `).catch(() => {
472
- });
509
+ `).catch((err) => logger$i.warn("Failed to inject scrollbar CSS:", err));
473
510
  });
474
511
  wc.on("page-favicon-updated", (_, favicons) => {
475
512
  this._state.favicon = favicons[0] || "";
@@ -491,7 +528,8 @@ class Tab {
491
528
  })()`
492
529
  ).then((highlightedText) => {
493
530
  this.buildContextMenu(wc, params, highlightedText.trim());
494
- }).catch(() => {
531
+ }).catch((err) => {
532
+ logger$i.warn("Failed to inspect highlighted text for context menu:", err);
495
533
  this.buildContextMenu(wc, params, "");
496
534
  });
497
535
  });
@@ -722,8 +760,7 @@ class Tab {
722
760
  document.addEventListener('mouseup', window.__vesselHighlightHandler);
723
761
  }
724
762
  })()
725
- `).catch(() => {
726
- });
763
+ `).catch((err) => logger$i.warn("Failed to inject highlight listener:", err));
727
764
  } else {
728
765
  void wc.executeJavaScript(`
729
766
  (function() {
@@ -734,8 +771,7 @@ class Tab {
734
771
  delete window.__vesselHighlightHandler;
735
772
  }
736
773
  })()
737
- `).catch(() => {
738
- });
774
+ `).catch((err) => logger$i.warn("Failed to remove highlight listener:", err));
739
775
  }
740
776
  }
741
777
  get webContentsId() {
@@ -746,6 +782,7 @@ class Tab {
746
782
  this.view.webContents.close();
747
783
  }
748
784
  }
785
+ const logger$h = createLogger("JsonPersistence");
749
786
  function canUseSafeStorage() {
750
787
  try {
751
788
  return electron.safeStorage.isEncryptionAvailable();
@@ -810,9 +847,7 @@ function createDebouncedJsonPersistence({
810
847
  data,
811
848
  typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
812
849
  )
813
- ).catch(
814
- (err) => console.error(`[Vessel] Failed to save ${logLabel}:`, err)
815
- );
850
+ ).catch((err) => logger$h.error(`Failed to save ${logLabel}:`, err));
816
851
  };
817
852
  const schedule = () => {
818
853
  saveDirty2 = true;
@@ -2306,6 +2341,7 @@ function destroyAllSessions() {
2306
2341
  }
2307
2342
  sessions.clear();
2308
2343
  }
2344
+ const logger$g = createLogger("TabManager");
2309
2345
  class TabManager {
2310
2346
  tabs = /* @__PURE__ */ new Map();
2311
2347
  order = [];
@@ -2498,8 +2534,9 @@ class TabManager {
2498
2534
  color: h.color
2499
2535
  }));
2500
2536
  if (entries.length > 0) {
2501
- void highlightBatchOnPage(wc, entries).catch(() => {
2502
- });
2537
+ void highlightBatchOnPage(wc, entries).catch(
2538
+ (err) => logger$g.warn("Failed to batch highlight:", err)
2539
+ );
2503
2540
  }
2504
2541
  }
2505
2542
  onHighlightCapture(callback) {
@@ -2519,11 +2556,13 @@ class TabManager {
2519
2556
  try {
2520
2557
  const result = await captureSelectionHighlight(wc);
2521
2558
  if (result.success && result.text) {
2522
- await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
2523
- });
2559
+ await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
2560
+ (err) => logger$g.warn("Failed to capture highlight:", err)
2561
+ );
2524
2562
  }
2525
2563
  this.highlightCaptureCallback?.(result);
2526
- } catch {
2564
+ } catch (err) {
2565
+ logger$g.warn("Failed to capture highlight from page:", err);
2527
2566
  this.highlightCaptureCallback?.({
2528
2567
  success: false,
2529
2568
  message: "Could not capture selection"
@@ -2547,7 +2586,8 @@ class TabManager {
2547
2586
  if (tabUrl === normalized) {
2548
2587
  void this.removeHighlightMarksForText(wc, text);
2549
2588
  }
2550
- } catch {
2589
+ } catch (err) {
2590
+ logger$g.warn("Failed to remove highlight from matching tab:", err);
2551
2591
  }
2552
2592
  }
2553
2593
  this.highlightCaptureCallback?.({
@@ -2577,11 +2617,13 @@ class TabManager {
2577
2617
  void 0,
2578
2618
  void 0,
2579
2619
  color
2580
- ).catch(() => {
2581
- });
2620
+ ).catch(
2621
+ (err) => logger$g.warn("Failed to update highlight color:", err)
2622
+ );
2582
2623
  });
2583
2624
  }
2584
- } catch {
2625
+ } catch (err) {
2626
+ logger$g.warn("Failed to iterate highlights for color change:", err);
2585
2627
  }
2586
2628
  }
2587
2629
  this.highlightCaptureCallback?.({
@@ -2602,8 +2644,9 @@ class TabManager {
2602
2644
  }
2603
2645
  });
2604
2646
  })()`
2605
- ).catch(() => {
2606
- });
2647
+ ).catch(
2648
+ (err) => logger$g.warn("Failed to remove highlight marks:", err)
2649
+ );
2607
2650
  }
2608
2651
  broadcastState() {
2609
2652
  const states = this.getAllStates();
@@ -3685,6 +3728,23 @@ function addIfPresent(target, key, value) {
3685
3728
  target[key] = value;
3686
3729
  }
3687
3730
  }
3731
+ function okResult(value) {
3732
+ return {
3733
+ ok: true,
3734
+ ...value ?? {}
3735
+ };
3736
+ }
3737
+ function errorResult(error, value) {
3738
+ return {
3739
+ ok: false,
3740
+ error,
3741
+ ...value ?? {}
3742
+ };
3743
+ }
3744
+ function getErrorMessage(error, fallback = "Unknown error") {
3745
+ return error instanceof Error && error.message ? error.message : fallback;
3746
+ }
3747
+ const logger$f = createLogger("Premium");
3688
3748
  const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
3689
3749
  const FREE_TOOL_ITERATION_LIMIT = 50;
3690
3750
  const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -3752,22 +3812,18 @@ async function getCheckoutUrl(email) {
3752
3812
  });
3753
3813
  if (!res.ok) {
3754
3814
  const body = await res.text();
3755
- return { ok: false, error: body || `HTTP ${res.status}` };
3815
+ return errorResult(body || `HTTP ${res.status}`);
3756
3816
  }
3757
3817
  const { url } = await res.json();
3758
- return { ok: true, url };
3818
+ return okResult({ url });
3759
3819
  } catch (err) {
3760
- return {
3761
- ok: false,
3762
- error: err instanceof Error ? err.message : "Failed to create checkout"
3763
- };
3820
+ return errorResult(getErrorMessage(err, "Failed to create checkout"));
3764
3821
  }
3765
3822
  }
3766
3823
  async function getPortalUrl() {
3767
- return {
3768
- ok: false,
3769
- error: "Billing portal access is temporarily disabled until authenticated customer access is implemented."
3770
- };
3824
+ return errorResult(
3825
+ "Billing portal access is temporarily disabled until authenticated customer access is implemented."
3826
+ );
3771
3827
  }
3772
3828
  async function verifySubscription(identifier) {
3773
3829
  const current = loadSettings().premium;
@@ -3782,7 +3838,7 @@ async function verifySubscription(identifier) {
3782
3838
  body: JSON.stringify({ identifier: verificationIdentifier })
3783
3839
  });
3784
3840
  if (!res.ok) {
3785
- console.warn("[Vessel Premium] Verification API returned", res.status);
3841
+ logger$f.warn("Verification API returned a non-OK status:", res.status);
3786
3842
  return current;
3787
3843
  }
3788
3844
  const data = await res.json();
@@ -3797,14 +3853,14 @@ async function verifySubscription(identifier) {
3797
3853
  setSetting("premium", updated);
3798
3854
  return updated;
3799
3855
  } catch (err) {
3800
- console.warn("[Vessel Premium] Verification failed:", err);
3856
+ logger$f.warn("Verification failed:", err);
3801
3857
  return current;
3802
3858
  }
3803
3859
  }
3804
3860
  async function requestActivationCode(email) {
3805
3861
  const normalizedEmail = email.trim().toLowerCase();
3806
3862
  if (!normalizedEmail) {
3807
- return { ok: false, error: "Email is required" };
3863
+ return errorResult("Email is required");
3808
3864
  }
3809
3865
  try {
3810
3866
  const res = await fetch(`${VERIFICATION_API}/activate/start`, {
@@ -3814,38 +3870,29 @@ async function requestActivationCode(email) {
3814
3870
  });
3815
3871
  const data = await res.json().catch(() => ({}));
3816
3872
  if (!res.ok || !data.challengeToken) {
3817
- return {
3818
- ok: false,
3819
- error: data.error || `HTTP ${res.status}`
3820
- };
3873
+ return errorResult(data.error || `HTTP ${res.status}`);
3821
3874
  }
3822
- return {
3823
- ok: true,
3875
+ return okResult({
3824
3876
  email: normalizedEmail,
3825
3877
  challengeToken: data.challengeToken
3826
- };
3878
+ });
3827
3879
  } catch (err) {
3828
- return {
3829
- ok: false,
3830
- error: err instanceof Error ? err.message : "Failed to send code"
3831
- };
3880
+ return errorResult(getErrorMessage(err, "Failed to send code"));
3832
3881
  }
3833
3882
  }
3834
3883
  async function verifyActivationCode(email, code, challengeToken) {
3835
3884
  const normalizedEmail = email.trim().toLowerCase();
3836
3885
  const trimmedCode = code.trim();
3837
3886
  if (!normalizedEmail) {
3838
- return { ok: false, state: getPremiumState(), error: "Email is required" };
3887
+ return errorResult("Email is required", { state: getPremiumState() });
3839
3888
  }
3840
3889
  if (!trimmedCode) {
3841
- return { ok: false, state: getPremiumState(), error: "Code is required" };
3890
+ return errorResult("Code is required", { state: getPremiumState() });
3842
3891
  }
3843
3892
  if (!challengeToken.trim()) {
3844
- return {
3845
- ok: false,
3846
- state: getPremiumState(),
3847
- error: "Request a new activation code and try again."
3848
- };
3893
+ return errorResult("Request a new activation code and try again.", {
3894
+ state: getPremiumState()
3895
+ });
3849
3896
  }
3850
3897
  try {
3851
3898
  const res = await fetch(`${VERIFICATION_API}/activate/verify`, {
@@ -3859,11 +3906,9 @@ async function verifyActivationCode(email, code, challengeToken) {
3859
3906
  });
3860
3907
  const data = await res.json().catch(() => ({}));
3861
3908
  if (!res.ok) {
3862
- return {
3863
- ok: false,
3864
- state: getPremiumState(),
3865
- error: data.error || `HTTP ${res.status}`
3866
- };
3909
+ return errorResult(data.error || `HTTP ${res.status}`, {
3910
+ state: getPremiumState()
3911
+ });
3867
3912
  }
3868
3913
  const updated = {
3869
3914
  status: data.status ?? "free",
@@ -3874,13 +3919,11 @@ async function verifyActivationCode(email, code, challengeToken) {
3874
3919
  expiresAt: data.expiresAt || ""
3875
3920
  };
3876
3921
  setSetting("premium", updated);
3877
- return { ok: isPremiumActiveState(updated), state: updated };
3922
+ return isPremiumActiveState(updated) ? okResult({ state: updated }) : errorResult("Subscription is not active.", { state: updated });
3878
3923
  } catch (err) {
3879
- return {
3880
- ok: false,
3881
- state: getPremiumState(),
3882
- error: err instanceof Error ? err.message : "Failed to verify code"
3883
- };
3924
+ return errorResult(getErrorMessage(err, "Failed to verify code"), {
3925
+ state: getPremiumState()
3926
+ });
3884
3927
  }
3885
3928
  }
3886
3929
  let revalidationTimer = null;
@@ -4376,6 +4419,14 @@ function inferPageSchema(page) {
4376
4419
  confidence
4377
4420
  };
4378
4421
  }
4422
+ const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1 = 1500;
4423
+ const EXTRACT_SCRIPT_TIMEOUT_MS = 3e3;
4424
+ const EXTRACT_TIMEOUT_BASE_MS = 12e3;
4425
+ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
4426
+ const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
4427
+ const MUTATION_SETTLE_AFTER_MS = 1500;
4428
+ const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
4429
+ const logger$e = createLogger("Extractor");
4379
4430
  const EMPTY_PAGE_CONTENT = {
4380
4431
  title: "",
4381
4432
  content: "",
@@ -5092,8 +5143,7 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
5092
5143
  function delay(ms) {
5093
5144
  return new Promise((resolve) => setTimeout(resolve, ms));
5094
5145
  }
5095
- const EXECUTE_SCRIPT_TIMEOUT_MS = 3e3;
5096
- async function waitForDomReady(webContents, timeoutMs = 1500) {
5146
+ async function waitForDomReady(webContents, timeoutMs = DEFAULT_PAGE_SCRIPT_TIMEOUT_MS$1) {
5097
5147
  const deadline = Date.now() + timeoutMs;
5098
5148
  while (Date.now() < deadline) {
5099
5149
  const readyState = await executeScript(
@@ -5119,10 +5169,14 @@ async function executeScript(webContents, script) {
5119
5169
  return await Promise.race([
5120
5170
  webContents.executeJavaScript(script),
5121
5171
  new Promise((resolve) => {
5122
- timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
5172
+ timer = setTimeout(
5173
+ () => resolve(null),
5174
+ EXTRACT_SCRIPT_TIMEOUT_MS
5175
+ );
5123
5176
  })
5124
5177
  ]);
5125
- } catch {
5178
+ } catch (err) {
5179
+ logger$e.warn("Failed to execute page script:", err);
5126
5180
  return null;
5127
5181
  } finally {
5128
5182
  if (timer) {
@@ -5215,8 +5269,6 @@ function mergePageContent(candidates, webContents) {
5215
5269
  url: mergedBase.url || webContents.getURL() || ""
5216
5270
  };
5217
5271
  }
5218
- const EXTRACT_TIMEOUT_BASE_MS = 12e3;
5219
- const EXTRACT_TIMEOUT_MAX_MS = 2e4;
5220
5272
  async function estimateExtractionTimeout(webContents) {
5221
5273
  try {
5222
5274
  const elementCount = await executeScript(
@@ -5230,7 +5282,8 @@ async function estimateExtractionTimeout(webContents) {
5230
5282
  );
5231
5283
  return EXTRACT_TIMEOUT_BASE_MS + extra;
5232
5284
  }
5233
- } catch {
5285
+ } catch (err) {
5286
+ logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
5234
5287
  }
5235
5288
  return EXTRACT_TIMEOUT_BASE_MS;
5236
5289
  }
@@ -5324,8 +5377,6 @@ function attachDestroyCleanup(wc) {
5324
5377
  cleanupTimersForWcId(wc.id);
5325
5378
  });
5326
5379
  }
5327
- const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
5328
- const SETTLE_AFTER_ACTIVITY_MS = 1500;
5329
5380
  function getLatestPageDiff(rawUrl) {
5330
5381
  if (!shouldTrackSnapshotUrl(rawUrl)) return null;
5331
5382
  return latestPageDiffs.get(normalizeUrl(rawUrl)) ?? null;
@@ -5383,8 +5434,8 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
5383
5434
  function computeNextSnapshotDueAt(wcId, now, delayMs) {
5384
5435
  const lastCaptureAt = lastMutationSnapshotAt.get(wcId) || 0;
5385
5436
  const lastActivityAt = lastMutationActivityAt.get(wcId) || 0;
5386
- const earliestAllowedAt = lastCaptureAt + MIN_MUTATION_CAPTURE_INTERVAL_MS;
5387
- const stableAfterActivityAt = lastActivityAt ? lastActivityAt + SETTLE_AFTER_ACTIVITY_MS : 0;
5437
+ const earliestAllowedAt = lastCaptureAt + MUTATION_CAPTURE_INTERVAL_MS;
5438
+ const stableAfterActivityAt = lastActivityAt ? lastActivityAt + MUTATION_SETTLE_AFTER_MS : 0;
5388
5439
  return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
5389
5440
  }
5390
5441
  function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
@@ -6019,6 +6070,9 @@ function isClickReadLoop(names) {
6019
6070
  }
6020
6071
  return clickReadPairs >= 2;
6021
6072
  }
6073
+ function isRecord(value) {
6074
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6075
+ }
6022
6076
  class AnthropicProvider {
6023
6077
  agentToolProfile = "default";
6024
6078
  client;
@@ -6086,7 +6140,7 @@ class AnthropicProvider {
6086
6140
  let textContent = "";
6087
6141
  const toolUseBlocks = [];
6088
6142
  let currentToolUse = null;
6089
- const STREAM_IDLE_TIMEOUT_MS = 3e4;
6143
+ const STREAM_IDLE_TIMEOUT_MS = AGENT_STREAM_IDLE_TIMEOUT_MS;
6090
6144
  let idleTimer = null;
6091
6145
  const resetIdleTimer = () => {
6092
6146
  if (idleTimer) clearTimeout(idleTimer);
@@ -6115,10 +6169,14 @@ class AnthropicProvider {
6115
6169
  }
6116
6170
  } else if (event.type === "content_block_stop" && currentToolUse) {
6117
6171
  try {
6172
+ const input = JSON.parse(currentToolUse.inputJson || "{}");
6173
+ if (!isRecord(input)) {
6174
+ throw new Error("Tool input must be a JSON object");
6175
+ }
6118
6176
  toolUseBlocks.push({
6119
6177
  id: currentToolUse.id,
6120
6178
  name: currentToolUse.name,
6121
- input: JSON.parse(currentToolUse.inputJson || "{}")
6179
+ input
6122
6180
  });
6123
6181
  } catch {
6124
6182
  toolUseBlocks.push({
@@ -6165,7 +6223,7 @@ class AnthropicProvider {
6165
6223
  });
6166
6224
  continue;
6167
6225
  }
6168
- const argSummary = tb.input.url || tb.input.text || tb.input.direction || "";
6226
+ const argSummary = [tb.input.url, tb.input.text, tb.input.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
6169
6227
  onChunk(`
6170
6228
  <<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
6171
6229
  `);
@@ -6392,6 +6450,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
6392
6450
  const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
6393
6451
  const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
6394
6452
  const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
6453
+ const logger$d = createLogger("OpenAIProvider");
6395
6454
  function shouldDebugAgentLoop() {
6396
6455
  const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
6397
6456
  return value === "1" || value === "true";
@@ -6798,9 +6857,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
6798
6857
  function logAgentLoopDebug(payload) {
6799
6858
  if (!shouldDebugAgentLoop()) return;
6800
6859
  try {
6801
- console.log(`[Vessel agent-debug] ${JSON.stringify(payload)}`);
6860
+ logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
6802
6861
  } catch (err) {
6803
- console.warn("[Vessel agent-debug] Failed to serialize debug payload:", err);
6862
+ logger$d.warn("Failed to serialize debug payload:", err);
6804
6863
  }
6805
6864
  }
6806
6865
  function recoverTextEncodedToolCalls(text, availableToolNames) {
@@ -6820,7 +6879,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
6820
6879
  const argsJson = match[2] ?? "{}";
6821
6880
  let parsedArgs = {};
6822
6881
  try {
6823
- parsedArgs = JSON.parse(argsJson);
6882
+ const raw = JSON.parse(argsJson);
6883
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
6884
+ parsedArgs = raw;
6885
+ }
6824
6886
  } catch {
6825
6887
  continue;
6826
6888
  }
@@ -6934,9 +6996,16 @@ class OpenAICompatProvider {
6934
6996
  constructor(config) {
6935
6997
  const meta = PROVIDERS[config.id];
6936
6998
  const baseURL = config.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
6999
+ const isOpenRouter = baseURL.includes("openrouter.ai");
6937
7000
  this.client = new OpenAI({
6938
7001
  apiKey: config.apiKey || "ollama",
6939
- baseURL
7002
+ baseURL,
7003
+ ...isOpenRouter && {
7004
+ defaultHeaders: {
7005
+ "HTTP-Referer": "https://github.com/unmodeled/vessel-browser",
7006
+ "X-Title": "Vessel"
7007
+ }
7008
+ }
6940
7009
  });
6941
7010
  this.providerId = config.id;
6942
7011
  this.model = config.model || meta?.defaultModel || "gpt-4o";
@@ -7217,7 +7286,7 @@ class OpenAICompatProvider {
7217
7286
  }
7218
7287
  continue;
7219
7288
  }
7220
- const argSummary = args.url || args.query || args.text || args.direction || "";
7289
+ const argSummary = [args.url, args.query, args.text, args.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
7221
7290
  onChunk(`
7222
7291
  <<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
7223
7292
  `);
@@ -7375,7 +7444,7 @@ async function fetchProviderModels(config) {
7375
7444
  if (normalized.id === "anthropic") {
7376
7445
  const client2 = new Anthropic({ apiKey: normalized.apiKey });
7377
7446
  const page2 = await client2.models.list();
7378
- return { ok: true, models: page2.data.map((model) => model.id) };
7447
+ return okResult({ models: page2.data.map((model) => model.id) });
7379
7448
  }
7380
7449
  const meta = PROVIDERS[normalized.id];
7381
7450
  const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
@@ -7387,9 +7456,10 @@ async function fetchProviderModels(config) {
7387
7456
  const models = page.data.map((model) => model.id);
7388
7457
  const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
7389
7458
  return {
7390
- ok: true,
7391
- models,
7392
- ...warning ? { warning } : {}
7459
+ ...okResult({
7460
+ models,
7461
+ ...warning ? { warning } : {}
7462
+ })
7393
7463
  };
7394
7464
  }
7395
7465
  function createProvider(config) {
@@ -7404,6 +7474,7 @@ function createProvider(config) {
7404
7474
  return new OpenAICompatProvider(normalized);
7405
7475
  }
7406
7476
  const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
7477
+ const logger$c = createLogger("DevTrace");
7407
7478
  let cachedFactory;
7408
7479
  function createNoopTraceSession() {
7409
7480
  return {
@@ -7436,7 +7507,7 @@ function loadLocalFactory() {
7436
7507
  return cachedFactory;
7437
7508
  }
7438
7509
  } catch (err) {
7439
- console.warn("[dev-trace] Failed to load local trace logger:", err);
7510
+ logger$c.warn("Failed to load local trace logger:", err);
7440
7511
  }
7441
7512
  }
7442
7513
  return cachedFactory;
@@ -11171,22 +11242,35 @@ function formatDeadLinkMessage(label, result) {
11171
11242
  const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
11172
11243
  return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
11173
11244
  }
11245
+ const logger$b = createLogger("Screenshot");
11246
+ const SCREENSHOT_RETRY_COUNT = 3;
11247
+ const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
11174
11248
  async function captureScreenshot(wc) {
11175
- for (let attempt = 0; attempt < 3; attempt += 1) {
11176
- await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
11249
+ for (let attempt = 0; attempt < SCREENSHOT_RETRY_COUNT; attempt += 1) {
11250
+ await new Promise(
11251
+ (resolve) => setTimeout(resolve, SCREENSHOT_RETRY_BASE_DELAY_MS * (attempt + 1))
11252
+ );
11177
11253
  try {
11178
11254
  const image = await wc.capturePage();
11179
11255
  if (!image.isEmpty()) {
11180
11256
  const size = image.getSize();
11181
11257
  const base64 = image.toPNG().toString("base64");
11182
11258
  if (base64) {
11183
- return { ok: true, base64, width: size.width, height: size.height };
11259
+ return okResult({
11260
+ base64,
11261
+ width: size.width,
11262
+ height: size.height
11263
+ });
11184
11264
  }
11185
11265
  }
11186
- } catch {
11266
+ } catch (err) {
11267
+ logger$b.debug(
11268
+ `capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
11269
+ getErrorMessage(err)
11270
+ );
11187
11271
  }
11188
11272
  }
11189
- return { ok: false, error: "Page image was empty after 3 attempts" };
11273
+ return errorResult("Page image was empty after 3 attempts");
11190
11274
  }
11191
11275
  const SESSION_VERSION = 1;
11192
11276
  function getSessionsDir() {
@@ -12081,17 +12165,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
12081
12165
  appliedFilters
12082
12166
  };
12083
12167
  }
12168
+ const logger$a = createLogger("PageActions");
12084
12169
  function getBookmarkMetadataFromArgs(args) {
12085
12170
  return normalizeBookmarkMetadata({
12086
- intent: args.intent,
12087
- expectedContent: args.expectedContent,
12088
- keyFields: args.keyFields,
12089
- agentHints: args.agentHints
12171
+ intent: args.intent ?? args.intent,
12172
+ expectedContent: args.expectedContent ?? args.expected_content,
12173
+ keyFields: args.keyFields ?? args.key_fields,
12174
+ agentHints: args.agentHints ?? args.agent_hints
12090
12175
  });
12091
12176
  }
12092
12177
  const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
12093
12178
  const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
12094
- async function loadPermittedUrl$1(wc, url) {
12179
+ async function loadPermittedUrl(wc, url) {
12095
12180
  assertPermittedNavigationURL(url);
12096
12181
  await wc.loadURL(url);
12097
12182
  }
@@ -12264,7 +12349,9 @@ async function executePageScript(wc, script, options) {
12264
12349
  return PAGE_SCRIPT_TIMEOUT;
12265
12350
  }
12266
12351
  return result;
12267
- } catch {
12352
+ } catch (err) {
12353
+ const label = options?.label ? ` (${options.label})` : "";
12354
+ logger$a.warn(`Failed to execute page script${label}:`, err);
12268
12355
  return null;
12269
12356
  } finally {
12270
12357
  if (timer) {
@@ -12364,7 +12451,8 @@ async function getPostSearchSummary(wc) {
12364
12451
  Search results snapshot:
12365
12452
  ${truncated}`;
12366
12453
  }
12367
- } catch {
12454
+ } catch (err) {
12455
+ logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
12368
12456
  }
12369
12457
  const fallback = await getPostNavSummary(wc);
12370
12458
  return fallback ? `${fallback}
@@ -12386,11 +12474,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
12386
12474
  Page snapshot after navigation:
12387
12475
  ${truncated}`;
12388
12476
  }
12389
- } catch {
12477
+ } catch (err) {
12478
+ logger$a.warn("Failed to build post-click navigation summary:", err);
12390
12479
  }
12391
12480
  return "";
12392
12481
  }
12393
- async function scrollPage$1(wc, deltaY) {
12482
+ async function scrollPage(wc, deltaY) {
12394
12483
  const getScrollY = async () => {
12395
12484
  const scrollY = await executePageScript(
12396
12485
  wc,
@@ -12434,7 +12523,7 @@ async function scrollPage$1(wc, deltaY) {
12434
12523
  movedY: Math.round(afterY - beforeY)
12435
12524
  };
12436
12525
  }
12437
- async function clickElement$1(wc, selector) {
12526
+ async function clickElement(wc, selector) {
12438
12527
  const target = await executePageScript(
12439
12528
  wc,
12440
12529
  `
@@ -12523,7 +12612,7 @@ async function clickElement$1(wc, selector) {
12523
12612
  return "Error: Could not resolve click coordinates";
12524
12613
  }
12525
12614
  if (hiddenWindow) {
12526
- const activationResult = await activateElement$1(wc, selector);
12615
+ const activationResult = await activateElement(wc, selector);
12527
12616
  if (activationResult.startsWith("Error:")) {
12528
12617
  return activationResult;
12529
12618
  }
@@ -12538,7 +12627,7 @@ async function clickElement$1(wc, selector) {
12538
12627
  await sleep(80);
12539
12628
  return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
12540
12629
  }
12541
- async function activateElement$1(wc, selector) {
12630
+ async function activateElement(wc, selector) {
12542
12631
  const activated = await executePageScript(
12543
12632
  wc,
12544
12633
  `
@@ -12570,7 +12659,7 @@ async function activateElement$1(wc, selector) {
12570
12659
  }
12571
12660
  return "Activated element via DOM click";
12572
12661
  }
12573
- async function describeElementForClick$1(wc, selector) {
12662
+ async function describeElementForClick(wc, selector) {
12574
12663
  const result = await executePageScript(
12575
12664
  wc,
12576
12665
  `
@@ -12879,7 +12968,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12879
12968
  return;
12880
12969
  }
12881
12970
  }
12882
- } catch {
12971
+ } catch (err) {
12972
+ logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
12883
12973
  }
12884
12974
  if (snapshot.url && snapshot.url !== wc.getURL()) {
12885
12975
  try {
@@ -12887,14 +12977,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12887
12977
  await wc.loadURL(snapshot.url);
12888
12978
  await waitForLoad(wc, 3e3);
12889
12979
  return;
12890
- } catch {
12980
+ } catch (err) {
12981
+ logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
12891
12982
  }
12892
12983
  }
12893
12984
  if (snapshot.url) {
12894
12985
  try {
12895
12986
  await wc.reload();
12896
12987
  await waitForLoad(wc, 3e3);
12897
- } catch {
12988
+ } catch (err) {
12989
+ logger$a.warn("Failed to restore locale via page reload:", err);
12898
12990
  }
12899
12991
  }
12900
12992
  }
@@ -13025,14 +13117,14 @@ Go back to search results to select the next product.`;
13025
13117
  if (!overlayHint) {
13026
13118
  return cartSummary;
13027
13119
  }
13028
- const dialogActions = await getCartDialogActions$1(wc);
13120
+ const dialogActions = await getCartDialogActions(wc);
13029
13121
  const actionsSuffix = dialogActions ? `
13030
13122
  ${dialogActions}
13031
13123
  Click one of these dialog actions. Do NOT click any other element.` : "";
13032
13124
  return `
13033
13125
  ${overlayHint}${actionsSuffix}${cartSummary}`;
13034
13126
  }
13035
- async function clickResolvedSelector$1(wc, selector) {
13127
+ async function clickResolvedSelector(wc, selector) {
13036
13128
  if (selector.startsWith("__vessel_idx:")) {
13037
13129
  const idx = Number(selector.slice("__vessel_idx:".length));
13038
13130
  const beforeUrl2 = wc.getURL();
@@ -13065,10 +13157,10 @@ Go back and select a different product.`;
13065
13157
  await waitForPotentialNavigation(wc, beforeUrl2);
13066
13158
  const afterUrl2 = wc.getURL();
13067
13159
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
13068
- let idxOverlay = await detectPostClickOverlay$1(wc);
13160
+ let idxOverlay = await detectPostClickOverlay(wc);
13069
13161
  if (!idxOverlay && idxCartMatch) {
13070
13162
  await sleep(1200);
13071
- idxOverlay = await detectPostClickOverlay$1(wc);
13163
+ idxOverlay = await detectPostClickOverlay(wc);
13072
13164
  }
13073
13165
  if (idxCartMatch) {
13074
13166
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
@@ -13077,7 +13169,7 @@ Go back and select a different product.`;
13077
13169
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
13078
13170
  if (hrefMatch) {
13079
13171
  try {
13080
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13172
+ await loadPermittedUrl(wc, hrefMatch[1]);
13081
13173
  await waitForLoad(wc, 8e3);
13082
13174
  const hrefUrl = wc.getURL();
13083
13175
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13132,10 +13224,10 @@ Go back and select a different product.`;
13132
13224
  await waitForPotentialNavigation(wc, beforeUrl2);
13133
13225
  const afterUrl2 = wc.getURL();
13134
13226
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
13135
- let shadowOverlay = await detectPostClickOverlay$1(wc);
13227
+ let shadowOverlay = await detectPostClickOverlay(wc);
13136
13228
  if (!shadowOverlay && shadowCartMatch) {
13137
13229
  await sleep(1200);
13138
- shadowOverlay = await detectPostClickOverlay$1(wc);
13230
+ shadowOverlay = await detectPostClickOverlay(wc);
13139
13231
  }
13140
13232
  if (shadowCartMatch) {
13141
13233
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
@@ -13144,7 +13236,7 @@ Go back and select a different product.`;
13144
13236
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
13145
13237
  if (hrefMatch) {
13146
13238
  try {
13147
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13239
+ await loadPermittedUrl(wc, hrefMatch[1]);
13148
13240
  await waitForLoad(wc, 8e3);
13149
13241
  const hrefUrl = wc.getURL();
13150
13242
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13157,14 +13249,14 @@ ${shadowOverlay}` : `${result}
13157
13249
  Note: Page did not change after click.`;
13158
13250
  }
13159
13251
  const beforeUrl = wc.getURL();
13160
- const elInfo = await describeElementForClick$1(wc, selector);
13252
+ const elInfo = await describeElementForClick(wc, selector);
13161
13253
  if ("error" in elInfo) return `Error: ${elInfo.error}`;
13162
13254
  const cartMatch = isAddToCartText(elInfo.text);
13163
13255
  if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
13164
13256
  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).`;
13165
13257
  }
13166
13258
  if (!cartMatch && recentCartClicks.has(beforeUrl)) {
13167
- const dialogActions = await getCartDialogActions$1(wc);
13259
+ const dialogActions = await getCartDialogActions(wc);
13168
13260
  if (dialogActions) {
13169
13261
  return `Blocked: a cart confirmation dialog is open. Do not click background elements.
13170
13262
  ${dialogActions}
@@ -13187,14 +13279,14 @@ Go back and select a different product.`;
13187
13279
  }
13188
13280
  const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
13189
13281
  const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
13190
- const clickResult = await clickElement$1(wc, selector);
13282
+ const clickResult = await clickElement(wc, selector);
13191
13283
  if (clickResult.startsWith("Error:")) return clickResult;
13192
13284
  await waitForPotentialNavigation(wc, beforeUrl);
13193
13285
  const afterUrl = wc.getURL();
13194
13286
  if (afterUrl !== beforeUrl) {
13195
13287
  return `${clickText} -> ${afterUrl}`;
13196
13288
  }
13197
- const overlayHint = await detectPostClickOverlay$1(wc);
13289
+ const overlayHint = await detectPostClickOverlay(wc);
13198
13290
  if (overlayHint) {
13199
13291
  if (cartMatch) {
13200
13292
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
@@ -13208,7 +13300,7 @@ ${overlayHint}`;
13208
13300
  }
13209
13301
  if (cartMatch) {
13210
13302
  await sleep(1200);
13211
- const delayedOverlayHint = await detectPostClickOverlay$1(wc);
13303
+ const delayedOverlayHint = await detectPostClickOverlay(wc);
13212
13304
  if (delayedOverlayHint) {
13213
13305
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
13214
13306
  wc,
@@ -13221,7 +13313,7 @@ ${overlayHint}`;
13221
13313
  beforeUrl
13222
13314
  )}`;
13223
13315
  }
13224
- const activationResult = await activateElement$1(wc, selector);
13316
+ const activationResult = await activateElement(wc, selector);
13225
13317
  if (!activationResult.startsWith("Error:")) {
13226
13318
  await waitForPotentialNavigation(wc, beforeUrl);
13227
13319
  const fallbackUrl = wc.getURL();
@@ -13229,7 +13321,7 @@ ${overlayHint}`;
13229
13321
  return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
13230
13322
  }
13231
13323
  }
13232
- const postActivationOverlayHint = await detectPostClickOverlay$1(wc);
13324
+ const postActivationOverlayHint = await detectPostClickOverlay(wc);
13233
13325
  if (postActivationOverlayHint) {
13234
13326
  return `${clickText} (${clickResult})
13235
13327
  ${postActivationOverlayHint}`;
@@ -13239,13 +13331,14 @@ ${postActivationOverlayHint}`;
13239
13331
  const validation = await validateLinkDestination(elInfo.href);
13240
13332
  if (validation.status !== "dead") {
13241
13333
  try {
13242
- await loadPermittedUrl$1(wc, elInfo.href);
13334
+ await loadPermittedUrl(wc, elInfo.href);
13243
13335
  await waitForLoad(wc, 8e3);
13244
13336
  const hrefFallbackUrl = wc.getURL();
13245
13337
  if (hrefFallbackUrl !== beforeUrl) {
13246
13338
  return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
13247
13339
  }
13248
- } catch {
13340
+ } catch (err) {
13341
+ logger$a.warn("Failed href fallback after click, returning generic click result:", err);
13249
13342
  }
13250
13343
  }
13251
13344
  }
@@ -13289,11 +13382,12 @@ async function tryAutoDismissCartDialog(wc) {
13289
13382
  await sleep(500);
13290
13383
  return result;
13291
13384
  }
13292
- } catch {
13385
+ } catch (err) {
13386
+ logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
13293
13387
  }
13294
13388
  return null;
13295
13389
  }
13296
- async function getCartDialogActions$1(wc) {
13390
+ async function getCartDialogActions(wc) {
13297
13391
  const result = await executePageScript(
13298
13392
  wc,
13299
13393
  `
@@ -13338,7 +13432,7 @@ async function getCartDialogActions$1(wc) {
13338
13432
  return `Available dialog actions:
13339
13433
  ${result.actions.join("\n")}`;
13340
13434
  }
13341
- async function detectPostClickOverlay$1(wc) {
13435
+ async function detectPostClickOverlay(wc) {
13342
13436
  const result = await executePageScript(
13343
13437
  wc,
13344
13438
  `
@@ -13444,7 +13538,7 @@ async function detectPostClickOverlay$1(wc) {
13444
13538
  const desc = result.label ? ` ("${result.label}")` : "";
13445
13539
  return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
13446
13540
  }
13447
- async function dismissPopup$1(wc) {
13541
+ async function dismissPopup(wc) {
13448
13542
  const before = await extractContent(wc);
13449
13543
  const initialBlocking = before.overlays.filter(
13450
13544
  (overlay) => overlay.blocksInteraction
@@ -13497,7 +13591,7 @@ async function dismissPopup$1(wc) {
13497
13591
  if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
13498
13592
  return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
13499
13593
  }
13500
- const dialogActions = await getCartDialogActions$1(wc);
13594
+ const dialogActions = await getCartDialogActions(wc);
13501
13595
  return `Cannot dismiss: this is a cart confirmation dialog. Item is in your cart.${dialogActions ? "\n" + dialogActions + "\nClick one of these instead." : " Use read_page to see dialog actions."}`;
13502
13596
  }
13503
13597
  }
@@ -13657,7 +13751,7 @@ async function dismissPopup$1(wc) {
13657
13751
  if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
13658
13752
  continue;
13659
13753
  }
13660
- const result = await clickElement$1(wc, candidate.selector);
13754
+ const result = await clickElement(wc, candidate.selector);
13661
13755
  if (result.startsWith("Error:")) continue;
13662
13756
  await sleep(250);
13663
13757
  const postClickLocale = await getLocaleSnapshot(wc);
@@ -13699,7 +13793,7 @@ function describeOverlayState(page) {
13699
13793
  }
13700
13794
  async function clickOverlayCandidate(wc, action) {
13701
13795
  if (!action?.selector) return null;
13702
- const result = await clickResolvedSelector$1(wc, action.selector);
13796
+ const result = await clickResolvedSelector(wc, action.selector);
13703
13797
  return `${action.label || action.selector}: ${result}`;
13704
13798
  }
13705
13799
  async function tryDismissConsentIframe(wc) {
@@ -13910,7 +14004,7 @@ Submitted modal: ${submitResult}`;
13910
14004
  }
13911
14005
  }
13912
14006
  if (!actionMessage) {
13913
- actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
14007
+ actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
13914
14008
  }
13915
14009
  steps.push(actionMessage);
13916
14010
  if (overlay.kind === "cookie_consent") {
@@ -14085,7 +14179,7 @@ async function fillFormFields(wc, fields) {
14085
14179
  });
14086
14180
  continue;
14087
14181
  }
14088
- const result = await setElementValue$1(
14182
+ const result = await setElementValue(
14089
14183
  wc,
14090
14184
  selector,
14091
14185
  String(field.value || "")
@@ -14094,14 +14188,14 @@ async function fillFormFields(wc, fields) {
14094
14188
  }
14095
14189
  return results;
14096
14190
  }
14097
- function getTabByMatch$1(tabManager, match) {
14191
+ function getTabByMatch(tabManager, match) {
14098
14192
  if (!match) return null;
14099
14193
  const lowered = match.toLowerCase();
14100
14194
  return tabManager.getAllStates().find(
14101
14195
  (tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
14102
14196
  ) || null;
14103
14197
  }
14104
- function isDangerousAction$1(name) {
14198
+ function isDangerousAction(name) {
14105
14199
  return [
14106
14200
  "navigate",
14107
14201
  "open_bookmark",
@@ -14112,6 +14206,7 @@ function isDangerousAction$1(name) {
14112
14206
  "press_key",
14113
14207
  "create_tab",
14114
14208
  "switch_tab",
14209
+ "close_tab",
14115
14210
  "restore_checkpoint",
14116
14211
  "load_session",
14117
14212
  "login",
@@ -14120,7 +14215,7 @@ function isDangerousAction$1(name) {
14120
14215
  "paginate"
14121
14216
  ].includes(name);
14122
14217
  }
14123
- async function setElementValue$1(wc, selector, value) {
14218
+ async function setElementValue(wc, selector, value) {
14124
14219
  if (selector.startsWith("__vessel_idx:")) {
14125
14220
  const idx = Number(selector.slice("__vessel_idx:".length));
14126
14221
  const result2 = await executePageScript(
@@ -14224,7 +14319,7 @@ async function setElementValue$1(wc, selector, value) {
14224
14319
  );
14225
14320
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14226
14321
  }
14227
- async function typeKeystroke$1(wc, selector, value) {
14322
+ async function typeKeystroke(wc, selector, value) {
14228
14323
  const result = await executePageScript(
14229
14324
  wc,
14230
14325
  `
@@ -14272,7 +14367,7 @@ async function typeKeystroke$1(wc, selector, value) {
14272
14367
  );
14273
14368
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14274
14369
  }
14275
- async function hoverElement$1(wc, selector) {
14370
+ async function hoverElement(wc, selector) {
14276
14371
  const pos = await wc.executeJavaScript(`
14277
14372
  (function() {
14278
14373
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14302,7 +14397,7 @@ async function hoverElement$1(wc, selector) {
14302
14397
  const label = typeof pos.label === "string" ? pos.label : "element";
14303
14398
  return `Hovered: ${label}`;
14304
14399
  }
14305
- async function focusElement$1(wc, selector) {
14400
+ async function focusElement(wc, selector) {
14306
14401
  return wc.executeJavaScript(`
14307
14402
  (function() {
14308
14403
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14316,7 +14411,7 @@ async function focusElement$1(wc, selector) {
14316
14411
  })()
14317
14412
  `);
14318
14413
  }
14319
- async function waitForCondition$1(wc, args) {
14414
+ async function waitForCondition(wc, args) {
14320
14415
  const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
14321
14416
  const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
14322
14417
  const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
@@ -14375,8 +14470,8 @@ function findCheckpoint(checkpoints, args) {
14375
14470
  }
14376
14471
  return null;
14377
14472
  }
14378
- function resolveBookmarkFolderTarget$1(args) {
14379
- const folderId = typeof args.folderId === "string" ? args.folderId.trim() : "";
14473
+ function resolveBookmarkFolderTarget(args) {
14474
+ const folderId = typeof args.folderId === "string" ? args.folderId.trim() : typeof args.folder_id === "string" ? args.folder_id.trim() : "";
14380
14475
  if (folderId) {
14381
14476
  if (folderId === UNSORTED_ID) {
14382
14477
  return {
@@ -14390,7 +14485,7 @@ function resolveBookmarkFolderTarget$1(args) {
14390
14485
  }
14391
14486
  return { folderId: folder2.id, folderName: folder2.name };
14392
14487
  }
14393
- const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
14488
+ const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
14394
14489
  if (!folderName || folderName.toLowerCase() === "unsorted") {
14395
14490
  return {
14396
14491
  folderId: UNSORTED_ID,
@@ -14401,10 +14496,11 @@ function resolveBookmarkFolderTarget$1(args) {
14401
14496
  if (existing) {
14402
14497
  return { folderId: existing.id, folderName: existing.name };
14403
14498
  }
14404
- if (args.createFolderIfMissing === false) {
14499
+ const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
14500
+ if (createIfMissing === false) {
14405
14501
  return { folderName, error: `Folder "${folderName}" not found` };
14406
14502
  }
14407
- const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : void 0;
14503
+ const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
14408
14504
  const { folder } = ensureFolder(folderName, folderSummary);
14409
14505
  return {
14410
14506
  folderId: folder.id,
@@ -14412,27 +14508,27 @@ function resolveBookmarkFolderTarget$1(args) {
14412
14508
  createdFolder: folder.name
14413
14509
  };
14414
14510
  }
14415
- function formatFolderStatus$1(limit = 6) {
14511
+ function formatFolderStatus(limit = 6) {
14416
14512
  const folders = listFolderOverviews();
14417
14513
  const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
14418
14514
  return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
14419
14515
  }
14420
- function describeFolder$1(folderId) {
14516
+ function describeFolder(folderId) {
14421
14517
  if (!folderId || folderId === UNSORTED_ID) {
14422
14518
  return "Unsorted";
14423
14519
  }
14424
14520
  return getFolder(folderId)?.name ?? folderId;
14425
14521
  }
14426
- function composeDuplicateBookmarkResponse$1(args) {
14522
+ function composeDuplicateBookmarkResponse(args) {
14427
14523
  return `Bookmark already exists for ${args.url} in "${args.folderName}" (id=${args.bookmarkId}). Retry with onDuplicate="update" to refresh the existing bookmark or onDuplicate="duplicate" to keep both entries.`;
14428
14524
  }
14429
- function composeFolderAwareResponse$1(message, createdFolder) {
14525
+ function composeFolderAwareResponse(message, createdFolder) {
14430
14526
  const prefix = createdFolder ? `Created folder "${createdFolder}".
14431
14527
  ` : "";
14432
14528
  return `${prefix}${message}
14433
- ${formatFolderStatus$1()}`;
14529
+ ${formatFolderStatus()}`;
14434
14530
  }
14435
- async function selectOption$1(wc, args) {
14531
+ async function selectOption(wc, args) {
14436
14532
  const selector = await resolveSelector(wc, args.index, args.selector);
14437
14533
  if (!selector) return "Error: No select element index or selector provided";
14438
14534
  const result = await executePageScript(
@@ -14468,7 +14564,7 @@ async function selectOption$1(wc, args) {
14468
14564
  );
14469
14565
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
14470
14566
  }
14471
- async function submitForm$1(wc, args) {
14567
+ async function submitForm(wc, args) {
14472
14568
  const beforeUrl = wc.getURL();
14473
14569
  let selector = await resolveSelector(wc, args.index, args.selector);
14474
14570
  if (!selector) {
@@ -14609,7 +14705,7 @@ async function submitForm$1(wc, args) {
14609
14705
  if (formInfo.params) {
14610
14706
  url.search = formInfo.params;
14611
14707
  }
14612
- await loadPermittedUrl$1(wc, url.toString());
14708
+ await loadPermittedUrl(wc, url.toString());
14613
14709
  await waitForPotentialNavigation(wc, beforeUrl);
14614
14710
  const afterUrl = wc.getURL();
14615
14711
  return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
@@ -14651,8 +14747,20 @@ async function submitForm$1(wc, args) {
14651
14747
  }
14652
14748
  return "Submitted form";
14653
14749
  }
14750
+ async function pressKeyDirect(wc, key, index, selector) {
14751
+ return pressKey(wc, { key, index, selector });
14752
+ }
14753
+ async function submitFormDirect(wc, index, selector) {
14754
+ return submitForm(wc, { index, selector });
14755
+ }
14756
+ async function selectOptionDirect(wc, index, selector, label, value) {
14757
+ return selectOption(wc, { index, selector, label, value });
14758
+ }
14759
+ async function waitForConditionDirect(wc, text, selector, timeoutMs) {
14760
+ return waitForCondition(wc, { text, selector, timeoutMs });
14761
+ }
14654
14762
  async function clickElementBySelector(wc, selector) {
14655
- return clickResolvedSelector$1(wc, selector);
14763
+ return clickResolvedSelector(wc, selector);
14656
14764
  }
14657
14765
  function normalizeSearchQuery(query) {
14658
14766
  return query.replace(/\s+/g, " ").trim();
@@ -14936,7 +15044,7 @@ async function searchPage(wc, args) {
14936
15044
  const shortcut = buildSearchShortcut(wc.getURL(), query);
14937
15045
  if (shortcut) {
14938
15046
  const beforeUrl2 = wc.getURL();
14939
- await loadPermittedUrl$1(wc, shortcut.url);
15047
+ await loadPermittedUrl(wc, shortcut.url);
14940
15048
  await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
14941
15049
  const afterUrl2 = wc.getURL();
14942
15050
  const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
@@ -14954,13 +15062,13 @@ async function searchPage(wc, args) {
14954
15062
  if (!searchInfo?.selector) {
14955
15063
  return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
14956
15064
  }
14957
- const fillResult = await setElementValue$1(wc, searchInfo.selector, query);
15065
+ const fillResult = await setElementValue(wc, searchInfo.selector, query);
14958
15066
  if (fillResult.startsWith("Error:")) {
14959
15067
  return fillResult;
14960
15068
  }
14961
15069
  await sleep(100);
14962
15070
  const beforeUrl = wc.getURL();
14963
- const keyResult = await pressKey$1(wc, {
15071
+ const keyResult = await pressKey(wc, {
14964
15072
  key: "Enter",
14965
15073
  selector: searchInfo.selector
14966
15074
  });
@@ -14984,7 +15092,7 @@ async function searchPage(wc, args) {
14984
15092
  }
14985
15093
  return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
14986
15094
  }
14987
- async function pressKey$1(wc, args) {
15095
+ async function pressKey(wc, args) {
14988
15096
  const key = typeof args.key === "string" ? args.key.trim() : "";
14989
15097
  if (!key) return "Error: No key provided";
14990
15098
  const selector = await resolveSelector(wc, args.index, args.selector);
@@ -15196,9 +15304,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
15196
15304
  "metrics",
15197
15305
  "wait_for_navigation"
15198
15306
  ]);
15199
- async function executeAction(name, rawArgs, ctx) {
15307
+ async function executeAction(name, args, ctx) {
15200
15308
  name = normalizeToolAlias(name);
15201
- const args = rawArgs;
15202
15309
  if (!KNOWN_TOOLS.has(name)) {
15203
15310
  for (const known of KNOWN_TOOLS) {
15204
15311
  if (name.startsWith(known) && name.length > known.length) {
@@ -15247,7 +15354,7 @@ async function executeAction(name, rawArgs, ctx) {
15247
15354
  name,
15248
15355
  args,
15249
15356
  tabId,
15250
- dangerous: isDangerousAction$1(name),
15357
+ dangerous: isDangerousAction(name),
15251
15358
  executor: async () => {
15252
15359
  switch (name) {
15253
15360
  case "screenshot": {
@@ -15295,7 +15402,7 @@ async function executeAction(name, rawArgs, ctx) {
15295
15402
  case "switch_tab": {
15296
15403
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15297
15404
  if (!targetId) {
15298
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15405
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15299
15406
  }
15300
15407
  if (!targetId) return "Error: No matching tab found";
15301
15408
  ctx.tabManager.switchTab(targetId);
@@ -15389,7 +15496,7 @@ async function executeAction(name, rawArgs, ctx) {
15389
15496
  if (!selector) {
15390
15497
  return "Error: No element index, selector, or visible text provided";
15391
15498
  }
15392
- return clickResolvedSelector$1(wc, selector);
15499
+ return clickResolvedSelector(wc, selector);
15393
15500
  }
15394
15501
  case "inspect_element": {
15395
15502
  if (!wc) return "Error: No active tab";
@@ -15421,18 +15528,18 @@ async function executeAction(name, rawArgs, ctx) {
15421
15528
  if (!selector) return "Error: No element index or selector provided";
15422
15529
  const mode = typeof args.mode === "string" ? args.mode : "default";
15423
15530
  if (mode === "keystroke") {
15424
- return typeKeystroke$1(wc, selector, String(args.text || ""));
15531
+ return typeKeystroke(wc, selector, String(args.text || ""));
15425
15532
  }
15426
- return setElementValue$1(wc, selector, String(args.text || ""));
15533
+ return setElementValue(wc, selector, String(args.text || ""));
15427
15534
  }
15428
15535
  case "select_option": {
15429
15536
  if (!wc) return "Error: No active tab";
15430
- return selectOption$1(wc, args);
15537
+ return selectOption(wc, args);
15431
15538
  }
15432
15539
  case "submit_form": {
15433
15540
  if (!wc) return "Error: No active tab";
15434
15541
  const beforeUrl = wc.getURL();
15435
- const result2 = await submitForm$1(wc, args);
15542
+ const result2 = await submitForm(wc, args);
15436
15543
  if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
15437
15544
  return result2;
15438
15545
  }
@@ -15443,7 +15550,7 @@ async function executeAction(name, rawArgs, ctx) {
15443
15550
  case "press_key": {
15444
15551
  if (!wc) return "Error: No active tab";
15445
15552
  const beforeUrl = wc.getURL();
15446
- const result2 = await pressKey$1(wc, args);
15553
+ const result2 = await pressKey(wc, args);
15447
15554
  const key = typeof args.key === "string" ? args.key.trim() : "";
15448
15555
  if (key === "Enter") {
15449
15556
  await waitForPotentialNavigation(wc, beforeUrl, 3e3);
@@ -15460,20 +15567,20 @@ async function executeAction(name, rawArgs, ctx) {
15460
15567
  if (!wc) return "Error: No active tab";
15461
15568
  const pixels = coerceOptionalNumber(args.amount) ?? 500;
15462
15569
  const dir = args.direction === "up" ? -pixels : pixels;
15463
- const result2 = await scrollPage$1(wc, dir);
15570
+ const result2 = await scrollPage(wc, dir);
15464
15571
  return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
15465
15572
  }
15466
15573
  case "hover": {
15467
15574
  if (!wc) return "Error: No active tab";
15468
15575
  const selector = await resolveSelector(wc, args.index, args.selector);
15469
15576
  if (!selector) return "Error: No element index or selector provided";
15470
- return hoverElement$1(wc, selector);
15577
+ return hoverElement(wc, selector);
15471
15578
  }
15472
15579
  case "focus": {
15473
15580
  if (!wc) return "Error: No active tab";
15474
15581
  const selector = await resolveSelector(wc, args.index, args.selector);
15475
15582
  if (!selector) return "Error: No element index or selector provided";
15476
- return focusElement$1(wc, selector);
15583
+ return focusElement(wc, selector);
15477
15584
  }
15478
15585
  case "set_ad_blocking": {
15479
15586
  const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
@@ -15482,7 +15589,7 @@ async function executeAction(name, rawArgs, ctx) {
15482
15589
  }
15483
15590
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15484
15591
  if (!targetId) {
15485
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15592
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15486
15593
  }
15487
15594
  if (!targetId) {
15488
15595
  targetId = ctx.tabManager.getActiveTabId() || "";
@@ -15501,7 +15608,7 @@ async function executeAction(name, rawArgs, ctx) {
15501
15608
  }
15502
15609
  case "dismiss_popup": {
15503
15610
  if (!wc) return "Error: No active tab";
15504
- return dismissPopup$1(wc);
15611
+ return dismissPopup(wc);
15505
15612
  }
15506
15613
  case "clear_overlays": {
15507
15614
  if (!wc) return "Error: No active tab";
@@ -15524,7 +15631,8 @@ async function executeAction(name, rawArgs, ctx) {
15524
15631
  }, 6e3)
15525
15632
  )
15526
15633
  ]);
15527
- } catch {
15634
+ } catch (err) {
15635
+ logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
15528
15636
  content = null;
15529
15637
  }
15530
15638
  if (!content || content.content.length === 0) {
@@ -15540,11 +15648,13 @@ async function executeAction(name, rawArgs, ctx) {
15540
15648
  extractContent(wc),
15541
15649
  new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
15542
15650
  ]);
15543
- } catch {
15651
+ } catch (err) {
15652
+ logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
15544
15653
  content = null;
15545
15654
  }
15546
15655
  }
15547
- } catch {
15656
+ } catch (err) {
15657
+ logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
15548
15658
  }
15549
15659
  }
15550
15660
  if (content && content.content.length > 0) {
@@ -15584,7 +15694,7 @@ ${truncated}`;
15584
15694
  }
15585
15695
  case "wait_for": {
15586
15696
  if (!wc) return "Error: No active tab";
15587
- return waitForCondition$1(wc, args);
15697
+ return waitForCondition(wc, args);
15588
15698
  }
15589
15699
  case "wait_for_navigation": {
15590
15700
  if (!wc) return "Error: No active tab";
@@ -15727,12 +15837,12 @@ ${truncated}`;
15727
15837
  (folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
15728
15838
  );
15729
15839
  if (existing) {
15730
- return composeFolderAwareResponse$1(
15840
+ return composeFolderAwareResponse(
15731
15841
  `Folder "${existing.name}" already exists (id=${existing.id})`
15732
15842
  );
15733
15843
  }
15734
15844
  const folder = createFolderWithSummary(name2, summary);
15735
- return composeFolderAwareResponse$1(
15845
+ return composeFolderAwareResponse(
15736
15846
  `Created folder "${folder.name}" (id=${folder.id})`
15737
15847
  );
15738
15848
  }
@@ -15744,7 +15854,7 @@ ${truncated}`;
15744
15854
  resolvedSelector
15745
15855
  });
15746
15856
  if ("error" in source) return `Error: ${source.error}`;
15747
- const target = resolveBookmarkFolderTarget$1(args);
15857
+ const target = resolveBookmarkFolderTarget(args);
15748
15858
  if (target.error) return target.error;
15749
15859
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
15750
15860
  const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
@@ -15759,10 +15869,10 @@ ${truncated}`;
15759
15869
  }
15760
15870
  );
15761
15871
  if (result2.status === "conflict" && result2.existing) {
15762
- return composeFolderAwareResponse$1(
15763
- composeDuplicateBookmarkResponse$1({
15872
+ return composeFolderAwareResponse(
15873
+ composeDuplicateBookmarkResponse({
15764
15874
  url: source.url,
15765
- folderName: describeFolder$1(target.folderId),
15875
+ folderName: describeFolder(target.folderId),
15766
15876
  bookmarkId: result2.existing.id
15767
15877
  }),
15768
15878
  target.createdFolder
@@ -15771,13 +15881,13 @@ ${truncated}`;
15771
15881
  const bookmark = result2.bookmark;
15772
15882
  if (!bookmark) return "Error: Bookmark save failed";
15773
15883
  const verb = result2.status === "updated" ? "Updated" : "Saved";
15774
- return composeFolderAwareResponse$1(
15775
- `${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15884
+ return composeFolderAwareResponse(
15885
+ `${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15776
15886
  target.createdFolder
15777
15887
  );
15778
15888
  }
15779
15889
  case "organize_bookmark": {
15780
- const target = resolveBookmarkFolderTarget$1(args);
15890
+ const target = resolveBookmarkFolderTarget(args);
15781
15891
  if (target.error) return target.error;
15782
15892
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15783
15893
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15801,8 +15911,8 @@ ${truncated}`;
15801
15911
  if (!updated) {
15802
15912
  return `Bookmark ${existing.id} not found`;
15803
15913
  }
15804
- return composeFolderAwareResponse$1(
15805
- `Organized existing bookmark "${updated.title}" into "${describeFolder$1(updated.folderId)}" (id=${updated.id})`,
15914
+ return composeFolderAwareResponse(
15915
+ `Organized existing bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
15806
15916
  target.createdFolder
15807
15917
  );
15808
15918
  }
@@ -15819,13 +15929,13 @@ ${truncated}`;
15819
15929
  );
15820
15930
  const bookmark = result2.bookmark;
15821
15931
  if (!bookmark) return "Error: Bookmark save failed";
15822
- return composeFolderAwareResponse$1(
15823
- `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15932
+ return composeFolderAwareResponse(
15933
+ `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15824
15934
  target.createdFolder
15825
15935
  );
15826
15936
  }
15827
15937
  case "archive_bookmark": {
15828
- const target = resolveBookmarkFolderTarget$1({ archive: true });
15938
+ const target = resolveBookmarkFolderTarget({ archive: true });
15829
15939
  if (target.error) return target.error;
15830
15940
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15831
15941
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15848,8 +15958,8 @@ ${truncated}`;
15848
15958
  if (!updated) {
15849
15959
  return `Bookmark ${existing.id} not found`;
15850
15960
  }
15851
- return composeFolderAwareResponse$1(
15852
- `Archived bookmark "${updated.title}" into "${describeFolder$1(updated.folderId)}" (id=${updated.id})`,
15961
+ return composeFolderAwareResponse(
15962
+ `Archived bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
15853
15963
  target.createdFolder
15854
15964
  );
15855
15965
  }
@@ -15862,8 +15972,8 @@ ${truncated}`;
15862
15972
  target.folderId,
15863
15973
  note
15864
15974
  );
15865
- return composeFolderAwareResponse$1(
15866
- `Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15975
+ return composeFolderAwareResponse(
15976
+ `Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15867
15977
  target.createdFolder
15868
15978
  );
15869
15979
  }
@@ -15951,7 +16061,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
15951
16061
  let page;
15952
16062
  try {
15953
16063
  page = await extractContent(wc);
15954
- } catch {
16064
+ } catch (err) {
16065
+ logger$a.warn("Failed to extract content for suggest:", err);
15955
16066
  return "Could not read page. Try navigate to a working URL.";
15956
16067
  }
15957
16068
  const suggestions = [];
@@ -16056,7 +16167,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
16056
16167
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
16057
16168
  if (firstSel) {
16058
16169
  const beforeUrl = wc.getURL();
16059
- const submitResult = await submitForm$1(wc, { selector: firstSel });
16170
+ const submitResult = await submitForm(wc, { selector: firstSel });
16060
16171
  await waitForPotentialNavigation(wc, beforeUrl);
16061
16172
  const afterUrl = wc.getURL();
16062
16173
  results.push(
@@ -16104,13 +16215,13 @@ ${results.join("\n")}`;
16104
16215
  );
16105
16216
  if (!passSel)
16106
16217
  return "Error: Could not find password field. Try providing password_selector.";
16107
- const userResult = await setElementValue$1(
16218
+ const userResult = await setElementValue(
16108
16219
  wc,
16109
16220
  userSel,
16110
16221
  String(args.username || "")
16111
16222
  );
16112
16223
  steps.push(userResult);
16113
- const passResult = await setElementValue$1(
16224
+ const passResult = await setElementValue(
16114
16225
  wc,
16115
16226
  passSel,
16116
16227
  String(args.password || "")
@@ -16118,7 +16229,7 @@ ${results.join("\n")}`;
16118
16229
  steps.push(passResult);
16119
16230
  const beforeUrl = wc.getURL();
16120
16231
  if (args.submit_selector) {
16121
- await clickResolvedSelector$1(wc, args.submit_selector);
16232
+ await clickResolvedSelector(wc, args.submit_selector);
16122
16233
  } else {
16123
16234
  const clicked = await executePageScript(
16124
16235
  wc,
@@ -16157,7 +16268,7 @@ ${steps.join("\n")}`;
16157
16268
  if (!wc) return "Error: No active tab";
16158
16269
  const beforeUrl = wc.getURL();
16159
16270
  if (args.selector) {
16160
- return clickResolvedSelector$1(wc, args.selector);
16271
+ return clickResolvedSelector(wc, args.selector);
16161
16272
  }
16162
16273
  const isNext = args.direction === "next";
16163
16274
  const clicked = await executePageScript(
@@ -17282,6 +17393,7 @@ const ALGORITHM = "aes-256-gcm";
17282
17393
  const IV_LENGTH = 12;
17283
17394
  const AUTH_TAG_LENGTH = 16;
17284
17395
  let cachedEntries = null;
17396
+ const logger$9 = createLogger("Vault");
17285
17397
  function getVaultDir() {
17286
17398
  return electron.app.getPath("userData");
17287
17399
  }
@@ -17348,7 +17460,7 @@ function loadVault() {
17348
17460
  cachedEntries = JSON.parse(json);
17349
17461
  return cachedEntries;
17350
17462
  } catch (err) {
17351
- console.error("[Vessel Vault] Failed to load vault:", err);
17463
+ logger$9.error("Failed to load vault:", err);
17352
17464
  throw new Error(
17353
17465
  "Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
17354
17466
  );
@@ -17490,6 +17602,7 @@ async function requestConsent(request) {
17490
17602
  }
17491
17603
  const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
17492
17604
  const MAX_ENTRIES = 1e3;
17605
+ const logger$8 = createLogger("VaultAudit");
17493
17606
  function getAuditPath() {
17494
17607
  return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
17495
17608
  }
@@ -17499,7 +17612,7 @@ function appendAuditEntry(entry) {
17499
17612
  fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
17500
17613
  fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
17501
17614
  } catch (err) {
17502
- console.error("[Vessel Vault] Failed to write audit log:", err);
17615
+ logger$8.error("Failed to write audit log:", err);
17503
17616
  }
17504
17617
  }
17505
17618
  function readAuditLog(limit = 100) {
@@ -17509,20 +17622,13 @@ function readAuditLog(limit = 100) {
17509
17622
  const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
17510
17623
  return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
17511
17624
  } catch (err) {
17512
- console.error("[Vessel Vault] Failed to read audit log:", err);
17625
+ logger$8.error("Failed to read audit log:", err);
17513
17626
  return [];
17514
17627
  }
17515
17628
  }
17516
17629
  let httpServer = null;
17517
17630
  let mcpAuthToken = null;
17518
- function getBookmarkMetadataFromToolArgs(args) {
17519
- return normalizeBookmarkMetadata({
17520
- intent: args.intent,
17521
- expectedContent: args.expected_content,
17522
- keyFields: args.key_fields,
17523
- agentHints: args.agent_hints
17524
- });
17525
- }
17631
+ const logger$7 = createLogger("MCP");
17526
17632
  const MCP_AUTH_FILENAME = "mcp-auth.json";
17527
17633
  function getMcpAuthFilePath() {
17528
17634
  const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
@@ -17558,7 +17664,7 @@ function writeMcpAuthFile(endpoint, token) {
17558
17664
  { mode: 384 }
17559
17665
  );
17560
17666
  } catch (err) {
17561
- console.warn("[Vessel MCP] Failed to write auth file:", err);
17667
+ logger$7.warn("Failed to write auth file:", err);
17562
17668
  }
17563
17669
  }
17564
17670
  function clearMcpAuthFile() {
@@ -17583,15 +17689,17 @@ function clearMcpAuthFile() {
17583
17689
  { mode: 384 }
17584
17690
  );
17585
17691
  } catch (err) {
17586
- console.warn("[Vessel MCP] Failed to clear auth file:", err);
17692
+ logger$7.warn("Failed to clear auth file:", err);
17587
17693
  }
17588
17694
  }
17589
17695
  function asTextResponse(text) {
17590
17696
  return { content: [{ type: "text", text }] };
17591
17697
  }
17592
- async function loadPermittedUrl(wc, url) {
17593
- assertPermittedNavigationURL(url);
17594
- await wc.loadURL(url);
17698
+ function asErrorTextResponse(message) {
17699
+ return asTextResponse(`Error: ${message}`);
17700
+ }
17701
+ function asNoActiveTabResponse() {
17702
+ return asErrorTextResponse("No active tab");
17595
17703
  }
17596
17704
  function asPromptResponse(text) {
17597
17705
  return {
@@ -17606,6 +17714,9 @@ function asPromptResponse(text) {
17606
17714
  ]
17607
17715
  };
17608
17716
  }
17717
+ function isDangerousMcpAction(name) {
17718
+ return name === "close_tab" || isDangerousAction(name);
17719
+ }
17609
17720
  function getActiveTabSummary(tabManager) {
17610
17721
  const activeTab = tabManager.getActiveTab();
17611
17722
  const activeTabId = tabManager.getActiveTabId();
@@ -17622,1298 +17733,162 @@ function getActiveTabSummary(tabManager) {
17622
17733
  humanFocused: true
17623
17734
  };
17624
17735
  }
17625
- function formatFolderStatus(limit = 6) {
17626
- const folders = listFolderOverviews();
17627
- const visible = folders.slice(0, limit);
17628
- const summary = visible.map((folder) => `${folder.name} (${folder.count})`).join(", ");
17629
- return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
17630
- }
17631
- function describeFolder(folderId) {
17632
- if (!folderId || folderId === UNSORTED_ID) {
17633
- return "Unsorted";
17634
- }
17635
- return getFolder(folderId)?.name ?? folderId;
17636
- }
17637
- function resolveBookmarkFolderTarget(args) {
17638
- const folderId = typeof args.folder_id === "string" ? args.folder_id.trim() : "";
17639
- if (folderId) {
17640
- if (folderId === UNSORTED_ID) {
17641
- return {
17642
- folderId: UNSORTED_ID,
17643
- folderName: "Unsorted"
17644
- };
17645
- }
17646
- const folder2 = getFolder(folderId);
17647
- if (!folder2) {
17648
- return { folderName: "Unsorted", error: `Folder ${folderId} not found` };
17736
+ async function getPostActionState(tabManager, name) {
17737
+ const tab = tabManager.getActiveTab();
17738
+ if (!tab) return "";
17739
+ const wc = tab.view.webContents;
17740
+ const navActions = [
17741
+ "navigate",
17742
+ "go_back",
17743
+ "go_forward",
17744
+ "click",
17745
+ "submit_form",
17746
+ "reload",
17747
+ "press_key"
17748
+ ];
17749
+ const interactActions = [
17750
+ "type",
17751
+ "type_text",
17752
+ "select_option",
17753
+ "hover",
17754
+ "focus"
17755
+ ];
17756
+ const tabActions = ["create_tab", "switch_tab", "close_tab"];
17757
+ if (navActions.includes(name)) {
17758
+ let warning = "";
17759
+ try {
17760
+ const page = await extractContent(wc);
17761
+ const issue = getRecoverableAccessIssue(page);
17762
+ if (issue) {
17763
+ const blockedUrl = wc.getURL();
17764
+ const canRecover = [
17765
+ "navigate",
17766
+ "open_bookmark",
17767
+ "click",
17768
+ "submit_form",
17769
+ "reload",
17770
+ "press_key"
17771
+ ].includes(name) && tab.canGoBack();
17772
+ if (canRecover && tab.goBack()) {
17773
+ await waitForLoad(wc);
17774
+ warning = `
17775
+ [warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
17776
+ } else {
17777
+ warning = `
17778
+ [warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
17779
+ }
17780
+ }
17781
+ } catch (err) {
17782
+ logger$7.warn("Failed to compute post-action state warning:", err);
17649
17783
  }
17650
- return { folderId: folder2.id, folderName: folder2.name };
17651
- }
17652
- const requestedName = typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
17653
- if (!requestedName || requestedName.toLowerCase() === "unsorted") {
17654
- return {
17655
- folderId: UNSORTED_ID,
17656
- folderName: "Unsorted"
17657
- };
17784
+ return `${warning}
17785
+ [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
17658
17786
  }
17659
- const existing = findFolderByName(requestedName);
17660
- if (existing) {
17661
- return { folderId: existing.id, folderName: existing.name };
17787
+ if (interactActions.includes(name)) {
17788
+ return `
17789
+ [state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
17662
17790
  }
17663
- const createIfMissing = args.create_folder_if_missing !== false;
17664
- if (!createIfMissing) {
17665
- return {
17666
- folderName: requestedName,
17667
- error: `Folder "${requestedName}" not found`
17668
- };
17669
- }
17670
- const folderSummary = typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
17671
- const { folder } = ensureFolder(requestedName, folderSummary);
17672
- return {
17673
- folderId: folder.id,
17674
- folderName: folder.name,
17675
- createdFolder: folder.name
17676
- };
17677
- }
17678
- function composeFolderAwareResponse(message, createdFolder) {
17679
- const prefix = createdFolder ? `Created folder "${createdFolder}".
17680
- ` : "";
17681
- return `${prefix}${message}
17682
- ${formatFolderStatus()}`;
17683
- }
17684
- function composeDuplicateBookmarkResponse(args) {
17685
- return `Bookmark already exists for ${args.url} in "${args.folderName}" (id=${args.bookmarkId}). Retry with on_duplicate="update" to refresh the existing bookmark or on_duplicate="duplicate" to keep both entries.`;
17686
- }
17687
- async function scrollPage(wc, deltaY) {
17688
- const getScrollY = () => wc.executeJavaScript(`
17689
- (function() {
17690
- return Math.max(
17691
- window.scrollY || 0,
17692
- window.pageYOffset || 0,
17693
- document.scrollingElement?.scrollTop || 0,
17694
- document.documentElement?.scrollTop || 0,
17695
- document.body?.scrollTop || 0,
17696
- );
17697
- })()
17698
- `);
17699
- const beforeY = await getScrollY();
17700
- await wc.executeJavaScript(`window.scrollBy(0, ${deltaY})`);
17701
- await sleep(100);
17702
- const afterY = await getScrollY();
17703
- return {
17704
- beforeY,
17705
- afterY,
17706
- movedY: Math.round(afterY - beforeY)
17707
- };
17708
- }
17709
- async function clickElement(wc, selector) {
17710
- const target = await wc.executeJavaScript(`
17711
- (async function() {
17712
- function matchesTarget(candidate, el) {
17713
- return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
17714
- }
17715
-
17716
- function samplePoints(rect) {
17717
- const width = window.innerWidth || document.documentElement?.clientWidth || 0;
17718
- const height = window.innerHeight || document.documentElement?.clientHeight || 0;
17719
- const insetX = Math.min(12, rect.width / 4);
17720
- const insetY = Math.min(12, rect.height / 4);
17721
- const raw = [
17722
- [rect.left + rect.width / 2, rect.top + rect.height / 2],
17723
- [rect.left + insetX, rect.top + insetY],
17724
- [rect.right - insetX, rect.top + insetY],
17725
- [rect.left + insetX, rect.bottom - insetY],
17726
- [rect.right - insetX, rect.bottom - insetY],
17727
- ];
17728
- return raw.map(([x, y]) => ({
17729
- x: Math.min(Math.max(1, x), Math.max(1, width - 1)),
17730
- y: Math.min(Math.max(1, y), Math.max(1, height - 1)),
17731
- }));
17732
- }
17733
-
17734
- const el = document.querySelector(${JSON.stringify(selector)});
17735
- if (!el) return { error: "Element not found" };
17736
-
17737
- if (el instanceof HTMLElement) {
17738
- el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
17739
- }
17740
-
17741
- await new Promise((resolve) => {
17742
- let settled = false;
17743
- const finish = () => {
17744
- if (settled) return;
17745
- settled = true;
17746
- resolve(undefined);
17747
- };
17748
- if (
17749
- typeof requestAnimationFrame === "function" &&
17750
- document.visibilityState === "visible"
17751
- ) {
17752
- requestAnimationFrame(() => finish());
17753
- }
17754
- setTimeout(finish, 32);
17755
- });
17756
-
17757
- const rect = el.getBoundingClientRect();
17758
- if (rect.width <= 0 || rect.height <= 0) {
17759
- return { error: "Element is not visible. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it (scroll or scroll_to_element) then call read_page to refresh visible elements before clicking again." };
17760
- }
17761
-
17762
- const points = samplePoints(rect);
17763
- const hit = points.find((point) => matchesTarget(document.elementFromPoint(point.x, point.y), el));
17764
- const chosen = hit || points[0];
17765
- const top = document.elementFromPoint(chosen.x, chosen.y);
17766
-
17767
- return {
17768
- x: Math.round(chosen.x),
17769
- y: Math.round(chosen.y),
17770
- obstructed: !matchesTarget(top, el),
17771
- hiddenWindow: document.visibilityState !== "visible",
17772
- };
17773
- })()
17774
- `);
17775
- if (!target || typeof target !== "object") {
17776
- return "Error: Could not resolve click target";
17777
- }
17778
- if ("error" in target && typeof target.error === "string") {
17779
- return `Error: ${target.error}`;
17780
- }
17781
- const x = typeof target.x === "number" ? target.x : null;
17782
- const y = typeof target.y === "number" ? target.y : null;
17783
- const hiddenWindow = target.hiddenWindow === true;
17784
- if (x == null || y == null) {
17785
- return "Error: Could not resolve click coordinates";
17786
- }
17787
- if (hiddenWindow) {
17788
- const activationResult = await activateElement(wc, selector);
17789
- if (activationResult.startsWith("Error:")) {
17790
- return activationResult;
17791
- }
17792
- await sleep(80);
17793
- return "Clicked via DOM activation";
17794
- }
17795
- wc.sendInputEvent({ type: "mouseMove", x, y });
17796
- await sleep(16);
17797
- wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
17798
- await sleep(24);
17799
- wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
17800
- await sleep(80);
17801
- return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
17802
- }
17803
- async function activateElement(wc, selector) {
17804
- const activated = await wc.executeJavaScript(`
17805
- (function() {
17806
- const el = document.querySelector(${JSON.stringify(selector)});
17807
- if (!el) return { error: "Element not found" };
17808
- if (el instanceof HTMLElement) {
17809
- el.focus({ preventScroll: true });
17810
- }
17811
- if (typeof el.click === "function") {
17812
- el.click();
17813
- return { ok: true };
17814
- }
17815
- return { error: "Element is not clickable" };
17816
- })()
17817
- `);
17818
- if (!activated || typeof activated !== "object") {
17819
- return "Error: Could not activate element";
17820
- }
17821
- if ("error" in activated && typeof activated.error === "string") {
17822
- return `Error: ${activated.error}`;
17823
- }
17824
- return "Activated element via DOM click";
17825
- }
17826
- async function describeElementForClick(wc, selector) {
17827
- const result = await wc.executeJavaScript(`
17828
- (function() {
17829
- const el = document.querySelector(${JSON.stringify(selector)});
17830
- if (!el) return { error: "Element not found" };
17831
- const anchor = el instanceof HTMLAnchorElement ? el : el.closest("a[href]");
17832
- const text = (el.textContent || el.tagName || "Element").trim().slice(0, 100);
17833
- return {
17834
- text: text || "Element",
17835
- href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
17836
- };
17837
- })()
17838
- `);
17839
- if (!result || typeof result !== "object") {
17840
- return { error: "Element not found" };
17841
- }
17842
- if ("error" in result && typeof result.error === "string") {
17843
- return { error: result.error };
17844
- }
17845
- return {
17846
- text: "text" in result && typeof result.text === "string" ? result.text : "Element",
17847
- href: "href" in result && typeof result.href === "string" ? result.href : void 0
17848
- };
17849
- }
17850
- async function clickResolvedSelector(wc, selector) {
17851
- if (selector.startsWith("__vessel_idx:")) {
17852
- const idx = Number(selector.slice("__vessel_idx:".length));
17853
- const beforeUrl2 = wc.getURL();
17854
- const idxLabel = await wc.executeJavaScript(
17855
- `window.__vessel?.getElementText?.(${idx}) || ""`
17856
- );
17857
- if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
17858
- return `Blocked: "${idxLabel}" 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).`;
17859
- }
17860
- const result = await wc.executeJavaScript(
17861
- `window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
17862
- );
17863
- if (typeof result === "string" && result.startsWith("Error")) return result;
17864
- if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
17865
- recordCartClick(beforeUrl2, idxLabel);
17866
- }
17867
- await waitForPotentialNavigation$1(wc, beforeUrl2);
17868
- const afterUrl2 = wc.getURL();
17869
- if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
17870
- let overlayHint2 = await detectPostClickOverlay(wc);
17871
- if (!overlayHint2 && typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
17872
- await sleep(1200);
17873
- overlayHint2 = await detectPostClickOverlay(wc);
17874
- }
17875
- if (!overlayHint2) {
17876
- const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
17877
- if (hrefMatch) {
17878
- try {
17879
- await loadPermittedUrl(wc, hrefMatch[1]);
17880
- await waitForLoad(wc, 8e3);
17881
- const hrefUrl = wc.getURL();
17882
- if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
17883
- } catch {
17884
- }
17885
- }
17886
- return result;
17887
- }
17888
- const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
17889
- const actionsSuffix = dialogActions ? `
17890
- ${dialogActions}
17891
- Click one of these dialog actions. Do NOT click any other element.` : "";
17892
- return `${result}
17893
- ${overlayHint2}${actionsSuffix}`;
17894
- }
17895
- if (selector.includes(" >>> ")) {
17896
- const beforeUrl2 = wc.getURL();
17897
- const shadowLabel = await wc.executeJavaScript(`
17898
- (function() {
17899
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
17900
- return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
17901
- })()
17902
- `);
17903
- if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
17904
- return `Blocked: "${shadowLabel}" 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).`;
17905
- }
17906
- const result = await wc.executeJavaScript(`
17907
- (function() {
17908
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
17909
- if (!el || !document.contains(el)) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
17910
- if (el instanceof HTMLElement) { el.focus(); el.click(); }
17911
- var anchor = el instanceof HTMLAnchorElement ? el : el.closest('a[href]');
17912
- var href = anchor instanceof HTMLAnchorElement ? anchor.href : null;
17913
- return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase()) + (href ? "\\nhref: " + href : "");
17914
- })()
17915
- `);
17916
- if (typeof result === "string" && result.startsWith("Error")) return result;
17917
- if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
17918
- recordCartClick(beforeUrl2, shadowLabel);
17919
- }
17920
- await waitForPotentialNavigation$1(wc, beforeUrl2);
17921
- const afterUrl2 = wc.getURL();
17922
- if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
17923
- let overlayHint2 = await detectPostClickOverlay(wc);
17924
- if (!overlayHint2 && typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
17925
- await sleep(1200);
17926
- overlayHint2 = await detectPostClickOverlay(wc);
17927
- }
17928
- if (!overlayHint2) {
17929
- const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
17930
- if (hrefMatch) {
17931
- try {
17932
- await loadPermittedUrl(wc, hrefMatch[1]);
17933
- await waitForLoad(wc, 8e3);
17934
- const hrefUrl = wc.getURL();
17935
- if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
17936
- } catch {
17937
- }
17938
- }
17939
- return result;
17940
- }
17941
- const dialogActions2 = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
17942
- const actionsSuffix2 = dialogActions2 ? `
17943
- ${dialogActions2}
17944
- Click one of these dialog actions. Do NOT click any other element.` : "";
17945
- return `${result}
17946
- ${overlayHint2}${actionsSuffix2}`;
17947
- }
17948
- const beforeUrl = wc.getURL();
17949
- const elInfo = await describeElementForClick(wc, selector);
17950
- if ("error" in elInfo) return `Error: ${elInfo.error}`;
17951
- const cartMatch = isAddToCartText(elInfo.text);
17952
- if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
17953
- 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).`;
17954
- }
17955
- if (!cartMatch) {
17956
- const dialogActions = await getCartDialogActions(wc);
17957
- if (dialogActions) {
17958
- return `Blocked: a cart confirmation dialog is open. Do not click background elements.
17959
- ${dialogActions}
17960
- Click one of these dialog actions instead.`;
17961
- }
17962
- }
17963
- if (elInfo.href) {
17964
- const validation = await validateLinkDestination(elInfo.href);
17965
- if (validation.status === "dead") {
17966
- return formatDeadLinkMessage(elInfo.text, validation);
17967
- }
17968
- }
17969
- if (cartMatch) {
17970
- recordCartClick(beforeUrl, elInfo.text);
17971
- }
17972
- const clickText = `Clicked: ${elInfo.text}`;
17973
- const clickResult = await clickElement(wc, selector);
17974
- if (clickResult.startsWith("Error:")) return clickResult;
17975
- await waitForPotentialNavigation$1(wc, beforeUrl);
17976
- const afterUrl = wc.getURL();
17977
- if (afterUrl !== beforeUrl) {
17978
- return `${clickText} -> ${afterUrl}`;
17979
- }
17980
- const overlayHint = await detectPostClickOverlay(wc);
17981
- if (overlayHint) {
17982
- const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
17983
- const actionsSuffix = dialogActions ? `
17984
- ${dialogActions}
17985
- Click one of these dialog actions. Do NOT click any other element.` : "";
17986
- return `${clickText} (${clickResult})
17987
- ${overlayHint}${actionsSuffix}`;
17988
- }
17989
- if (cartMatch) {
17990
- await sleep(1200);
17991
- const delayedOverlayHint = await detectPostClickOverlay(wc);
17992
- if (delayedOverlayHint) {
17993
- const dialogActions = await getCartDialogActions(wc);
17994
- const actionsSuffix = dialogActions ? `
17995
- ${dialogActions}
17996
- Click one of these dialog actions. Do NOT click any other element.` : "";
17997
- return `${clickText} (${clickResult})
17998
- ${delayedOverlayHint}${actionsSuffix}`;
17999
- }
18000
- return `${clickText} (${clickResult})`;
18001
- }
18002
- const activationResult = await activateElement(wc, selector);
18003
- if (!activationResult.startsWith("Error:")) {
18004
- await waitForPotentialNavigation$1(wc, beforeUrl);
18005
- const fallbackUrl = wc.getURL();
18006
- if (fallbackUrl !== beforeUrl) {
18007
- return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
18008
- }
18009
- }
18010
- const postActivationOverlayHint = await detectPostClickOverlay(wc);
18011
- if (postActivationOverlayHint) {
18012
- return `${clickText} (${clickResult})
18013
- ${postActivationOverlayHint}`;
18014
- }
18015
- return `${clickText} (${clickResult})`;
18016
- }
18017
- async function getCartDialogActions(wc) {
18018
- const result = await wc.executeJavaScript(`
18019
- (function() {
18020
- function isVisible(el) {
18021
- if (!(el instanceof HTMLElement)) return false;
18022
- const style = getComputedStyle(el);
18023
- if (style.display === "none" || style.visibility === "hidden") return false;
18024
- const rect = el.getBoundingClientRect();
18025
- return rect.width >= 20 && rect.height >= 10;
18026
- }
18027
-
18028
- function findDialogRoot() {
18029
- const selectors = [
18030
- '[data-test="basket-flyout"]',
18031
- '[role="dialog"]',
18032
- 'dialog[open]',
18033
- '[role="alertdialog"]',
18034
- '[aria-modal="true"]',
18035
- ];
18036
- for (const selector of selectors) {
18037
- const nodes = document.querySelectorAll(selector);
18038
- for (const node of nodes) {
18039
- if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
18040
- const text = (node.textContent || "").slice(0, 800).toLowerCase();
18041
- const cartSignals = [
18042
- "added to cart", "added to bag", "added to basket",
18043
- "item added", "your basket", "your cart", "your bag",
18044
- "view basket", "view cart", "continue shopping",
18045
- ];
18046
- if (cartSignals.some((signal) => text.includes(signal))) {
18047
- return node;
18048
- }
18049
- }
18050
- }
18051
- return null;
18052
- }
18053
-
18054
- const dialog = findDialogRoot();
18055
- if (!dialog) return { found: false, actions: [] };
18056
-
18057
- const actions = [];
18058
- dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
18059
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
18060
- const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
18061
- if (!label || label.length < 2) return;
18062
- const href = el.getAttribute("href") || "";
18063
- const selector = el.id ? "#" + el.id
18064
- : el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
18065
- : el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
18066
- : null;
18067
- if (selector) {
18068
- actions.push({ label: label, href: href, selector: selector });
18069
- }
18070
- });
18071
-
18072
- return {
18073
- found: true,
18074
- actions: actions.map((action) =>
18075
- '- "' + action.label + '"' +
18076
- (action.href ? ' -> ' + action.href : "") +
18077
- (action.selector ? ' (selector: ' + action.selector + ')' : "")
18078
- ),
18079
- };
18080
- })()
18081
- `);
18082
- if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
18083
- return null;
18084
- }
18085
- if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
18086
- return null;
18087
- }
18088
- return `Available dialog actions:
18089
- ${result.actions.join("\n")}`;
18090
- }
18091
- async function detectPostClickOverlay(wc) {
18092
- const result = await wc.executeJavaScript(`
18093
- (function() {
18094
- var vw = window.innerWidth || document.documentElement.clientWidth;
18095
- var vh = window.innerHeight || document.documentElement.clientHeight;
18096
- var vpArea = Math.max(1, vw * vh);
18097
-
18098
- function isVisible(el) {
18099
- if (!(el instanceof HTMLElement)) return false;
18100
- var style = getComputedStyle(el);
18101
- if (style.display === "none" || style.visibility === "hidden") return false;
18102
- return el.getBoundingClientRect().width > 0;
18103
- }
18104
-
18105
- function hasFixedAncestor(el) {
18106
- var current = el.parentElement;
18107
- while (current && current !== document.body) {
18108
- var position = getComputedStyle(current).position;
18109
- if (position === "fixed" || position === "sticky") return true;
18110
- current = current.parentElement;
18111
- }
18112
- return false;
18113
- }
18114
-
18115
- function effectiveZ(el) {
18116
- var current = el;
18117
- while (current && current !== document.body) {
18118
- var z = parseInt(getComputedStyle(current).zIndex, 10);
18119
- if (z > 0) return z;
18120
- current = current.parentElement;
18121
- }
18122
- return 0;
18123
- }
18124
-
18125
- function touchesViewportEdge(rect) {
18126
- return rect.left <= 24 || rect.top <= 24 ||
18127
- rect.right >= vw - 24 || rect.bottom >= vh - 24;
18128
- }
18129
-
18130
- var cartPhrases = [
18131
- "added to cart", "added to bag", "added to basket",
18132
- "added to your cart", "added to your bag", "added to your basket",
18133
- ];
18134
- var cartActions = [
18135
- "view cart", "go to cart", "view basket", "go to basket",
18136
- "continue shopping", "keep shopping", "checkout",
18137
- ];
18138
-
18139
- var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
18140
- var candidates = document.querySelectorAll(selectors);
18141
- var hit = null;
18142
- for (var i = 0; i < candidates.length; i++) {
18143
- if (isVisible(candidates[i])) {
18144
- hit = candidates[i];
18145
- break;
18146
- }
18147
- }
18148
-
18149
- if (!hit) {
18150
- var elements = document.querySelectorAll("*");
18151
- for (var j = 0; j < elements.length; j++) {
18152
- var el = elements[j];
18153
- if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
18154
- var style = getComputedStyle(el);
18155
- var position = style.position;
18156
- var isFixed = position === "fixed" || position === "sticky";
18157
- var isAbsolute = position === "absolute";
18158
- if (!isFixed && !isAbsolute) continue;
18159
- if (isAbsolute && !hasFixedAncestor(el)) continue;
18160
- if (effectiveZ(el) < 5) continue;
18161
- var rect = el.getBoundingClientRect();
18162
- var areaRatio = (rect.width * rect.height) / vpArea;
18163
- if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
18164
- hit = el;
18165
- break;
18166
- }
18167
- }
18168
- }
18169
-
18170
- if (!hit) return { found: false, label: "", cartLike: false };
18171
- var text = (hit.textContent || "").slice(0, 800).toLowerCase();
18172
- var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
18173
- return text.indexOf(signal) !== -1;
18174
- });
18175
- var heading = hit.querySelector("h1,h2,h3,h4");
18176
- var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
18177
- return { found: true, label: label, cartLike: cartLike };
18178
- })()
18179
- `);
18180
- if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
18181
- return null;
18182
- }
18183
- const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
18184
- if ("cartLike" in result && result.cartLike) {
18185
- return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
18186
- }
18187
- return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
18188
- }
18189
- async function dismissPopup(wc) {
18190
- const before = await extractContent(wc);
18191
- const initialBlocking = before.overlays.filter(
18192
- (overlay) => overlay.blocksInteraction
18193
- ).length;
18194
- if (initialBlocking > 0) {
18195
- const overlayText = before.overlays.map(
18196
- (o) => [o.label, o.text].filter(Boolean).join(" ")
18197
- ).join(" ").toLowerCase();
18198
- const cartSignals = [
18199
- "added to cart",
18200
- "added to bag",
18201
- "added to basket",
18202
- "item added",
18203
- "items in your basket",
18204
- "items in your cart",
18205
- "items in your bag",
18206
- "your basket",
18207
- "your cart",
18208
- "your bag",
18209
- "view basket",
18210
- "view cart",
18211
- "continue shopping"
18212
- ];
18213
- if (cartSignals.some((s) => overlayText.includes(s))) {
18214
- const continueResult = await wc.executeJavaScript(`
18215
- (function() {
18216
- var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
18217
- if (!dialog) return "Error: dialog not found";
18218
- var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
18219
- var continueBtn = null;
18220
- var viewCartBtn = null;
18221
- for (var i = 0; i < buttons.length; i++) {
18222
- var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
18223
- if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
18224
- if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
18225
- }
18226
- var target = continueBtn || viewCartBtn;
18227
- if (!target) return "Error: no dialog action found";
18228
- var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
18229
- if (target.tagName === 'A' && target.href) {
18230
- window.location.href = target.href;
18231
- return "Clicked: " + actionLabel + " -> " + target.href;
18232
- }
18233
- target.click();
18234
- return "Clicked: " + actionLabel;
18235
- })()
18236
- `);
18237
- if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
18238
- return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
18239
- }
18240
- return "Cannot dismiss: this is a cart confirmation dialog. Item is in your cart. Use read_page to see dialog actions (e.g. View Basket, Continue Shopping) and click one of them instead.";
18241
- }
18242
- }
18243
- const initialDormant = before.dormantOverlays.length;
18244
- const candidates = await wc.executeJavaScript(`
18245
- (function() {
18246
- function text(value) {
18247
- const trimmed = value == null ? "" : String(value).trim();
18248
- return trimmed || "";
18249
- }
18250
-
18251
- ${selectorHelpersJS(["data-testid", "data-test", "aria-label", "name", "title"])}
18252
-
18253
- function isVisible(el) {
18254
- if (!(el instanceof HTMLElement)) return true;
18255
- const style = window.getComputedStyle(el);
18256
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
18257
- return false;
18258
- }
18259
- if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
18260
- return false;
18261
- }
18262
- const rect = el.getBoundingClientRect();
18263
- return rect.width > 0 && rect.height > 0;
18264
- }
18265
-
18266
- function overlayRoots() {
18267
- const nodes = [];
18268
- document.querySelectorAll("dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']").forEach((el) => {
18269
- if (isVisible(el)) nodes.push(el);
18270
- });
18271
- // Detect known consent manager containers
18272
- document.querySelectorAll("#onetrust-consent-sdk, #onetrust-banner-sdk, [id*='onetrust'], [class*='onetrust'], #CybotCookiebotDialog, #truste-consent-track, [id*='cookie-banner'], [id*='consent-banner'], [class*='cookie-consent'], [class*='consent-banner'], [id*='gdpr'], [class*='gdpr']").forEach((el) => {
18273
- if (el instanceof HTMLElement && isVisible(el)) nodes.push(el);
18274
- });
18275
- document.querySelectorAll("body *").forEach((el) => {
18276
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
18277
- const style = window.getComputedStyle(el);
18278
- const rect = el.getBoundingClientRect();
18279
- const zIndex = Number.parseInt(style.zIndex, 10);
18280
- const coversCenter =
18281
- rect.left <= (window.innerWidth || 0) / 2 &&
18282
- rect.right >= (window.innerWidth || 0) / 2 &&
18283
- rect.top <= (window.innerHeight || 0) / 2 &&
18284
- rect.bottom >= (window.innerHeight || 0) / 2;
18285
- if (
18286
- (style.position === "fixed" || style.position === "sticky") &&
18287
- Number.isFinite(zIndex) &&
18288
- zIndex >= 10 &&
18289
- coversCenter
18290
- ) {
18291
- nodes.push(el);
18292
- }
18293
- });
18294
- return Array.from(new Set(nodes));
18295
- }
18296
-
18297
- function scoreCandidate(el, rooted) {
18298
- const label = text(
18299
- el.getAttribute("aria-label") ||
18300
- el.getAttribute("title") ||
18301
- el.textContent ||
18302
- el.getAttribute("value"),
18303
- ).toLowerCase();
18304
- const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
18305
- const idText = text(el.id).toLowerCase();
18306
- const combined = classText + " " + idText;
18307
- let score = rooted ? 30 : 0;
18308
- if (/^x$|^×$/.test(label)) score += 120;
18309
- if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
18310
- if (/close|dismiss|modal-close|overlay-close/.test(combined)) score += 90;
18311
- if (/onetrust-close|onetrust-reject|cookie.*close|consent.*close|cookie.*reject|consent.*reject/.test(combined)) score += 110;
18312
- if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
18313
- if (el.getAttribute("aria-label")) score += 20;
18314
- if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
18315
- const rect = el.getBoundingClientRect();
18316
- if (rect.top < 120) score += 10;
18317
- if (rect.right > (window.innerWidth || 0) - 120) score += 15;
18318
- return score;
18319
- }
18320
-
18321
- const selector = "button, [role='button'], a[href], input[type='button'], input[type='submit'], [aria-label], [title]";
18322
- const results = [];
18323
- const roots = overlayRoots();
18324
-
18325
- function collect(container, rooted) {
18326
- container.querySelectorAll(selector).forEach((el) => {
18327
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
18328
- const candidateSelector = selectorFor(el);
18329
- if (!candidateSelector) return;
18330
- var label = text(
18331
- el.getAttribute("aria-label") ||
18332
- el.getAttribute("title") ||
18333
- el.textContent ||
18334
- el.getAttribute("value"),
18335
- );
18336
- if (!label) {
18337
- var idLower = (el.id || "").toLowerCase();
18338
- var classLower = (typeof el.className === "string" ? el.className : "").toLowerCase();
18339
- var combined = idLower + " " + classLower;
18340
- if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(combined)) {
18341
- label = idLower.includes("accept") ? "Accept cookies"
18342
- : idLower.includes("reject") ? "Reject cookies"
18343
- : idLower.includes("close") || classLower.includes("close") ? "Close"
18344
- : "Consent button";
18345
- } else {
18346
- return;
18347
- }
18348
- }
18349
- results.push({
18350
- selector: candidateSelector,
18351
- label: label.slice(0, 120),
18352
- score: scoreCandidate(el, rooted),
18353
- });
18354
- });
18355
- }
18356
-
18357
- roots.forEach((root) => collect(root, true));
18358
- if (results.length === 0) {
18359
- collect(document, false);
18360
- }
18361
-
18362
- const seen = new Set();
18363
- return results
18364
- .filter((candidate) => {
18365
- if (seen.has(candidate.selector)) return false;
18366
- seen.add(candidate.selector);
18367
- return candidate.score > 0;
18368
- })
18369
- .sort((a, b) => b.score - a.score)
18370
- .slice(0, 8);
18371
- })()
18372
- `);
18373
- if (Array.isArray(candidates)) {
18374
- for (const candidate of candidates) {
18375
- if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
18376
- continue;
18377
- }
18378
- const result = await clickElement(wc, candidate.selector);
18379
- if (result.startsWith("Error:")) continue;
18380
- await sleep(250);
18381
- const after = await extractContent(wc);
18382
- const blocking = after.overlays.filter(
18383
- (overlay) => overlay.blocksInteraction
18384
- ).length;
18385
- if (blocking < initialBlocking || initialBlocking > 0 && blocking === 0) {
18386
- const label = typeof candidate.label === "string" && candidate.label ? candidate.label : "popup control";
18387
- return `Dismissed popup using "${label}"`;
18388
- }
18389
- }
18390
- }
18391
- wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
18392
- await sleep(16);
18393
- wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
18394
- await sleep(200);
18395
- const afterEscape = await extractContent(wc);
18396
- const escapeBlocking = afterEscape.overlays.filter(
18397
- (overlay) => overlay.blocksInteraction
18398
- ).length;
18399
- if (escapeBlocking < initialBlocking || initialBlocking > 0 && escapeBlocking === 0) {
18400
- return "Dismissed popup with Escape";
18401
- }
18402
- return initialBlocking > 0 ? "Could not dismiss the blocking popup automatically" : initialDormant > 0 ? `No active blocking popup detected. Found ${initialDormant} dormant consent/modal surface(s) in the DOM, likely geo-gated or inactive in this session.` : "No blocking popup detected";
18403
- }
18404
- function isDangerousAction(name) {
18405
- return [
18406
- "navigate",
18407
- "click",
18408
- "type",
18409
- "select_option",
18410
- "submit_form",
18411
- "press_key",
18412
- "create_tab",
18413
- "switch_tab",
18414
- "close_tab",
18415
- "restore_checkpoint",
18416
- "login",
18417
- "fill_form",
18418
- "search",
18419
- "paginate"
18420
- ].includes(name);
18421
- }
18422
- function getTabByMatch(tabManager, match) {
18423
- const lowered = match.toLowerCase();
18424
- return tabManager.getAllStates().find(
18425
- (tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
18426
- ) || null;
18427
- }
18428
- async function getPostActionState(tabManager, name) {
18429
- const tab = tabManager.getActiveTab();
18430
- if (!tab) return "";
18431
- const wc = tab.view.webContents;
18432
- const navActions = [
18433
- "navigate",
18434
- "go_back",
18435
- "go_forward",
18436
- "click",
18437
- "submit_form",
18438
- "reload",
18439
- "press_key"
18440
- ];
18441
- const interactActions = [
18442
- "type",
18443
- "type_text",
18444
- "select_option",
18445
- "hover",
18446
- "focus"
18447
- ];
18448
- const tabActions = ["create_tab", "switch_tab", "close_tab"];
18449
- if (navActions.includes(name)) {
18450
- let warning = "";
18451
- try {
18452
- const page = await extractContent(wc);
18453
- const issue = getRecoverableAccessIssue(page);
18454
- if (issue) {
18455
- const blockedUrl = wc.getURL();
18456
- const canRecover = [
18457
- "navigate",
18458
- "open_bookmark",
18459
- "click",
18460
- "submit_form",
18461
- "reload",
18462
- "press_key"
18463
- ].includes(name) && tab.canGoBack();
18464
- if (canRecover && tab.goBack()) {
18465
- await waitForLoad(wc);
18466
- warning = `
18467
- [warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
18468
- } else {
18469
- warning = `
18470
- [warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
18471
- }
18472
- }
18473
- } catch {
18474
- }
18475
- return `${warning}
18476
- [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
18477
- }
18478
- if (interactActions.includes(name)) {
18479
- return `
18480
- [state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
18481
- }
18482
- if (tabActions.includes(name)) {
18483
- const activeId = tabManager.getActiveTabId();
18484
- const active = getActiveTabSummary(tabManager);
18485
- const count = tabManager.getAllStates().length;
18486
- return `
18487
- [state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
18488
- }
18489
- return "";
18490
- }
18491
- async function withAction(runtime2, tabManager, name, args, executor) {
18492
- try {
18493
- const result = await runtime2.runControlledAction({
18494
- source: "mcp",
18495
- name,
18496
- args,
18497
- tabId: tabManager.getActiveTabId(),
18498
- dangerous: isDangerousAction(name),
18499
- executor
18500
- });
18501
- const stateInfo = await getPostActionState(tabManager, name);
18502
- const flowCtx = runtime2.getFlowContext();
18503
- return asTextResponse(result + stateInfo + flowCtx);
18504
- } catch (error) {
18505
- return asTextResponse(
18506
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
18507
- );
18508
- }
18509
- }
18510
- async function setElementValue(wc, selector, value) {
18511
- if (selector.startsWith("__vessel_idx:")) {
18512
- const idx = Number(selector.slice("__vessel_idx:".length));
18513
- return wc.executeJavaScript(
18514
- `window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
18515
- );
18516
- }
18517
- if (selector.includes(" >>> ")) {
18518
- return wc.executeJavaScript(`
18519
- (function() {
18520
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
18521
- if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
18522
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
18523
- var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
18524
- var desc = Object.getOwnPropertyDescriptor(proto, "value");
18525
- if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
18526
- el.focus();
18527
- el.dispatchEvent(new Event("input", { bubbles: true }));
18528
- el.dispatchEvent(new Event("change", { bubbles: true }));
18529
- return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
18530
- })()
18531
- `);
18532
- }
18533
- return wc.executeJavaScript(`
18534
- (function() {
18535
- const el = document.querySelector(${JSON.stringify(selector)});
18536
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18537
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
18538
- return 'Error[not-input]: Element is not a text input';
18539
- }
18540
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18541
- return 'Error[disabled]: Input is disabled';
18542
- }
18543
- const prototype = el instanceof HTMLTextAreaElement
18544
- ? HTMLTextAreaElement.prototype
18545
- : HTMLInputElement.prototype;
18546
- const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
18547
- if (descriptor && descriptor.set) {
18548
- descriptor.set.call(el, ${JSON.stringify(value)});
18549
- } else {
18550
- el.value = ${JSON.stringify(value)};
18551
- }
18552
- el.focus();
18553
- el.dispatchEvent(new InputEvent('input', {
18554
- bubbles: true,
18555
- cancelable: true,
18556
- data: ${JSON.stringify(value)},
18557
- inputType: 'insertText',
18558
- }));
18559
- el.dispatchEvent(new Event('change', { bubbles: true }));
18560
- return 'Typed into: ' +
18561
- (el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
18562
- ' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
18563
- })()
18564
- `);
18565
- }
18566
- async function typeKeystroke(wc, selector, value) {
18567
- return wc.executeJavaScript(`
18568
- (async function() {
18569
- const el = document.querySelector(${JSON.stringify(selector)});
18570
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18571
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
18572
- return 'Error[not-input]: Element is not a text input';
18573
- }
18574
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18575
- return 'Error[disabled]: Input is disabled';
18576
- }
18577
- el.focus();
18578
- const prototype = el instanceof HTMLTextAreaElement
18579
- ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
18580
- const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
18581
- if (descriptor && descriptor.set) {
18582
- descriptor.set.call(el, '');
18583
- } else {
18584
- el.value = '';
18585
- }
18586
- el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: '', inputType: 'deleteContentBackward' }));
18587
- const chars = ${JSON.stringify(value)}.split('');
18588
- for (const ch of chars) {
18589
- el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true, cancelable: true }));
18590
- el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true, cancelable: true }));
18591
- if (descriptor && descriptor.set) {
18592
- descriptor.set.call(el, el.value + ch);
18593
- } else {
18594
- el.value += ch;
18595
- }
18596
- el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: ch, inputType: 'insertText' }));
18597
- el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true, cancelable: true }));
18598
- }
18599
- el.dispatchEvent(new Event('change', { bubbles: true }));
18600
- return 'Typed into: ' +
18601
- (el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
18602
- ' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
18603
- })()
18604
- `);
18605
- }
18606
- async function hoverElement(wc, selector) {
18607
- const pos = await wc.executeJavaScript(`
18608
- (function() {
18609
- const el = document.querySelector(${JSON.stringify(selector)});
18610
- if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
18611
- if (el instanceof HTMLElement) {
18612
- el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
18613
- }
18614
- const rect = el.getBoundingClientRect();
18615
- if (rect.width <= 0 || rect.height <= 0) return { error: 'Error[hidden]: Element has no visible area. It may be inside a collapsed, lazy-loaded, or virtual-scroll section. Scroll toward it then call read_page to refresh visible elements.' };
18616
- el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
18617
- el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
18618
- const label = (el.textContent || el.tagName || 'Element').trim().slice(0, 80);
18619
- return {
18620
- x: Math.round(rect.left + rect.width / 2),
18621
- y: Math.round(rect.top + rect.height / 2),
18622
- label: label,
18623
- };
18624
- })()
18625
- `);
18626
- if (!pos || typeof pos !== "object") return "Error: Could not hover element";
18627
- if ("error" in pos && typeof pos.error === "string") return pos.error;
18628
- const x = typeof pos.x === "number" ? pos.x : null;
18629
- const y = typeof pos.y === "number" ? pos.y : null;
18630
- if (x == null || y == null)
18631
- return "Error: Could not resolve hover coordinates";
18632
- wc.sendInputEvent({ type: "mouseMove", x, y });
18633
- const label = typeof pos.label === "string" ? pos.label : "element";
18634
- return `Hovered: ${label}`;
18635
- }
18636
- async function focusElement(wc, selector) {
18637
- return wc.executeJavaScript(`
18638
- (function() {
18639
- const el = document.querySelector(${JSON.stringify(selector)});
18640
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18641
- if (!(el instanceof HTMLElement)) return 'Error[not-interactive]: Element is not focusable';
18642
- if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
18643
- return 'Error[disabled]: Element is disabled';
18644
- }
18645
- el.focus({ preventScroll: false });
18646
- return 'Focused: ' + (el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
18647
- })()
18648
- `);
18649
- }
18650
- async function selectOption(wc, index, selector, label, value) {
18651
- const resolvedSelector = await resolveSelector(wc, index, selector);
18652
- if (!resolvedSelector)
18653
- return "Error: No select element index or selector provided";
18654
- return wc.executeJavaScript(`
18655
- (function() {
18656
- const el = document.querySelector(${JSON.stringify(resolvedSelector)});
18657
- if (!(el instanceof HTMLSelectElement)) {
18658
- return 'Element is not a select dropdown';
18659
- }
18660
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18661
- return 'Select is disabled';
18662
- }
18663
- const requestedLabel = ${JSON.stringify(label || "")}.trim().toLowerCase();
18664
- const requestedValue = ${JSON.stringify(value || "")}.trim();
18665
- const option = Array.from(el.options).find((item) => {
18666
- const optionLabel = (item.textContent || '').trim().toLowerCase();
18667
- return (requestedLabel && optionLabel === requestedLabel) ||
18668
- (requestedValue && item.value === requestedValue);
18669
- });
18670
- if (!option) return 'Option not found';
18671
- el.value = option.value;
18672
- el.dispatchEvent(new Event('input', { bubbles: true }));
18673
- el.dispatchEvent(new Event('change', { bubbles: true }));
18674
- return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
18675
- })()
18676
- `);
18677
- }
18678
- async function submitForm(wc, index, selector) {
18679
- const beforeUrl = wc.getURL();
18680
- let resolvedSelector = await resolveSelector(wc, index, selector);
18681
- if (!resolvedSelector) {
18682
- resolvedSelector = await wc.executeJavaScript(`
18683
- (function() {
18684
- var forms = document.querySelectorAll('form');
18685
- for (var i = 0; i < forms.length; i++) {
18686
- var f = forms[i];
18687
- var rect = f.getBoundingClientRect();
18688
- if (rect.width > 0 && rect.height > 0) return 'form';
18689
- }
18690
- return forms.length > 0 ? 'form' : null;
18691
- })()
18692
- `);
18693
- if (!resolvedSelector) return "Error: No form found on the page";
18694
- }
18695
- const formInfo = await wc.executeJavaScript(`
18696
- (function() {
18697
- const target = document.querySelector(${JSON.stringify(resolvedSelector)});
18698
- if (!target) return { error: 'Target not found' };
18699
- // Find the form: nested, or linked via form="id" attribute
18700
- var form = target instanceof HTMLFormElement ? target : target.closest('form');
18701
- if (!form) {
18702
- const formId = target.getAttribute('form');
18703
- if (formId) {
18704
- const linked = document.getElementById(formId);
18705
- if (linked instanceof HTMLFormElement) form = linked;
18706
- }
18707
- }
18708
- if (!form) return { error: 'No parent form found' };
18709
- function isSubmitControl(el) {
18710
- return (
18711
- (el instanceof HTMLButtonElement &&
18712
- ((el.getAttribute('type') || '').trim().toLowerCase() === '' ||
18713
- el.type === 'submit')) ||
18714
- (el instanceof HTMLInputElement &&
18715
- (el.type === 'submit' || el.type === 'image'))
18716
- );
18717
- }
18718
- const submitter = isSubmitControl(target)
18719
- ? target
18720
- : Array.from(document.querySelectorAll('button, input[type="submit"], input[type="image"]')).find(
18721
- (candidate) => isSubmitControl(candidate) && candidate.form === form,
18722
- );
18723
- if (
18724
- submitter instanceof HTMLElement &&
18725
- (submitter.hasAttribute('disabled') ||
18726
- submitter.getAttribute('aria-disabled') === 'true')
18727
- ) {
18728
- return { error: 'Submit control is disabled' };
18729
- }
18730
- // Collect form data and determine method
18731
- const submitterActionAttr =
18732
- (submitter instanceof HTMLButtonElement ||
18733
- submitter instanceof HTMLInputElement
18734
- ? submitter.getAttribute('formaction')?.trim()
18735
- : '') || '';
18736
- const action = submitterActionAttr
18737
- ? new URL(submitterActionAttr, document.baseURI).toString()
18738
- : form.action || window.location.href;
18739
- const submitterMethodAttr =
18740
- (submitter instanceof HTMLButtonElement ||
18741
- submitter instanceof HTMLInputElement
18742
- ? submitter.getAttribute('formmethod')?.trim()
18743
- : '') || '';
18744
- const method = (
18745
- submitterMethodAttr ||
18746
- form.getAttribute('method') ||
18747
- form.method ||
18748
- 'GET'
18749
- ).toUpperCase();
18750
- let fd;
18751
- try {
18752
- fd = submitter instanceof HTMLElement
18753
- ? new FormData(form, submitter)
18754
- : new FormData(form);
18755
- } catch {
18756
- fd = new FormData(form);
18757
- }
18758
- const params = new URLSearchParams();
18759
- for (const [k, v] of fd.entries()) {
18760
- if (typeof v === 'string') params.append(k, v);
18761
- }
18762
- // Use requestSubmit to fire JS submit handlers for all methods
18763
- if (typeof form.requestSubmit === 'function') {
18764
- try {
18765
- if (
18766
- submitter instanceof HTMLButtonElement ||
18767
- submitter instanceof HTMLInputElement
18768
- ) {
18769
- form.requestSubmit(submitter);
18770
- } else {
18771
- form.requestSubmit();
18772
- }
18773
- } catch {
18774
- form.requestSubmit();
18775
- }
18776
- return { submitted: true, method };
18777
- }
18778
- if (submitter instanceof HTMLElement && typeof submitter.click === 'function') {
18779
- submitter.click();
18780
- return { submitted: true, method };
18781
- }
18782
- // Last resort: form.submit() bypasses JS handlers but at least submits
18783
- if (method === 'GET') {
18784
- return { action, method, params: params.toString(), found: true };
18785
- }
18786
- form.submit();
18787
- return { submitted: true, method };
18788
- })()
18789
- `);
18790
- if (formInfo.error) return formInfo.error;
18791
- if (formInfo.found && formInfo.method === "GET") {
18792
- const url = new URL(formInfo.action);
18793
- if (formInfo.params) {
18794
- url.search = formInfo.params;
18795
- }
18796
- await loadPermittedUrl(wc, url.toString());
18797
- await waitForPotentialNavigation$1(wc, beforeUrl);
18798
- const afterUrl = wc.getURL();
18799
- return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
18800
- }
18801
- if (formInfo.submitted) {
18802
- await waitForPotentialNavigation$1(wc, beforeUrl);
18803
- const afterUrl = wc.getURL();
18804
- return afterUrl !== beforeUrl ? `Submitted form via ${formInfo.method} -> ${afterUrl}` : `Submitted form via ${formInfo.method}`;
17791
+ if (tabActions.includes(name)) {
17792
+ const activeId = tabManager.getActiveTabId();
17793
+ const active = getActiveTabSummary(tabManager);
17794
+ const count = tabManager.getAllStates().length;
17795
+ return `
17796
+ [state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
18805
17797
  }
18806
- return "Submitted form";
17798
+ return "";
18807
17799
  }
18808
- async function pressKey(wc, key, index, selector) {
18809
- const resolvedSelector = await resolveSelector(wc, index, selector);
18810
- return wc.executeJavaScript(`
18811
- (function() {
18812
- const key = ${JSON.stringify(key)};
18813
- const selector = ${JSON.stringify(resolvedSelector)};
18814
- const target = selector ? document.querySelector(selector) : document.activeElement;
18815
- if (!target || !(target instanceof HTMLElement)) {
18816
- return selector ? 'Target not found' : 'No focused element';
18817
- }
18818
- target.focus();
18819
- const eventInit = { key, bubbles: true, cancelable: true };
18820
- target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
18821
- target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
18822
- const tag = target.tagName;
18823
- const type = target instanceof HTMLInputElement ? target.type : '';
18824
- if (key === 'Enter') {
18825
- if (tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button'))) {
18826
- target.click();
18827
- } else if (tag === 'INPUT' || tag === 'TEXTAREA') {
18828
- const form = target.closest('form');
18829
- if (form) {
18830
- if (typeof form.requestSubmit === 'function') {
18831
- form.requestSubmit();
18832
- } else {
18833
- const submitBtn = form.querySelector('[type="submit"]');
18834
- if (submitBtn) submitBtn.click();
18835
- else form.submit();
18836
- }
18837
- }
18838
- }
18839
- }
18840
- target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
18841
- return 'Pressed key: ' + key;
18842
- })()
18843
- `);
17800
+ async function withAction(runtime2, tabManager, name, args, executor) {
17801
+ try {
17802
+ const result = await runtime2.runControlledAction({
17803
+ source: "mcp",
17804
+ name,
17805
+ args,
17806
+ tabId: tabManager.getActiveTabId(),
17807
+ dangerous: isDangerousMcpAction(name),
17808
+ executor
17809
+ });
17810
+ const stateInfo = await getPostActionState(tabManager, name);
17811
+ const flowCtx = runtime2.getFlowContext();
17812
+ return asTextResponse(result + stateInfo + flowCtx);
17813
+ } catch (error) {
17814
+ return asErrorTextResponse(getErrorMessage(error));
17815
+ }
18844
17816
  }
18845
- async function waitForCondition(wc, text, selector, timeoutMs) {
17817
+ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
18846
17818
  const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
18847
17819
  const expectedText = (text || "").trim();
18848
17820
  const expectedSelector = (selector || "").trim();
18849
- if (!expectedText && !expectedSelector) {
17821
+ const startedAt = Date.now();
17822
+ const result = await waitForConditionDirect(
17823
+ wc,
17824
+ expectedText,
17825
+ expectedSelector,
17826
+ effectiveTimeout
17827
+ );
17828
+ const elapsedMs = Date.now() - startedAt;
17829
+ if (result === "Error: wait_for requires text or selector") {
18850
17830
  return JSON.stringify({
18851
17831
  matched: false,
18852
17832
  error: "wait_for requires text or selector"
18853
17833
  });
18854
17834
  }
18855
- if (wc.isLoading()) {
18856
- await waitForLoad(wc, Math.min(effectiveTimeout, 5e3));
17835
+ if (result.startsWith("Error: Invalid selector ")) {
17836
+ return JSON.stringify({
17837
+ matched: false,
17838
+ error: result.slice("Error: ".length)
17839
+ });
18857
17840
  }
18858
- const startedAt = Date.now();
18859
- while (Date.now() - startedAt < effectiveTimeout) {
18860
- const result = await wc.executeJavaScript(`
17841
+ if (result.startsWith("Error: Page is still busy; wait_for timed out")) {
17842
+ return JSON.stringify({
17843
+ matched: false,
17844
+ error: result.slice("Error: ".length),
17845
+ elapsed_ms: elapsedMs,
17846
+ timeout_ms: effectiveTimeout
17847
+ });
17848
+ }
17849
+ if (expectedSelector && result === `Matched selector ${expectedSelector}`) {
17850
+ return JSON.stringify({
17851
+ matched: true,
17852
+ type: "selector",
17853
+ value: expectedSelector,
17854
+ elapsed_ms: elapsedMs
17855
+ });
17856
+ }
17857
+ const matchedTextPrefix = 'Matched text "';
17858
+ if (result.startsWith(matchedTextPrefix) && result.endsWith('"')) {
17859
+ return JSON.stringify({
17860
+ matched: true,
17861
+ type: "text",
17862
+ value: result.slice(matchedTextPrefix.length, -1),
17863
+ elapsed_ms: elapsedMs
17864
+ });
17865
+ }
17866
+ const timeoutPayload = {
17867
+ matched: false,
17868
+ type: expectedSelector ? "selector" : "text",
17869
+ value: expectedSelector || expectedText.slice(0, 80),
17870
+ elapsed_ms: elapsedMs,
17871
+ timeout_ms: effectiveTimeout
17872
+ };
17873
+ if (expectedSelector) {
17874
+ const diagnostic = await wc.executeJavaScript(`
18861
17875
  (function() {
18862
- var selector = ${JSON.stringify(expectedSelector)};
18863
- var text = ${JSON.stringify(expectedText)};
18864
- if (selector) {
18865
- try {
18866
- if (document.querySelector(selector)) return 'selector';
18867
- } catch (e) {
18868
- return 'invalid_selector:' + e.message;
18869
- }
17876
+ try {
17877
+ var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
17878
+ return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
17879
+ } catch (e) {
17880
+ return 'selector error: ' + e.message;
18870
17881
  }
18871
- if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
18872
- return '';
18873
17882
  })()
18874
- `);
18875
- const elapsedMs2 = Date.now() - startedAt;
18876
- if (result === "selector") {
18877
- return JSON.stringify({
18878
- matched: true,
18879
- type: "selector",
18880
- value: expectedSelector,
18881
- elapsed_ms: elapsedMs2
18882
- });
18883
- }
18884
- if (result === "text") {
18885
- return JSON.stringify({
18886
- matched: true,
18887
- type: "text",
18888
- value: expectedText.slice(0, 80),
18889
- elapsed_ms: elapsedMs2
18890
- });
18891
- }
18892
- if (typeof result === "string" && result.startsWith("invalid_selector:")) {
18893
- return JSON.stringify({
18894
- matched: false,
18895
- error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
18896
- });
17883
+ `).catch((err) => {
17884
+ logger$7.warn("Failed to gather wait_for timeout diagnostic:", err);
17885
+ return null;
17886
+ });
17887
+ if (typeof diagnostic === "string" && diagnostic.trim()) {
17888
+ timeoutPayload.diagnostic = diagnostic;
18897
17889
  }
18898
- await new Promise((resolve) => setTimeout(resolve, 150));
18899
17890
  }
18900
- const elapsedMs = Date.now() - startedAt;
18901
- const diagnostic = expectedSelector ? await wc.executeJavaScript(`
18902
- (function() {
18903
- try {
18904
- var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
18905
- return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
18906
- } catch (e) { return 'selector error: ' + e.message; }
18907
- })()
18908
- `) : null;
18909
- return JSON.stringify({
18910
- matched: false,
18911
- type: expectedSelector ? "selector" : "text",
18912
- value: expectedSelector ? expectedSelector : expectedText.slice(0, 80),
18913
- elapsed_ms: elapsedMs,
18914
- timeout_ms: effectiveTimeout,
18915
- ...diagnostic ? { diagnostic } : {}
18916
- });
17891
+ return JSON.stringify(timeoutPayload);
18917
17892
  }
18918
17893
  function registerTools(server, tabManager, runtime2) {
18919
17894
  server.registerPrompt(
@@ -18992,7 +17967,8 @@ function registerTools(server, tabManager, runtime2) {
18992
17967
  pageTitle = wc.getTitle();
18993
17968
  const page = await extractContent(wc);
18994
17969
  pageType = detectPageType(page);
18995
- } catch {
17970
+ } catch (err) {
17971
+ logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
18996
17972
  }
18997
17973
  }
18998
17974
  const scored = TOOL_DEFINITIONS.map((def) => {
@@ -19044,7 +18020,7 @@ function registerTools(server, tabManager, runtime2) {
19044
18020
  },
19045
18021
  async () => {
19046
18022
  const activeTab = getActiveTabSummary(tabManager);
19047
- if (!activeTab) return asTextResponse("Error: No active tab");
18023
+ if (!activeTab) return asNoActiveTabResponse();
19048
18024
  return asTextResponse(JSON.stringify(activeTab, null, 2));
19049
18025
  }
19050
18026
  );
@@ -19153,7 +18129,7 @@ ${buildScopedContext(pageContent, mode)}`;
19153
18129
  },
19154
18130
  async ({ mode }) => {
19155
18131
  const tab = tabManager.getActiveTab();
19156
- if (!tab) return asTextResponse("Error: No active tab");
18132
+ if (!tab) return asNoActiveTabResponse();
19157
18133
  try {
19158
18134
  const pageContent = await extractContent(tab.view.webContents);
19159
18135
  const effectiveMode = mode || "full";
@@ -19185,7 +18161,7 @@ ${buildScopedContext(pageContent, mode)}`;
19185
18161
  },
19186
18162
  async ({ mode }) => {
19187
18163
  const tab = tabManager.getActiveTab();
19188
- if (!tab) return asTextResponse("Error: No active tab");
18164
+ if (!tab) return asNoActiveTabResponse();
19189
18165
  try {
19190
18166
  const pageContent = await extractContent(tab.view.webContents);
19191
18167
  const effectiveMode = mode || "full";
@@ -19234,7 +18210,7 @@ ${buildScopedContext(pageContent, mode)}`;
19234
18210
  },
19235
18211
  async ({ url, postBody }) => {
19236
18212
  const tab = tabManager.getActiveTab();
19237
- if (!tab) return asTextResponse("Error: No active tab");
18213
+ if (!tab) return asNoActiveTabResponse();
19238
18214
  const preCheck = await validateLinkDestination(url);
19239
18215
  if (preCheck.status === "dead") {
19240
18216
  return asTextResponse(
@@ -19276,7 +18252,7 @@ ${buildScopedContext(pageContent, mode)}`;
19276
18252
  async ({ enabled, tabId, match, reload }) => {
19277
18253
  const activeTab = tabManager.getActiveTab();
19278
18254
  if (!activeTab && !tabId && !match) {
19279
- return asTextResponse("Error: No active tab");
18255
+ return asNoActiveTabResponse();
19280
18256
  }
19281
18257
  return withAction(
19282
18258
  runtime2,
@@ -19319,7 +18295,7 @@ ${buildScopedContext(pageContent, mode)}`;
19319
18295
  },
19320
18296
  async ({ type }) => {
19321
18297
  const tab = tabManager.getActiveTab();
19322
- if (!tab) return asTextResponse("Error: No active tab");
18298
+ if (!tab) return asNoActiveTabResponse();
19323
18299
  try {
19324
18300
  const pageContent = await extractContent(tab.view.webContents);
19325
18301
  const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
@@ -19367,7 +18343,7 @@ ${buildScopedContext(pageContent, mode)}`;
19367
18343
  },
19368
18344
  async () => {
19369
18345
  const tab = tabManager.getActiveTab();
19370
- if (!tab) return asTextResponse("Error: No active tab");
18346
+ if (!tab) return asNoActiveTabResponse();
19371
18347
  return withAction(runtime2, tabManager, "go_back", {}, async () => {
19372
18348
  if (!tab.canGoBack()) {
19373
18349
  return "No previous page in history";
@@ -19388,7 +18364,7 @@ ${buildScopedContext(pageContent, mode)}`;
19388
18364
  },
19389
18365
  async () => {
19390
18366
  const tab = tabManager.getActiveTab();
19391
- if (!tab) return asTextResponse("Error: No active tab");
18367
+ if (!tab) return asNoActiveTabResponse();
19392
18368
  return withAction(runtime2, tabManager, "go_forward", {}, async () => {
19393
18369
  if (!tab.canGoForward()) {
19394
18370
  return "No forward page in history";
@@ -19409,7 +18385,7 @@ ${buildScopedContext(pageContent, mode)}`;
19409
18385
  },
19410
18386
  async () => {
19411
18387
  const tab = tabManager.getActiveTab();
19412
- if (!tab) return asTextResponse("Error: No active tab");
18388
+ if (!tab) return asNoActiveTabResponse();
19413
18389
  return withAction(runtime2, tabManager, "reload", {}, async () => {
19414
18390
  tabManager.reloadTab(tabManager.getActiveTabId());
19415
18391
  await waitForLoad(tab.view.webContents);
@@ -19429,7 +18405,7 @@ ${buildScopedContext(pageContent, mode)}`;
19429
18405
  },
19430
18406
  async ({ index, selector }) => {
19431
18407
  const tab = tabManager.getActiveTab();
19432
- if (!tab) return asTextResponse("Error: No active tab");
18408
+ if (!tab) return asNoActiveTabResponse();
19433
18409
  return withAction(
19434
18410
  runtime2,
19435
18411
  tabManager,
@@ -19458,7 +18434,7 @@ ${buildScopedContext(pageContent, mode)}`;
19458
18434
  },
19459
18435
  async ({ index, selector }) => {
19460
18436
  const tab = tabManager.getActiveTab();
19461
- if (!tab) return asTextResponse("Error: No active tab");
18437
+ if (!tab) return asNoActiveTabResponse();
19462
18438
  return withAction(
19463
18439
  runtime2,
19464
18440
  tabManager,
@@ -19487,7 +18463,7 @@ ${buildScopedContext(pageContent, mode)}`;
19487
18463
  },
19488
18464
  async ({ index, selector }) => {
19489
18465
  const tab = tabManager.getActiveTab();
19490
- if (!tab) return asTextResponse("Error: No active tab");
18466
+ if (!tab) return asNoActiveTabResponse();
19491
18467
  return withAction(
19492
18468
  runtime2,
19493
18469
  tabManager,
@@ -19516,11 +18492,11 @@ ${buildScopedContext(pageContent, mode)}`;
19516
18492
  },
19517
18493
  async ({ index, selector }) => {
19518
18494
  const tab = tabManager.getActiveTab();
19519
- if (!tab) return asTextResponse("Error: No active tab");
18495
+ if (!tab) return asNoActiveTabResponse();
19520
18496
  const wc = tab.view.webContents;
19521
18497
  const resolvedSelector = await resolveSelector(wc, index, selector);
19522
18498
  if (!resolvedSelector) {
19523
- return asTextResponse("Error: No index or selector provided");
18499
+ return asErrorTextResponse("No index or selector provided");
19524
18500
  }
19525
18501
  const result = await wc.executeJavaScript(`
19526
18502
  (function() {
@@ -19570,7 +18546,7 @@ ${buildScopedContext(pageContent, mode)}`;
19570
18546
  );
19571
18547
  }
19572
18548
  if ("error" in result && typeof result.error === "string") {
19573
- return asTextResponse(`Error: ${result.error}`);
18549
+ return asErrorTextResponse(result.error);
19574
18550
  }
19575
18551
  const parts = [`<${result.tag}>`];
19576
18552
  if ("role" in result && typeof result.role === "string" && result.role.trim()) {
@@ -19601,7 +18577,7 @@ ${buildScopedContext(pageContent, mode)}`;
19601
18577
  },
19602
18578
  async ({ index, selector, text, mode }) => {
19603
18579
  const tab = tabManager.getActiveTab();
19604
- if (!tab) return asTextResponse("Error: No active tab");
18580
+ if (!tab) return asNoActiveTabResponse();
19605
18581
  return withAction(
19606
18582
  runtime2,
19607
18583
  tabManager,
@@ -19640,7 +18616,7 @@ ${buildScopedContext(pageContent, mode)}`;
19640
18616
  },
19641
18617
  async ({ index, selector, text, mode }) => {
19642
18618
  const tab = tabManager.getActiveTab();
19643
- if (!tab) return asTextResponse("Error: No active tab");
18619
+ if (!tab) return asNoActiveTabResponse();
19644
18620
  return withAction(
19645
18621
  runtime2,
19646
18622
  tabManager,
@@ -19677,13 +18653,13 @@ ${buildScopedContext(pageContent, mode)}`;
19677
18653
  },
19678
18654
  async ({ index, selector, label, value }) => {
19679
18655
  const tab = tabManager.getActiveTab();
19680
- if (!tab) return asTextResponse("Error: No active tab");
18656
+ if (!tab) return asNoActiveTabResponse();
19681
18657
  return withAction(
19682
18658
  runtime2,
19683
18659
  tabManager,
19684
18660
  "select_option",
19685
18661
  { index, selector, label, value },
19686
- async () => selectOption(tab.view.webContents, index, selector, label, value)
18662
+ async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
19687
18663
  );
19688
18664
  }
19689
18665
  );
@@ -19699,23 +18675,13 @@ ${buildScopedContext(pageContent, mode)}`;
19699
18675
  },
19700
18676
  async ({ index, selector }) => {
19701
18677
  const tab = tabManager.getActiveTab();
19702
- if (!tab) return asTextResponse("Error: No active tab");
18678
+ if (!tab) return asNoActiveTabResponse();
19703
18679
  return withAction(
19704
18680
  runtime2,
19705
18681
  tabManager,
19706
18682
  "submit_form",
19707
18683
  { index, selector },
19708
- async () => {
19709
- const wc = tab.view.webContents;
19710
- const beforeUrl = wc.getURL();
19711
- const result = await submitForm(wc, index, selector);
19712
- if (result.startsWith("Error") || result.startsWith("Target") || result.startsWith("No parent") || result.startsWith("Submit control")) {
19713
- return result;
19714
- }
19715
- await waitForPotentialNavigation$1(wc, beforeUrl);
19716
- const afterUrl = wc.getURL();
19717
- return afterUrl !== beforeUrl ? `${result} -> ${afterUrl}` : result;
19718
- }
18684
+ async () => submitFormDirect(tab.view.webContents, index, selector)
19719
18685
  );
19720
18686
  }
19721
18687
  );
@@ -19732,7 +18698,7 @@ ${buildScopedContext(pageContent, mode)}`;
19732
18698
  },
19733
18699
  async ({ key, index, selector }) => {
19734
18700
  const tab = tabManager.getActiveTab();
19735
- if (!tab) return asTextResponse("Error: No active tab");
18701
+ if (!tab) return asNoActiveTabResponse();
19736
18702
  return withAction(
19737
18703
  runtime2,
19738
18704
  tabManager,
@@ -19741,7 +18707,7 @@ ${buildScopedContext(pageContent, mode)}`;
19741
18707
  async () => {
19742
18708
  const wc = tab.view.webContents;
19743
18709
  const beforeUrl = wc.getURL();
19744
- const result = await pressKey(wc, key, index, selector);
18710
+ const result = await pressKeyDirect(wc, key, index, selector);
19745
18711
  if (key === "Enter") {
19746
18712
  await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
19747
18713
  const afterUrl = wc.getURL();
@@ -19768,7 +18734,7 @@ ${buildScopedContext(pageContent, mode)}`;
19768
18734
  },
19769
18735
  async ({ direction, amount }) => {
19770
18736
  const tab = tabManager.getActiveTab();
19771
- if (!tab) return asTextResponse("Error: No active tab");
18737
+ if (!tab) return asNoActiveTabResponse();
19772
18738
  return withAction(
19773
18739
  runtime2,
19774
18740
  tabManager,
@@ -19791,7 +18757,7 @@ ${buildScopedContext(pageContent, mode)}`;
19791
18757
  },
19792
18758
  async () => {
19793
18759
  const tab = tabManager.getActiveTab();
19794
- if (!tab) return asTextResponse("Error: No active tab");
18760
+ if (!tab) return asNoActiveTabResponse();
19795
18761
  return withAction(
19796
18762
  runtime2,
19797
18763
  tabManager,
@@ -19814,7 +18780,7 @@ ${buildScopedContext(pageContent, mode)}`;
19814
18780
  },
19815
18781
  async ({ strategy }) => {
19816
18782
  const tab = tabManager.getActiveTab();
19817
- if (!tab) return asTextResponse("Error: No active tab");
18783
+ if (!tab) return asNoActiveTabResponse();
19818
18784
  return withAction(
19819
18785
  runtime2,
19820
18786
  tabManager,
@@ -19840,13 +18806,13 @@ ${buildScopedContext(pageContent, mode)}`;
19840
18806
  },
19841
18807
  async ({ text, selector, timeoutMs }) => {
19842
18808
  const tab = tabManager.getActiveTab();
19843
- if (!tab) return asTextResponse("Error: No active tab");
18809
+ if (!tab) return asNoActiveTabResponse();
19844
18810
  return withAction(
19845
18811
  runtime2,
19846
18812
  tabManager,
19847
18813
  "wait_for",
19848
18814
  { text, selector, timeoutMs },
19849
- async () => waitForCondition(tab.view.webContents, text, selector, timeoutMs)
18815
+ async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
19850
18816
  );
19851
18817
  }
19852
18818
  );
@@ -20027,7 +18993,7 @@ ${buildScopedContext(pageContent, mode)}`;
20027
18993
  },
20028
18994
  async () => {
20029
18995
  const tab = tabManager.getActiveTab();
20030
- if (!tab) return asTextResponse("Error: No active tab");
18996
+ if (!tab) return asNoActiveTabResponse();
20031
18997
  try {
20032
18998
  const bounds = tab.view.getBounds();
20033
18999
  if (bounds.width <= 0 || bounds.height <= 0) {
@@ -20096,7 +19062,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20096
19062
  },
20097
19063
  async ({ index, selector, text, label, durationMs, persist, color }) => {
20098
19064
  const tab = tabManager.getActiveTab();
20099
- if (!tab) return asTextResponse("Error: No active tab");
19065
+ if (!tab) return asNoActiveTabResponse();
20100
19066
  const normalizedText = normalizeLooseString(text);
20101
19067
  return withAction(
20102
19068
  runtime2,
@@ -20146,7 +19112,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20146
19112
  },
20147
19113
  async () => {
20148
19114
  const tab = tabManager.getActiveTab();
20149
- if (!tab) return asTextResponse("Error: No active tab");
19115
+ if (!tab) return asNoActiveTabResponse();
20150
19116
  return withAction(
20151
19117
  runtime2,
20152
19118
  tabManager,
@@ -20285,8 +19251,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20285
19251
  h.label,
20286
19252
  void 0,
20287
19253
  h.color
20288
- ).catch(() => {
20289
- });
19254
+ ).catch(
19255
+ (err) => logger$7.warn("Failed to restore highlight after removal:", err)
19256
+ );
20290
19257
  }
20291
19258
  }
20292
19259
  return asTextResponse(`Removed highlight ${id}`);
@@ -20426,7 +19393,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20426
19393
  note,
20427
19394
  {
20428
19395
  onDuplicate: on_duplicate ?? "ask",
20429
- extra: getBookmarkMetadataFromToolArgs({
19396
+ extra: getBookmarkMetadataFromArgs({
20430
19397
  intent,
20431
19398
  expected_content,
20432
19399
  key_fields,
@@ -20570,7 +19537,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20570
19537
  folderId: target.folderId,
20571
19538
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
20572
19539
  note,
20573
- ...getBookmarkMetadataFromToolArgs(args)
19540
+ ...getBookmarkMetadataFromArgs(args)
20574
19541
  });
20575
19542
  if (!updated) {
20576
19543
  return `Bookmark ${existing.id} not found`;
@@ -20588,7 +19555,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20588
19555
  note,
20589
19556
  {
20590
19557
  onDuplicate: "update",
20591
- extra: getBookmarkMetadataFromToolArgs(args)
19558
+ extra: getBookmarkMetadataFromArgs(args)
20592
19559
  }
20593
19560
  );
20594
19561
  const bookmark = result.bookmark;
@@ -20971,7 +19938,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20971
19938
  },
20972
19939
  async ({ title, folder, summary, note, tags }) => {
20973
19940
  const tab = tabManager.getActiveTab();
20974
- if (!tab) return asTextResponse("Error: No active tab");
19941
+ if (!tab) return asNoActiveTabResponse();
20975
19942
  return withAction(
20976
19943
  runtime2,
20977
19944
  tabManager,
@@ -21115,7 +20082,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21115
20082
  let page;
21116
20083
  try {
21117
20084
  page = await extractContent(wc);
21118
- } catch {
20085
+ } catch (err) {
20086
+ logger$7.warn("Failed to extract page while generating suggestions:", err);
21119
20087
  return asTextResponse(
21120
20088
  "Could not read page. Try navigate to a working URL."
21121
20089
  );
@@ -21222,7 +20190,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21222
20190
  },
21223
20191
  async ({ fields, submit }) => {
21224
20192
  const tab = tabManager.getActiveTab();
21225
- if (!tab) return asTextResponse("Error: No active tab");
20193
+ if (!tab) return asNoActiveTabResponse();
21226
20194
  return withAction(
21227
20195
  runtime2,
21228
20196
  tabManager,
@@ -21236,7 +20204,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21236
20204
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
21237
20205
  if (firstSel) {
21238
20206
  const beforeUrl = wc.getURL();
21239
- const submitResult = await submitForm(wc, void 0, firstSel);
20207
+ const submitResult = await submitFormDirect(wc, void 0, firstSel);
21240
20208
  await waitForPotentialNavigation$1(wc, beforeUrl);
21241
20209
  const afterUrl = wc.getURL();
21242
20210
  results.push(
@@ -21279,7 +20247,7 @@ ${results.join("\n")}`;
21279
20247
  submit_selector
21280
20248
  }) => {
21281
20249
  const tab = tabManager.getActiveTab();
21282
- if (!tab) return asTextResponse("Error: No active tab");
20250
+ if (!tab) return asNoActiveTabResponse();
21283
20251
  return withAction(
21284
20252
  runtime2,
21285
20253
  tabManager,
@@ -21353,7 +20321,7 @@ ${steps.join("\n")}`;
21353
20321
  },
21354
20322
  async ({ query, selector }) => {
21355
20323
  const tab = tabManager.getActiveTab();
21356
- if (!tab) return asTextResponse("Error: No active tab");
20324
+ if (!tab) return asNoActiveTabResponse();
21357
20325
  const qLower = query.toLowerCase().trim();
21358
20326
  const buttonLabels = [
21359
20327
  "add to cart",
@@ -21438,7 +20406,7 @@ ${steps.join("\n")}`;
21438
20406
  },
21439
20407
  async ({ direction, selector }) => {
21440
20408
  const tab = tabManager.getActiveTab();
21441
- if (!tab) return asTextResponse("Error: No active tab");
20409
+ if (!tab) return asNoActiveTabResponse();
21442
20410
  return withAction(
21443
20411
  runtime2,
21444
20412
  tabManager,
@@ -21489,7 +20457,7 @@ ${steps.join("\n")}`;
21489
20457
  },
21490
20458
  async () => {
21491
20459
  const tab = tabManager.getActiveTab();
21492
- if (!tab) return asTextResponse("Error: No active tab");
20460
+ if (!tab) return asNoActiveTabResponse();
21493
20461
  return withAction(
21494
20462
  runtime2,
21495
20463
  tabManager,
@@ -21547,7 +20515,7 @@ ${steps.join("\n")}`;
21547
20515
  },
21548
20516
  async ({ index, selector: rawSelector }) => {
21549
20517
  const tab = tabManager.getActiveTab();
21550
- if (!tab) return asTextResponse("Error: No active tab");
20518
+ if (!tab) return asNoActiveTabResponse();
21551
20519
  return withAction(
21552
20520
  runtime2,
21553
20521
  tabManager,
@@ -21602,7 +20570,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21602
20570
  },
21603
20571
  async ({ index, selector: rawSelector, position }) => {
21604
20572
  const tab = tabManager.getActiveTab();
21605
- if (!tab) return asTextResponse("Error: No active tab");
20573
+ if (!tab) return asNoActiveTabResponse();
21606
20574
  return withAction(
21607
20575
  runtime2,
21608
20576
  tabManager,
@@ -21660,7 +20628,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21660
20628
  },
21661
20629
  async ({ timeoutMs }) => {
21662
20630
  const tab = tabManager.getActiveTab();
21663
- if (!tab) return asTextResponse("Error: No active tab");
20631
+ if (!tab) return asNoActiveTabResponse();
21664
20632
  return withAction(
21665
20633
  runtime2,
21666
20634
  tabManager,
@@ -21720,11 +20688,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
21720
20688
  let targetDomain = domain;
21721
20689
  if (!targetDomain) {
21722
20690
  const tab = tabManager.getActiveTab();
21723
- if (!tab) return asTextResponse("Error: No active tab and no domain specified");
20691
+ if (!tab) return asErrorTextResponse("No active tab and no domain specified");
21724
20692
  try {
21725
20693
  targetDomain = new URL(tab.state.url).hostname;
21726
- } catch {
21727
- return asTextResponse("Error: Could not parse active tab URL");
20694
+ } catch (err) {
20695
+ logger$7.warn("Failed to parse active tab URL for vault_status:", err);
20696
+ return asErrorTextResponse("Could not parse active tab URL");
21728
20697
  }
21729
20698
  }
21730
20699
  const matches = findEntriesForDomain(
@@ -21783,13 +20752,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21783
20752
  submit_index
21784
20753
  }) => {
21785
20754
  const tab = tabManager.getActiveTab();
21786
- if (!tab) return asTextResponse("Error: No active tab");
20755
+ if (!tab) return asNoActiveTabResponse();
21787
20756
  const wc = tab.view.webContents;
21788
20757
  let hostname;
21789
20758
  try {
21790
20759
  hostname = new URL(tab.state.url).hostname;
21791
- } catch {
21792
- return asTextResponse("Error: Could not parse active tab URL");
20760
+ } catch (err) {
20761
+ logger$7.warn("Failed to parse active tab URL for vault_login:", err);
20762
+ return asErrorTextResponse("Could not parse active tab URL");
21793
20763
  }
21794
20764
  const matches = findEntriesForDomain(`https://${hostname}`);
21795
20765
  if (matches.length === 0) {
@@ -21825,7 +20795,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21825
20795
  }
21826
20796
  const creds = getCredential(match.id);
21827
20797
  if (!creds) {
21828
- return asTextResponse("Error: Credential not found in vault");
20798
+ return asErrorTextResponse("Credential not found in vault");
21829
20799
  }
21830
20800
  const results = [];
21831
20801
  if (username_index != null) {
@@ -21876,13 +20846,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21876
20846
  },
21877
20847
  async ({ credential_label, code_index, submit_after, submit_index }) => {
21878
20848
  const tab = tabManager.getActiveTab();
21879
- if (!tab) return asTextResponse("Error: No active tab");
20849
+ if (!tab) return asNoActiveTabResponse();
21880
20850
  const wc = tab.view.webContents;
21881
20851
  let hostname;
21882
20852
  try {
21883
20853
  hostname = new URL(tab.state.url).hostname;
21884
- } catch {
21885
- return asTextResponse("Error: Could not parse active tab URL");
20854
+ } catch (err) {
20855
+ logger$7.warn("Failed to parse active tab URL for vault_totp:", err);
20856
+ return asErrorTextResponse("Could not parse active tab URL");
21886
20857
  }
21887
20858
  const matches = findEntriesForDomain(`https://${hostname}`);
21888
20859
  const match = credential_label ? matches.find(
@@ -22053,7 +21024,7 @@ function startMcpServer(tabManager, runtime2, port) {
22053
21024
  await mcpServer.connect(transport);
22054
21025
  await transport.handleRequest(req, res);
22055
21026
  } catch (error) {
22056
- console.error("[Vessel MCP] Error handling request:", error);
21027
+ logger$7.error("Error handling request:", error);
22057
21028
  if (!res.headersSent) {
22058
21029
  res.writeHead(500, { "Content-Type": "application/json" });
22059
21030
  res.end(
@@ -22072,7 +21043,7 @@ function startMcpServer(tabManager, runtime2, port) {
22072
21043
  };
22073
21044
  server.once("error", (error) => {
22074
21045
  const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
22075
- console.error("[Vessel MCP] Server error:", error);
21046
+ logger$7.error("Server error:", error);
22076
21047
  clearMcpAuthFile();
22077
21048
  setMcpHealth({
22078
21049
  configuredPort: port,
@@ -22084,14 +21055,12 @@ function startMcpServer(tabManager, runtime2, port) {
22084
21055
  if (httpServer === server) {
22085
21056
  httpServer = null;
22086
21057
  }
22087
- finish({
22088
- ok: false,
21058
+ finish(errorResult(message, {
22089
21059
  configuredPort: port,
22090
21060
  activePort: null,
22091
21061
  endpoint: null,
22092
- authToken: null,
22093
- error: message
22094
- });
21062
+ authToken: null
21063
+ }));
22095
21064
  });
22096
21065
  server.listen(port, "127.0.0.1", () => {
22097
21066
  httpServer = server;
@@ -22106,7 +21075,7 @@ function startMcpServer(tabManager, runtime2, port) {
22106
21075
  message: `MCP server listening on ${endpoint}.`
22107
21076
  });
22108
21077
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22109
- console.log(`[Vessel MCP] Server listening on ${endpoint} (auth enabled)`);
21078
+ logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
22110
21079
  }
22111
21080
  if (mcpAuthToken) {
22112
21081
  writeMcpAuthFile(endpoint, mcpAuthToken);
@@ -22145,7 +21114,7 @@ function stopMcpServer() {
22145
21114
  message: "MCP server is stopped."
22146
21115
  });
22147
21116
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22148
- console.log("[Vessel MCP] Server stopped");
21117
+ logger$7.info("Server stopped");
22149
21118
  }
22150
21119
  resolve();
22151
21120
  });
@@ -22166,6 +21135,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
22166
21135
  function isSafeAutomationKitId(id) {
22167
21136
  return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
22168
21137
  }
21138
+ const logger$6 = createLogger("KitRegistry");
22169
21139
  function getUserKitsDir() {
22170
21140
  return path$1.join(electron.app.getPath("userData"), "kits");
22171
21141
  }
@@ -22203,10 +21173,10 @@ function getInstalledKits() {
22203
21173
  if (isValidKit(parsed)) {
22204
21174
  kits.push(parsed);
22205
21175
  } else {
22206
- console.warn(`[kit-registry] Skipping invalid kit file: ${file}`);
21176
+ logger$6.warn(`Skipping invalid kit file: ${file}`);
22207
21177
  }
22208
- } catch {
22209
- console.warn(`[kit-registry] Failed to read kit file: ${file}`);
21178
+ } catch (err) {
21179
+ logger$6.warn(`Failed to read kit file: ${file}`, err);
22210
21180
  }
22211
21181
  }
22212
21182
  return kits;
@@ -22218,69 +21188,67 @@ async function installKitFromFile() {
22218
21188
  properties: ["openFile"]
22219
21189
  });
22220
21190
  if (canceled || filePaths.length === 0) {
22221
- return { ok: false, error: "canceled" };
21191
+ return errorResult("canceled");
22222
21192
  }
22223
21193
  let raw;
22224
21194
  try {
22225
21195
  raw = fs$1.readFileSync(filePaths[0], "utf-8");
22226
21196
  } catch {
22227
- return { ok: false, error: "Could not read the selected file." };
21197
+ return errorResult("Could not read the selected file.");
22228
21198
  }
22229
21199
  let parsed;
22230
21200
  try {
22231
21201
  parsed = JSON.parse(raw);
22232
21202
  } catch {
22233
- return { ok: false, error: "File is not valid JSON." };
21203
+ return errorResult("File is not valid JSON.");
22234
21204
  }
22235
21205
  if (!isValidKit(parsed)) {
22236
- return {
22237
- ok: false,
22238
- error: "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
22239
- };
21206
+ return errorResult(
21207
+ "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
21208
+ );
22240
21209
  }
22241
21210
  if (BUNDLED_KIT_IDS.has(parsed.id)) {
22242
- return {
22243
- ok: false,
22244
- error: `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
22245
- };
21211
+ return errorResult(
21212
+ `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
21213
+ );
22246
21214
  }
22247
21215
  ensureKitsDir();
22248
21216
  const dest = getKitFilePath(parsed.id);
22249
21217
  if (!dest) {
22250
- return { ok: false, error: "Kit id contains unsupported characters." };
21218
+ return errorResult("Kit id contains unsupported characters.");
22251
21219
  }
22252
21220
  try {
22253
21221
  fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
22254
21222
  } catch {
22255
- return { ok: false, error: "Failed to save the kit file." };
21223
+ return errorResult("Failed to save the kit file.");
22256
21224
  }
22257
- return { ok: true, kit: parsed };
21225
+ return okResult({ kit: parsed });
22258
21226
  }
22259
21227
  function uninstallKit(id, scheduledKitIds) {
22260
21228
  if (BUNDLED_KIT_IDS.has(id)) {
22261
- return { ok: false, error: "Built-in kits cannot be removed." };
21229
+ return errorResult("Built-in kits cannot be removed.");
22262
21230
  }
22263
21231
  if (scheduledKitIds?.has(id)) {
22264
- return {
22265
- ok: false,
22266
- error: "This kit has active scheduled jobs. Delete or reassign them first."
22267
- };
21232
+ return errorResult(
21233
+ "This kit has active scheduled jobs. Delete or reassign them first."
21234
+ );
22268
21235
  }
22269
21236
  ensureKitsDir();
22270
21237
  const target = getKitFilePath(id);
22271
21238
  if (!target) {
22272
- return { ok: false, error: "Kit id contains unsupported characters." };
21239
+ return errorResult("Kit id contains unsupported characters.");
22273
21240
  }
22274
21241
  if (!fs$1.existsSync(target)) {
22275
- return { ok: false, error: "Kit not found." };
21242
+ return errorResult("Kit not found.");
22276
21243
  }
22277
21244
  try {
22278
21245
  fs$1.unlinkSync(target);
22279
- return { ok: true };
21246
+ return okResult();
22280
21247
  } catch {
22281
- return { ok: false, error: "Failed to remove the kit file." };
21248
+ return errorResult("Failed to remove the kit file.");
22282
21249
  }
22283
21250
  }
21251
+ const logger$5 = createLogger("Scheduler");
22284
21252
  let jobs = [];
22285
21253
  let removeIdleListener = null;
22286
21254
  let broadcastFn = null;
@@ -22305,7 +21273,7 @@ function saveJobs() {
22305
21273
  try {
22306
21274
  fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
22307
21275
  } catch (err) {
22308
- console.warn("[scheduler] Failed to save jobs:", err);
21276
+ logger$5.warn("Failed to save jobs:", err);
22309
21277
  }
22310
21278
  }
22311
21279
  function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
@@ -22427,7 +21395,7 @@ async function fireJob(job, windowState, runtime2) {
22427
21395
  };
22428
21396
  startActivity();
22429
21397
  if (!settings2.chatProvider) {
22430
- console.warn(`[scheduler] Job "${job.kitName}" skipped — no chat provider configured`);
21398
+ logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
22431
21399
  appendActivity(
22432
21400
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
22433
21401
  );
@@ -22435,7 +21403,7 @@ async function fireJob(job, windowState, runtime2) {
22435
21403
  return;
22436
21404
  }
22437
21405
  if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
22438
- console.log(`[scheduler] Firing scheduled job: ${job.kitName} (${job.id})`);
21406
+ logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
22439
21407
  }
22440
21408
  try {
22441
21409
  const provider = createProvider(settings2.chatProvider);
@@ -22488,7 +21456,7 @@ function tick(windowState, runtime2) {
22488
21456
  saveJobs();
22489
21457
  broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
22490
21458
  void fireJob(job, windowState, runtime2).catch((err) => {
22491
- console.warn("[scheduler] Unexpected error firing job:", err);
21459
+ logger$5.warn("Unexpected error firing job:", err);
22492
21460
  }).finally(fireNext);
22493
21461
  };
22494
21462
  fireNext();
@@ -23046,6 +22014,7 @@ function registerWindowControlHandlers(mainWindow) {
23046
22014
  });
23047
22015
  }
23048
22016
  let activeChatProvider = null;
22017
+ const logger$4 = createLogger("IPC");
23049
22018
  const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
23050
22019
  function registerIpcHandlers(windowState, runtime2) {
23051
22020
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
@@ -23122,7 +22091,8 @@ function registerIpcHandlers(windowState, runtime2) {
23122
22091
  let parsed;
23123
22092
  try {
23124
22093
  parsed = new URL(rawUrl);
23125
- } catch {
22094
+ } catch (err) {
22095
+ logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
23126
22096
  return;
23127
22097
  }
23128
22098
  if (parsed.origin !== premiumApiOrigin) return;
@@ -23179,7 +22149,8 @@ function registerIpcHandlers(windowState, runtime2) {
23179
22149
  if (wc.isDestroyed()) return 0;
23180
22150
  try {
23181
22151
  return await getHighlightCount(wc) ?? 0;
23182
- } catch {
22152
+ } catch (err) {
22153
+ logger$4.warn("Failed to get active highlight count:", err);
23183
22154
  return 0;
23184
22155
  }
23185
22156
  };
@@ -23287,13 +22258,13 @@ function registerIpcHandlers(windowState, runtime2) {
23287
22258
  electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
23288
22259
  try {
23289
22260
  if (!config || typeof config !== "object" || !("id" in config)) {
23290
- return { ok: false, models: [], error: "Invalid provider configuration" };
22261
+ return errorResult("Invalid provider configuration", { models: [] });
23291
22262
  }
23292
22263
  return await fetchProviderModels(
23293
22264
  config
23294
22265
  );
23295
22266
  } catch (err) {
23296
- return { ok: false, models: [], error: err instanceof Error ? err.message : "Unknown error" };
22267
+ return errorResult(getErrorMessage(err), { models: [] });
23297
22268
  }
23298
22269
  });
23299
22270
  electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
@@ -23488,12 +22459,14 @@ function registerIpcHandlers(windowState, runtime2) {
23488
22459
  const wc = activeTab.view.webContents;
23489
22460
  const result = await captureSelectionHighlight(wc);
23490
22461
  if (result.success && result.text) {
23491
- await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
23492
- });
22462
+ await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
22463
+ (err) => logger$4.warn("Failed to highlight captured selection:", err)
22464
+ );
23493
22465
  await emitHighlightCount();
23494
22466
  }
23495
22467
  return result;
23496
- } catch {
22468
+ } catch (err) {
22469
+ logger$4.warn("Failed to capture highlight from active tab:", err);
23497
22470
  return { success: false, message: "Could not capture selection" };
23498
22471
  }
23499
22472
  });
@@ -23517,7 +22490,8 @@ function registerIpcHandlers(windowState, runtime2) {
23517
22490
  chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
23518
22491
  }
23519
22492
  });
23520
- } catch {
22493
+ } catch (err) {
22494
+ logger$4.warn("Failed to persist auto-highlight selection:", err);
23521
22495
  }
23522
22496
  });
23523
22497
  electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
@@ -23530,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
23530
22504
  if (wc.isDestroyed()) return false;
23531
22505
  try {
23532
22506
  return scrollToHighlight(wc, index);
23533
- } catch {
22507
+ } catch (err) {
22508
+ logger$4.warn("Failed to scroll to highlight:", err);
23534
22509
  return false;
23535
22510
  }
23536
22511
  });
@@ -23545,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
23545
22520
  await emitHighlightCount();
23546
22521
  }
23547
22522
  return removed;
23548
- } catch {
22523
+ } catch (err) {
22524
+ logger$4.warn("Failed to remove highlight at index:", err);
23549
22525
  return false;
23550
22526
  }
23551
22527
  });
@@ -23560,7 +22536,8 @@ function registerIpcHandlers(windowState, runtime2) {
23560
22536
  await emitHighlightCount();
23561
22537
  }
23562
22538
  return cleared;
23563
- } catch {
22539
+ } catch (err) {
22540
+ logger$4.warn("Failed to clear highlight elements:", err);
23564
22541
  return false;
23565
22542
  }
23566
22543
  });
@@ -23644,7 +22621,7 @@ function registerIpcHandlers(windowState, runtime2) {
23644
22621
  electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
23645
22622
  assertString(email, "email");
23646
22623
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23647
- return { ok: false, error: "Invalid email format" };
22624
+ return errorResult("Invalid email format");
23648
22625
  }
23649
22626
  trackPremiumFunnel("activation_attempted");
23650
22627
  const result = await requestActivationCode(email);
@@ -23660,11 +22637,9 @@ function registerIpcHandlers(windowState, runtime2) {
23660
22637
  assertString(code, "code");
23661
22638
  assertString(challengeToken, "challengeToken");
23662
22639
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23663
- return {
23664
- ok: false,
23665
- state: getPremiumState(),
23666
- error: "Invalid email format"
23667
- };
22640
+ return errorResult("Invalid email format", {
22641
+ state: getPremiumState()
22642
+ });
23668
22643
  }
23669
22644
  trackPremiumFunnel("activation_attempted");
23670
22645
  const result = await verifyActivationCode(email, code, challengeToken);
@@ -24099,6 +23074,7 @@ ${lines.join("\n")}
24099
23074
  }
24100
23075
  const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
24101
23076
  const PERSIST_DEBOUNCE_MS = 500;
23077
+ const logger$3 = createLogger("Runtime");
24102
23078
  function clone(value) {
24103
23079
  return JSON.parse(JSON.stringify(value));
24104
23080
  }
@@ -24488,9 +23464,7 @@ ${progress}
24488
23464
  JSON.stringify(persisted, null, 2),
24489
23465
  "utf-8"
24490
23466
  )
24491
- ).catch(
24492
- (err) => console.error("[Vessel] Failed to persist runtime state:", err)
24493
- );
23467
+ ).catch((err) => logger$3.error("Failed to persist runtime state:", err));
24494
23468
  }
24495
23469
  schedulePersist() {
24496
23470
  this.persistDirty = true;
@@ -24755,6 +23729,7 @@ function installDownloadHandler(chromeView) {
24755
23729
  });
24756
23730
  });
24757
23731
  }
23732
+ const logger$2 = createLogger("Shortcuts");
24758
23733
  function registerHighlightShortcut(mainWindow, tabManager) {
24759
23734
  const register = () => {
24760
23735
  electron.globalShortcut.unregister("CommandOrControl+H");
@@ -24764,7 +23739,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
24764
23739
  tabManager.captureHighlightFromActiveTab();
24765
23740
  });
24766
23741
  if (!success) {
24767
- console.warn("[Vessel] Failed to register Ctrl+H shortcut");
23742
+ logger$2.warn("Failed to register Ctrl+H shortcut");
24768
23743
  }
24769
23744
  };
24770
23745
  register();
@@ -24833,6 +23808,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
24833
23808
  });
24834
23809
  }
24835
23810
  }
23811
+ const logger$1 = createLogger("Splash");
24836
23812
  function findIconBase64() {
24837
23813
  const candidates = [
24838
23814
  path$1.join(process.resourcesPath, "vessel-icon.png"),
@@ -24995,7 +23971,7 @@ function createSplashWindow() {
24995
23971
  fs$1.writeFileSync(tmpPath, html, "utf-8");
24996
23972
  void splash.loadFile(tmpPath);
24997
23973
  } catch (err) {
24998
- console.warn("[splash] Failed to write temp HTML, using fallback:", err);
23974
+ logger$1.warn("Failed to write temp HTML, using fallback:", err);
24999
23975
  void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
25000
23976
  }
25001
23977
  return splash;
@@ -25005,6 +23981,7 @@ function closeSplash(splash, delayMs = 0) {
25005
23981
  if (!splash.isDestroyed()) splash.close();
25006
23982
  }, delayMs);
25007
23983
  }
23984
+ const logger = createLogger("Bootstrap");
25008
23985
  let runtime = null;
25009
23986
  function checkWritableUserData(userDataPath) {
25010
23987
  const issues = [];
@@ -25124,7 +24101,7 @@ async function bootstrap() {
25124
24101
  };
25125
24102
  let didInitializeChromeRenderer = false;
25126
24103
  const splashTimeout = setTimeout(() => {
25127
- console.warn("[bootstrap] Renderer did not finish loading before splash timeout");
24104
+ logger.warn("Renderer did not finish loading before splash timeout");
25128
24105
  revealMainWindow();
25129
24106
  }, 8e3);
25130
24107
  const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
@@ -25175,8 +24152,8 @@ async function bootstrap() {
25175
24152
  "did-fail-load",
25176
24153
  (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
25177
24154
  if (!isMainFrame) return;
25178
- console.error(
25179
- "[bootstrap] Chrome renderer failed to load:",
24155
+ logger.error(
24156
+ "Chrome renderer failed to load:",
25180
24157
  errorCode,
25181
24158
  errorDescription,
25182
24159
  validatedURL
@@ -25187,21 +24164,21 @@ async function bootstrap() {
25187
24164
  );
25188
24165
  loadRenderers(chromeView, sidebarView, devtoolsPanelView);
25189
24166
  startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
25190
- console.error("[bootstrap] MCP server failed to start:", err);
24167
+ logger.error("MCP server failed to start:", err);
25191
24168
  });
25192
24169
  }
25193
24170
  process.on("uncaughtException", (error) => {
25194
- console.error("[Vessel] Uncaught exception:", error.message, error.stack);
24171
+ logger.error("Uncaught exception:", error.message, error.stack);
25195
24172
  electron.app.quit();
25196
24173
  });
25197
24174
  process.on("unhandledRejection", (reason) => {
25198
- console.error(
25199
- "[Vessel] Unhandled rejection:",
24175
+ logger.error(
24176
+ "Unhandled rejection:",
25200
24177
  reason instanceof Error ? reason.message : reason
25201
24178
  );
25202
24179
  });
25203
24180
  electron.app.whenReady().then(bootstrap).catch((error) => {
25204
- console.error("[Vessel] Failed to bootstrap application:", error);
24181
+ logger.error("Failed to bootstrap application:", error);
25205
24182
  electron.app.quit();
25206
24183
  });
25207
24184
  electron.app.on("window-all-closed", () => {