@quanta-intellect/vessel-browser 0.1.63 → 0.1.65

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,7 @@ function inferPageSchema(page) {
4376
4419
  confidence
4377
4420
  };
4378
4421
  }
4422
+ const logger$e = createLogger("Extractor");
4379
4423
  const EMPTY_PAGE_CONTENT = {
4380
4424
  title: "",
4381
4425
  content: "",
@@ -5122,7 +5166,8 @@ async function executeScript(webContents, script) {
5122
5166
  timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
5123
5167
  })
5124
5168
  ]);
5125
- } catch {
5169
+ } catch (err) {
5170
+ logger$e.warn("Failed to execute page script:", err);
5126
5171
  return null;
5127
5172
  } finally {
5128
5173
  if (timer) {
@@ -5230,7 +5275,8 @@ async function estimateExtractionTimeout(webContents) {
5230
5275
  );
5231
5276
  return EXTRACT_TIMEOUT_BASE_MS + extra;
5232
5277
  }
5233
- } catch {
5278
+ } catch (err) {
5279
+ logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
5234
5280
  }
5235
5281
  return EXTRACT_TIMEOUT_BASE_MS;
5236
5282
  }
@@ -6019,6 +6065,9 @@ function isClickReadLoop(names) {
6019
6065
  }
6020
6066
  return clickReadPairs >= 2;
6021
6067
  }
6068
+ function isRecord(value) {
6069
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6070
+ }
6022
6071
  class AnthropicProvider {
6023
6072
  agentToolProfile = "default";
6024
6073
  client;
@@ -6115,10 +6164,14 @@ class AnthropicProvider {
6115
6164
  }
6116
6165
  } else if (event.type === "content_block_stop" && currentToolUse) {
6117
6166
  try {
6167
+ const input = JSON.parse(currentToolUse.inputJson || "{}");
6168
+ if (!isRecord(input)) {
6169
+ throw new Error("Tool input must be a JSON object");
6170
+ }
6118
6171
  toolUseBlocks.push({
6119
6172
  id: currentToolUse.id,
6120
6173
  name: currentToolUse.name,
6121
- input: JSON.parse(currentToolUse.inputJson || "{}")
6174
+ input
6122
6175
  });
6123
6176
  } catch {
6124
6177
  toolUseBlocks.push({
@@ -6165,7 +6218,7 @@ class AnthropicProvider {
6165
6218
  });
6166
6219
  continue;
6167
6220
  }
6168
- const argSummary = tb.input.url || tb.input.text || tb.input.direction || "";
6221
+ const argSummary = [tb.input.url, tb.input.text, tb.input.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
6169
6222
  onChunk(`
6170
6223
  <<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
6171
6224
  `);
@@ -6392,6 +6445,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
6392
6445
  const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
6393
6446
  const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
6394
6447
  const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
6448
+ const logger$d = createLogger("OpenAIProvider");
6395
6449
  function shouldDebugAgentLoop() {
6396
6450
  const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
6397
6451
  return value === "1" || value === "true";
@@ -6798,9 +6852,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
6798
6852
  function logAgentLoopDebug(payload) {
6799
6853
  if (!shouldDebugAgentLoop()) return;
6800
6854
  try {
6801
- console.log(`[Vessel agent-debug] ${JSON.stringify(payload)}`);
6855
+ logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
6802
6856
  } catch (err) {
6803
- console.warn("[Vessel agent-debug] Failed to serialize debug payload:", err);
6857
+ logger$d.warn("Failed to serialize debug payload:", err);
6804
6858
  }
6805
6859
  }
6806
6860
  function recoverTextEncodedToolCalls(text, availableToolNames) {
@@ -6820,7 +6874,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
6820
6874
  const argsJson = match[2] ?? "{}";
6821
6875
  let parsedArgs = {};
6822
6876
  try {
6823
- parsedArgs = JSON.parse(argsJson);
6877
+ const raw = JSON.parse(argsJson);
6878
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
6879
+ parsedArgs = raw;
6880
+ }
6824
6881
  } catch {
6825
6882
  continue;
6826
6883
  }
@@ -7217,7 +7274,7 @@ class OpenAICompatProvider {
7217
7274
  }
7218
7275
  continue;
7219
7276
  }
7220
- const argSummary = args.url || args.query || args.text || args.direction || "";
7277
+ const argSummary = [args.url, args.query, args.text, args.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
7221
7278
  onChunk(`
7222
7279
  <<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
7223
7280
  `);
@@ -7375,7 +7432,7 @@ async function fetchProviderModels(config) {
7375
7432
  if (normalized.id === "anthropic") {
7376
7433
  const client2 = new Anthropic({ apiKey: normalized.apiKey });
7377
7434
  const page2 = await client2.models.list();
7378
- return { ok: true, models: page2.data.map((model) => model.id) };
7435
+ return okResult({ models: page2.data.map((model) => model.id) });
7379
7436
  }
7380
7437
  const meta = PROVIDERS[normalized.id];
7381
7438
  const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
@@ -7387,9 +7444,10 @@ async function fetchProviderModels(config) {
7387
7444
  const models = page.data.map((model) => model.id);
7388
7445
  const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
7389
7446
  return {
7390
- ok: true,
7391
- models,
7392
- ...warning ? { warning } : {}
7447
+ ...okResult({
7448
+ models,
7449
+ ...warning ? { warning } : {}
7450
+ })
7393
7451
  };
7394
7452
  }
7395
7453
  function createProvider(config) {
@@ -7404,6 +7462,7 @@ function createProvider(config) {
7404
7462
  return new OpenAICompatProvider(normalized);
7405
7463
  }
7406
7464
  const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
7465
+ const logger$c = createLogger("DevTrace");
7407
7466
  let cachedFactory;
7408
7467
  function createNoopTraceSession() {
7409
7468
  return {
@@ -7436,7 +7495,7 @@ function loadLocalFactory() {
7436
7495
  return cachedFactory;
7437
7496
  }
7438
7497
  } catch (err) {
7439
- console.warn("[dev-trace] Failed to load local trace logger:", err);
7498
+ logger$c.warn("Failed to load local trace logger:", err);
7440
7499
  }
7441
7500
  }
7442
7501
  return cachedFactory;
@@ -11171,6 +11230,7 @@ function formatDeadLinkMessage(label, result) {
11171
11230
  const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
11172
11231
  return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
11173
11232
  }
11233
+ const logger$b = createLogger("Screenshot");
11174
11234
  async function captureScreenshot(wc) {
11175
11235
  for (let attempt = 0; attempt < 3; attempt += 1) {
11176
11236
  await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
@@ -11180,13 +11240,21 @@ async function captureScreenshot(wc) {
11180
11240
  const size = image.getSize();
11181
11241
  const base64 = image.toPNG().toString("base64");
11182
11242
  if (base64) {
11183
- return { ok: true, base64, width: size.width, height: size.height };
11243
+ return okResult({
11244
+ base64,
11245
+ width: size.width,
11246
+ height: size.height
11247
+ });
11184
11248
  }
11185
11249
  }
11186
- } catch {
11250
+ } catch (err) {
11251
+ logger$b.debug(
11252
+ `capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
11253
+ getErrorMessage(err)
11254
+ );
11187
11255
  }
11188
11256
  }
11189
- return { ok: false, error: "Page image was empty after 3 attempts" };
11257
+ return errorResult("Page image was empty after 3 attempts");
11190
11258
  }
11191
11259
  const SESSION_VERSION = 1;
11192
11260
  function getSessionsDir() {
@@ -12081,17 +12149,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
12081
12149
  appliedFilters
12082
12150
  };
12083
12151
  }
12152
+ const logger$a = createLogger("PageActions");
12084
12153
  function getBookmarkMetadataFromArgs(args) {
12085
12154
  return normalizeBookmarkMetadata({
12086
- intent: args.intent,
12087
- expectedContent: args.expectedContent,
12088
- keyFields: args.keyFields,
12089
- agentHints: args.agentHints
12155
+ intent: args.intent ?? args.intent,
12156
+ expectedContent: args.expectedContent ?? args.expected_content,
12157
+ keyFields: args.keyFields ?? args.key_fields,
12158
+ agentHints: args.agentHints ?? args.agent_hints
12090
12159
  });
12091
12160
  }
12092
12161
  const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
12093
12162
  const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
12094
- async function loadPermittedUrl$1(wc, url) {
12163
+ async function loadPermittedUrl(wc, url) {
12095
12164
  assertPermittedNavigationURL(url);
12096
12165
  await wc.loadURL(url);
12097
12166
  }
@@ -12264,7 +12333,9 @@ async function executePageScript(wc, script, options) {
12264
12333
  return PAGE_SCRIPT_TIMEOUT;
12265
12334
  }
12266
12335
  return result;
12267
- } catch {
12336
+ } catch (err) {
12337
+ const label = options?.label ? ` (${options.label})` : "";
12338
+ logger$a.warn(`Failed to execute page script${label}:`, err);
12268
12339
  return null;
12269
12340
  } finally {
12270
12341
  if (timer) {
@@ -12364,7 +12435,8 @@ async function getPostSearchSummary(wc) {
12364
12435
  Search results snapshot:
12365
12436
  ${truncated}`;
12366
12437
  }
12367
- } catch {
12438
+ } catch (err) {
12439
+ logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
12368
12440
  }
12369
12441
  const fallback = await getPostNavSummary(wc);
12370
12442
  return fallback ? `${fallback}
@@ -12386,11 +12458,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
12386
12458
  Page snapshot after navigation:
12387
12459
  ${truncated}`;
12388
12460
  }
12389
- } catch {
12461
+ } catch (err) {
12462
+ logger$a.warn("Failed to build post-click navigation summary:", err);
12390
12463
  }
12391
12464
  return "";
12392
12465
  }
12393
- async function scrollPage$1(wc, deltaY) {
12466
+ async function scrollPage(wc, deltaY) {
12394
12467
  const getScrollY = async () => {
12395
12468
  const scrollY = await executePageScript(
12396
12469
  wc,
@@ -12434,7 +12507,7 @@ async function scrollPage$1(wc, deltaY) {
12434
12507
  movedY: Math.round(afterY - beforeY)
12435
12508
  };
12436
12509
  }
12437
- async function clickElement$1(wc, selector) {
12510
+ async function clickElement(wc, selector) {
12438
12511
  const target = await executePageScript(
12439
12512
  wc,
12440
12513
  `
@@ -12523,7 +12596,7 @@ async function clickElement$1(wc, selector) {
12523
12596
  return "Error: Could not resolve click coordinates";
12524
12597
  }
12525
12598
  if (hiddenWindow) {
12526
- const activationResult = await activateElement$1(wc, selector);
12599
+ const activationResult = await activateElement(wc, selector);
12527
12600
  if (activationResult.startsWith("Error:")) {
12528
12601
  return activationResult;
12529
12602
  }
@@ -12538,7 +12611,7 @@ async function clickElement$1(wc, selector) {
12538
12611
  await sleep(80);
12539
12612
  return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
12540
12613
  }
12541
- async function activateElement$1(wc, selector) {
12614
+ async function activateElement(wc, selector) {
12542
12615
  const activated = await executePageScript(
12543
12616
  wc,
12544
12617
  `
@@ -12570,7 +12643,7 @@ async function activateElement$1(wc, selector) {
12570
12643
  }
12571
12644
  return "Activated element via DOM click";
12572
12645
  }
12573
- async function describeElementForClick$1(wc, selector) {
12646
+ async function describeElementForClick(wc, selector) {
12574
12647
  const result = await executePageScript(
12575
12648
  wc,
12576
12649
  `
@@ -12879,7 +12952,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12879
12952
  return;
12880
12953
  }
12881
12954
  }
12882
- } catch {
12955
+ } catch (err) {
12956
+ logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
12883
12957
  }
12884
12958
  if (snapshot.url && snapshot.url !== wc.getURL()) {
12885
12959
  try {
@@ -12887,14 +12961,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12887
12961
  await wc.loadURL(snapshot.url);
12888
12962
  await waitForLoad(wc, 3e3);
12889
12963
  return;
12890
- } catch {
12964
+ } catch (err) {
12965
+ logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
12891
12966
  }
12892
12967
  }
12893
12968
  if (snapshot.url) {
12894
12969
  try {
12895
12970
  await wc.reload();
12896
12971
  await waitForLoad(wc, 3e3);
12897
- } catch {
12972
+ } catch (err) {
12973
+ logger$a.warn("Failed to restore locale via page reload:", err);
12898
12974
  }
12899
12975
  }
12900
12976
  }
@@ -13025,14 +13101,14 @@ Go back to search results to select the next product.`;
13025
13101
  if (!overlayHint) {
13026
13102
  return cartSummary;
13027
13103
  }
13028
- const dialogActions = await getCartDialogActions$1(wc);
13104
+ const dialogActions = await getCartDialogActions(wc);
13029
13105
  const actionsSuffix = dialogActions ? `
13030
13106
  ${dialogActions}
13031
13107
  Click one of these dialog actions. Do NOT click any other element.` : "";
13032
13108
  return `
13033
13109
  ${overlayHint}${actionsSuffix}${cartSummary}`;
13034
13110
  }
13035
- async function clickResolvedSelector$1(wc, selector) {
13111
+ async function clickResolvedSelector(wc, selector) {
13036
13112
  if (selector.startsWith("__vessel_idx:")) {
13037
13113
  const idx = Number(selector.slice("__vessel_idx:".length));
13038
13114
  const beforeUrl2 = wc.getURL();
@@ -13065,10 +13141,10 @@ Go back and select a different product.`;
13065
13141
  await waitForPotentialNavigation(wc, beforeUrl2);
13066
13142
  const afterUrl2 = wc.getURL();
13067
13143
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
13068
- let idxOverlay = await detectPostClickOverlay$1(wc);
13144
+ let idxOverlay = await detectPostClickOverlay(wc);
13069
13145
  if (!idxOverlay && idxCartMatch) {
13070
13146
  await sleep(1200);
13071
- idxOverlay = await detectPostClickOverlay$1(wc);
13147
+ idxOverlay = await detectPostClickOverlay(wc);
13072
13148
  }
13073
13149
  if (idxCartMatch) {
13074
13150
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
@@ -13077,7 +13153,7 @@ Go back and select a different product.`;
13077
13153
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
13078
13154
  if (hrefMatch) {
13079
13155
  try {
13080
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13156
+ await loadPermittedUrl(wc, hrefMatch[1]);
13081
13157
  await waitForLoad(wc, 8e3);
13082
13158
  const hrefUrl = wc.getURL();
13083
13159
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13132,10 +13208,10 @@ Go back and select a different product.`;
13132
13208
  await waitForPotentialNavigation(wc, beforeUrl2);
13133
13209
  const afterUrl2 = wc.getURL();
13134
13210
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
13135
- let shadowOverlay = await detectPostClickOverlay$1(wc);
13211
+ let shadowOverlay = await detectPostClickOverlay(wc);
13136
13212
  if (!shadowOverlay && shadowCartMatch) {
13137
13213
  await sleep(1200);
13138
- shadowOverlay = await detectPostClickOverlay$1(wc);
13214
+ shadowOverlay = await detectPostClickOverlay(wc);
13139
13215
  }
13140
13216
  if (shadowCartMatch) {
13141
13217
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
@@ -13144,7 +13220,7 @@ Go back and select a different product.`;
13144
13220
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
13145
13221
  if (hrefMatch) {
13146
13222
  try {
13147
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13223
+ await loadPermittedUrl(wc, hrefMatch[1]);
13148
13224
  await waitForLoad(wc, 8e3);
13149
13225
  const hrefUrl = wc.getURL();
13150
13226
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13157,14 +13233,14 @@ ${shadowOverlay}` : `${result}
13157
13233
  Note: Page did not change after click.`;
13158
13234
  }
13159
13235
  const beforeUrl = wc.getURL();
13160
- const elInfo = await describeElementForClick$1(wc, selector);
13236
+ const elInfo = await describeElementForClick(wc, selector);
13161
13237
  if ("error" in elInfo) return `Error: ${elInfo.error}`;
13162
13238
  const cartMatch = isAddToCartText(elInfo.text);
13163
13239
  if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
13164
13240
  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
13241
  }
13166
13242
  if (!cartMatch && recentCartClicks.has(beforeUrl)) {
13167
- const dialogActions = await getCartDialogActions$1(wc);
13243
+ const dialogActions = await getCartDialogActions(wc);
13168
13244
  if (dialogActions) {
13169
13245
  return `Blocked: a cart confirmation dialog is open. Do not click background elements.
13170
13246
  ${dialogActions}
@@ -13187,14 +13263,14 @@ Go back and select a different product.`;
13187
13263
  }
13188
13264
  const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
13189
13265
  const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
13190
- const clickResult = await clickElement$1(wc, selector);
13266
+ const clickResult = await clickElement(wc, selector);
13191
13267
  if (clickResult.startsWith("Error:")) return clickResult;
13192
13268
  await waitForPotentialNavigation(wc, beforeUrl);
13193
13269
  const afterUrl = wc.getURL();
13194
13270
  if (afterUrl !== beforeUrl) {
13195
13271
  return `${clickText} -> ${afterUrl}`;
13196
13272
  }
13197
- const overlayHint = await detectPostClickOverlay$1(wc);
13273
+ const overlayHint = await detectPostClickOverlay(wc);
13198
13274
  if (overlayHint) {
13199
13275
  if (cartMatch) {
13200
13276
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
@@ -13208,7 +13284,7 @@ ${overlayHint}`;
13208
13284
  }
13209
13285
  if (cartMatch) {
13210
13286
  await sleep(1200);
13211
- const delayedOverlayHint = await detectPostClickOverlay$1(wc);
13287
+ const delayedOverlayHint = await detectPostClickOverlay(wc);
13212
13288
  if (delayedOverlayHint) {
13213
13289
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
13214
13290
  wc,
@@ -13221,7 +13297,7 @@ ${overlayHint}`;
13221
13297
  beforeUrl
13222
13298
  )}`;
13223
13299
  }
13224
- const activationResult = await activateElement$1(wc, selector);
13300
+ const activationResult = await activateElement(wc, selector);
13225
13301
  if (!activationResult.startsWith("Error:")) {
13226
13302
  await waitForPotentialNavigation(wc, beforeUrl);
13227
13303
  const fallbackUrl = wc.getURL();
@@ -13229,7 +13305,7 @@ ${overlayHint}`;
13229
13305
  return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
13230
13306
  }
13231
13307
  }
13232
- const postActivationOverlayHint = await detectPostClickOverlay$1(wc);
13308
+ const postActivationOverlayHint = await detectPostClickOverlay(wc);
13233
13309
  if (postActivationOverlayHint) {
13234
13310
  return `${clickText} (${clickResult})
13235
13311
  ${postActivationOverlayHint}`;
@@ -13239,13 +13315,14 @@ ${postActivationOverlayHint}`;
13239
13315
  const validation = await validateLinkDestination(elInfo.href);
13240
13316
  if (validation.status !== "dead") {
13241
13317
  try {
13242
- await loadPermittedUrl$1(wc, elInfo.href);
13318
+ await loadPermittedUrl(wc, elInfo.href);
13243
13319
  await waitForLoad(wc, 8e3);
13244
13320
  const hrefFallbackUrl = wc.getURL();
13245
13321
  if (hrefFallbackUrl !== beforeUrl) {
13246
13322
  return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
13247
13323
  }
13248
- } catch {
13324
+ } catch (err) {
13325
+ logger$a.warn("Failed href fallback after click, returning generic click result:", err);
13249
13326
  }
13250
13327
  }
13251
13328
  }
@@ -13289,11 +13366,12 @@ async function tryAutoDismissCartDialog(wc) {
13289
13366
  await sleep(500);
13290
13367
  return result;
13291
13368
  }
13292
- } catch {
13369
+ } catch (err) {
13370
+ logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
13293
13371
  }
13294
13372
  return null;
13295
13373
  }
13296
- async function getCartDialogActions$1(wc) {
13374
+ async function getCartDialogActions(wc) {
13297
13375
  const result = await executePageScript(
13298
13376
  wc,
13299
13377
  `
@@ -13338,7 +13416,7 @@ async function getCartDialogActions$1(wc) {
13338
13416
  return `Available dialog actions:
13339
13417
  ${result.actions.join("\n")}`;
13340
13418
  }
13341
- async function detectPostClickOverlay$1(wc) {
13419
+ async function detectPostClickOverlay(wc) {
13342
13420
  const result = await executePageScript(
13343
13421
  wc,
13344
13422
  `
@@ -13444,7 +13522,7 @@ async function detectPostClickOverlay$1(wc) {
13444
13522
  const desc = result.label ? ` ("${result.label}")` : "";
13445
13523
  return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
13446
13524
  }
13447
- async function dismissPopup$1(wc) {
13525
+ async function dismissPopup(wc) {
13448
13526
  const before = await extractContent(wc);
13449
13527
  const initialBlocking = before.overlays.filter(
13450
13528
  (overlay) => overlay.blocksInteraction
@@ -13497,7 +13575,7 @@ async function dismissPopup$1(wc) {
13497
13575
  if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
13498
13576
  return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
13499
13577
  }
13500
- const dialogActions = await getCartDialogActions$1(wc);
13578
+ const dialogActions = await getCartDialogActions(wc);
13501
13579
  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
13580
  }
13503
13581
  }
@@ -13657,7 +13735,7 @@ async function dismissPopup$1(wc) {
13657
13735
  if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
13658
13736
  continue;
13659
13737
  }
13660
- const result = await clickElement$1(wc, candidate.selector);
13738
+ const result = await clickElement(wc, candidate.selector);
13661
13739
  if (result.startsWith("Error:")) continue;
13662
13740
  await sleep(250);
13663
13741
  const postClickLocale = await getLocaleSnapshot(wc);
@@ -13699,7 +13777,7 @@ function describeOverlayState(page) {
13699
13777
  }
13700
13778
  async function clickOverlayCandidate(wc, action) {
13701
13779
  if (!action?.selector) return null;
13702
- const result = await clickResolvedSelector$1(wc, action.selector);
13780
+ const result = await clickResolvedSelector(wc, action.selector);
13703
13781
  return `${action.label || action.selector}: ${result}`;
13704
13782
  }
13705
13783
  async function tryDismissConsentIframe(wc) {
@@ -13910,7 +13988,7 @@ Submitted modal: ${submitResult}`;
13910
13988
  }
13911
13989
  }
13912
13990
  if (!actionMessage) {
13913
- actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
13991
+ actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
13914
13992
  }
13915
13993
  steps.push(actionMessage);
13916
13994
  if (overlay.kind === "cookie_consent") {
@@ -14085,7 +14163,7 @@ async function fillFormFields(wc, fields) {
14085
14163
  });
14086
14164
  continue;
14087
14165
  }
14088
- const result = await setElementValue$1(
14166
+ const result = await setElementValue(
14089
14167
  wc,
14090
14168
  selector,
14091
14169
  String(field.value || "")
@@ -14094,14 +14172,14 @@ async function fillFormFields(wc, fields) {
14094
14172
  }
14095
14173
  return results;
14096
14174
  }
14097
- function getTabByMatch$1(tabManager, match) {
14175
+ function getTabByMatch(tabManager, match) {
14098
14176
  if (!match) return null;
14099
14177
  const lowered = match.toLowerCase();
14100
14178
  return tabManager.getAllStates().find(
14101
14179
  (tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
14102
14180
  ) || null;
14103
14181
  }
14104
- function isDangerousAction$1(name) {
14182
+ function isDangerousAction(name) {
14105
14183
  return [
14106
14184
  "navigate",
14107
14185
  "open_bookmark",
@@ -14112,6 +14190,7 @@ function isDangerousAction$1(name) {
14112
14190
  "press_key",
14113
14191
  "create_tab",
14114
14192
  "switch_tab",
14193
+ "close_tab",
14115
14194
  "restore_checkpoint",
14116
14195
  "load_session",
14117
14196
  "login",
@@ -14120,7 +14199,7 @@ function isDangerousAction$1(name) {
14120
14199
  "paginate"
14121
14200
  ].includes(name);
14122
14201
  }
14123
- async function setElementValue$1(wc, selector, value) {
14202
+ async function setElementValue(wc, selector, value) {
14124
14203
  if (selector.startsWith("__vessel_idx:")) {
14125
14204
  const idx = Number(selector.slice("__vessel_idx:".length));
14126
14205
  const result2 = await executePageScript(
@@ -14224,7 +14303,7 @@ async function setElementValue$1(wc, selector, value) {
14224
14303
  );
14225
14304
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14226
14305
  }
14227
- async function typeKeystroke$1(wc, selector, value) {
14306
+ async function typeKeystroke(wc, selector, value) {
14228
14307
  const result = await executePageScript(
14229
14308
  wc,
14230
14309
  `
@@ -14272,7 +14351,7 @@ async function typeKeystroke$1(wc, selector, value) {
14272
14351
  );
14273
14352
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14274
14353
  }
14275
- async function hoverElement$1(wc, selector) {
14354
+ async function hoverElement(wc, selector) {
14276
14355
  const pos = await wc.executeJavaScript(`
14277
14356
  (function() {
14278
14357
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14302,7 +14381,7 @@ async function hoverElement$1(wc, selector) {
14302
14381
  const label = typeof pos.label === "string" ? pos.label : "element";
14303
14382
  return `Hovered: ${label}`;
14304
14383
  }
14305
- async function focusElement$1(wc, selector) {
14384
+ async function focusElement(wc, selector) {
14306
14385
  return wc.executeJavaScript(`
14307
14386
  (function() {
14308
14387
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14316,7 +14395,7 @@ async function focusElement$1(wc, selector) {
14316
14395
  })()
14317
14396
  `);
14318
14397
  }
14319
- async function waitForCondition$1(wc, args) {
14398
+ async function waitForCondition(wc, args) {
14320
14399
  const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
14321
14400
  const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
14322
14401
  const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
@@ -14375,8 +14454,8 @@ function findCheckpoint(checkpoints, args) {
14375
14454
  }
14376
14455
  return null;
14377
14456
  }
14378
- function resolveBookmarkFolderTarget$1(args) {
14379
- const folderId = typeof args.folderId === "string" ? args.folderId.trim() : "";
14457
+ function resolveBookmarkFolderTarget(args) {
14458
+ const folderId = typeof args.folderId === "string" ? args.folderId.trim() : typeof args.folder_id === "string" ? args.folder_id.trim() : "";
14380
14459
  if (folderId) {
14381
14460
  if (folderId === UNSORTED_ID) {
14382
14461
  return {
@@ -14390,7 +14469,7 @@ function resolveBookmarkFolderTarget$1(args) {
14390
14469
  }
14391
14470
  return { folderId: folder2.id, folderName: folder2.name };
14392
14471
  }
14393
- const folderName = typeof args.folderName === "string" && args.folderName.trim() ? args.folderName.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
14472
+ 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
14473
  if (!folderName || folderName.toLowerCase() === "unsorted") {
14395
14474
  return {
14396
14475
  folderId: UNSORTED_ID,
@@ -14401,10 +14480,11 @@ function resolveBookmarkFolderTarget$1(args) {
14401
14480
  if (existing) {
14402
14481
  return { folderId: existing.id, folderName: existing.name };
14403
14482
  }
14404
- if (args.createFolderIfMissing === false) {
14483
+ const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
14484
+ if (createIfMissing === false) {
14405
14485
  return { folderName, error: `Folder "${folderName}" not found` };
14406
14486
  }
14407
- const folderSummary = typeof args.folderSummary === "string" && args.folderSummary.trim() ? args.folderSummary.trim() : void 0;
14487
+ 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
14488
  const { folder } = ensureFolder(folderName, folderSummary);
14409
14489
  return {
14410
14490
  folderId: folder.id,
@@ -14412,27 +14492,27 @@ function resolveBookmarkFolderTarget$1(args) {
14412
14492
  createdFolder: folder.name
14413
14493
  };
14414
14494
  }
14415
- function formatFolderStatus$1(limit = 6) {
14495
+ function formatFolderStatus(limit = 6) {
14416
14496
  const folders = listFolderOverviews();
14417
14497
  const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
14418
14498
  return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
14419
14499
  }
14420
- function describeFolder$1(folderId) {
14500
+ function describeFolder(folderId) {
14421
14501
  if (!folderId || folderId === UNSORTED_ID) {
14422
14502
  return "Unsorted";
14423
14503
  }
14424
14504
  return getFolder(folderId)?.name ?? folderId;
14425
14505
  }
14426
- function composeDuplicateBookmarkResponse$1(args) {
14506
+ function composeDuplicateBookmarkResponse(args) {
14427
14507
  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
14508
  }
14429
- function composeFolderAwareResponse$1(message, createdFolder) {
14509
+ function composeFolderAwareResponse(message, createdFolder) {
14430
14510
  const prefix = createdFolder ? `Created folder "${createdFolder}".
14431
14511
  ` : "";
14432
14512
  return `${prefix}${message}
14433
- ${formatFolderStatus$1()}`;
14513
+ ${formatFolderStatus()}`;
14434
14514
  }
14435
- async function selectOption$1(wc, args) {
14515
+ async function selectOption(wc, args) {
14436
14516
  const selector = await resolveSelector(wc, args.index, args.selector);
14437
14517
  if (!selector) return "Error: No select element index or selector provided";
14438
14518
  const result = await executePageScript(
@@ -14468,7 +14548,7 @@ async function selectOption$1(wc, args) {
14468
14548
  );
14469
14549
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
14470
14550
  }
14471
- async function submitForm$1(wc, args) {
14551
+ async function submitForm(wc, args) {
14472
14552
  const beforeUrl = wc.getURL();
14473
14553
  let selector = await resolveSelector(wc, args.index, args.selector);
14474
14554
  if (!selector) {
@@ -14609,7 +14689,7 @@ async function submitForm$1(wc, args) {
14609
14689
  if (formInfo.params) {
14610
14690
  url.search = formInfo.params;
14611
14691
  }
14612
- await loadPermittedUrl$1(wc, url.toString());
14692
+ await loadPermittedUrl(wc, url.toString());
14613
14693
  await waitForPotentialNavigation(wc, beforeUrl);
14614
14694
  const afterUrl = wc.getURL();
14615
14695
  return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
@@ -14651,8 +14731,20 @@ async function submitForm$1(wc, args) {
14651
14731
  }
14652
14732
  return "Submitted form";
14653
14733
  }
14734
+ async function pressKeyDirect(wc, key, index, selector) {
14735
+ return pressKey(wc, { key, index, selector });
14736
+ }
14737
+ async function submitFormDirect(wc, index, selector) {
14738
+ return submitForm(wc, { index, selector });
14739
+ }
14740
+ async function selectOptionDirect(wc, index, selector, label, value) {
14741
+ return selectOption(wc, { index, selector, label, value });
14742
+ }
14743
+ async function waitForConditionDirect(wc, text, selector, timeoutMs) {
14744
+ return waitForCondition(wc, { text, selector, timeoutMs });
14745
+ }
14654
14746
  async function clickElementBySelector(wc, selector) {
14655
- return clickResolvedSelector$1(wc, selector);
14747
+ return clickResolvedSelector(wc, selector);
14656
14748
  }
14657
14749
  function normalizeSearchQuery(query) {
14658
14750
  return query.replace(/\s+/g, " ").trim();
@@ -14936,7 +15028,7 @@ async function searchPage(wc, args) {
14936
15028
  const shortcut = buildSearchShortcut(wc.getURL(), query);
14937
15029
  if (shortcut) {
14938
15030
  const beforeUrl2 = wc.getURL();
14939
- await loadPermittedUrl$1(wc, shortcut.url);
15031
+ await loadPermittedUrl(wc, shortcut.url);
14940
15032
  await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
14941
15033
  const afterUrl2 = wc.getURL();
14942
15034
  const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
@@ -14954,13 +15046,13 @@ async function searchPage(wc, args) {
14954
15046
  if (!searchInfo?.selector) {
14955
15047
  return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
14956
15048
  }
14957
- const fillResult = await setElementValue$1(wc, searchInfo.selector, query);
15049
+ const fillResult = await setElementValue(wc, searchInfo.selector, query);
14958
15050
  if (fillResult.startsWith("Error:")) {
14959
15051
  return fillResult;
14960
15052
  }
14961
15053
  await sleep(100);
14962
15054
  const beforeUrl = wc.getURL();
14963
- const keyResult = await pressKey$1(wc, {
15055
+ const keyResult = await pressKey(wc, {
14964
15056
  key: "Enter",
14965
15057
  selector: searchInfo.selector
14966
15058
  });
@@ -14984,7 +15076,7 @@ async function searchPage(wc, args) {
14984
15076
  }
14985
15077
  return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
14986
15078
  }
14987
- async function pressKey$1(wc, args) {
15079
+ async function pressKey(wc, args) {
14988
15080
  const key = typeof args.key === "string" ? args.key.trim() : "";
14989
15081
  if (!key) return "Error: No key provided";
14990
15082
  const selector = await resolveSelector(wc, args.index, args.selector);
@@ -15196,9 +15288,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
15196
15288
  "metrics",
15197
15289
  "wait_for_navigation"
15198
15290
  ]);
15199
- async function executeAction(name, rawArgs, ctx) {
15291
+ async function executeAction(name, args, ctx) {
15200
15292
  name = normalizeToolAlias(name);
15201
- const args = rawArgs;
15202
15293
  if (!KNOWN_TOOLS.has(name)) {
15203
15294
  for (const known of KNOWN_TOOLS) {
15204
15295
  if (name.startsWith(known) && name.length > known.length) {
@@ -15247,7 +15338,7 @@ async function executeAction(name, rawArgs, ctx) {
15247
15338
  name,
15248
15339
  args,
15249
15340
  tabId,
15250
- dangerous: isDangerousAction$1(name),
15341
+ dangerous: isDangerousAction(name),
15251
15342
  executor: async () => {
15252
15343
  switch (name) {
15253
15344
  case "screenshot": {
@@ -15295,7 +15386,7 @@ async function executeAction(name, rawArgs, ctx) {
15295
15386
  case "switch_tab": {
15296
15387
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15297
15388
  if (!targetId) {
15298
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15389
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15299
15390
  }
15300
15391
  if (!targetId) return "Error: No matching tab found";
15301
15392
  ctx.tabManager.switchTab(targetId);
@@ -15389,7 +15480,7 @@ async function executeAction(name, rawArgs, ctx) {
15389
15480
  if (!selector) {
15390
15481
  return "Error: No element index, selector, or visible text provided";
15391
15482
  }
15392
- return clickResolvedSelector$1(wc, selector);
15483
+ return clickResolvedSelector(wc, selector);
15393
15484
  }
15394
15485
  case "inspect_element": {
15395
15486
  if (!wc) return "Error: No active tab";
@@ -15421,18 +15512,18 @@ async function executeAction(name, rawArgs, ctx) {
15421
15512
  if (!selector) return "Error: No element index or selector provided";
15422
15513
  const mode = typeof args.mode === "string" ? args.mode : "default";
15423
15514
  if (mode === "keystroke") {
15424
- return typeKeystroke$1(wc, selector, String(args.text || ""));
15515
+ return typeKeystroke(wc, selector, String(args.text || ""));
15425
15516
  }
15426
- return setElementValue$1(wc, selector, String(args.text || ""));
15517
+ return setElementValue(wc, selector, String(args.text || ""));
15427
15518
  }
15428
15519
  case "select_option": {
15429
15520
  if (!wc) return "Error: No active tab";
15430
- return selectOption$1(wc, args);
15521
+ return selectOption(wc, args);
15431
15522
  }
15432
15523
  case "submit_form": {
15433
15524
  if (!wc) return "Error: No active tab";
15434
15525
  const beforeUrl = wc.getURL();
15435
- const result2 = await submitForm$1(wc, args);
15526
+ const result2 = await submitForm(wc, args);
15436
15527
  if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
15437
15528
  return result2;
15438
15529
  }
@@ -15443,7 +15534,7 @@ async function executeAction(name, rawArgs, ctx) {
15443
15534
  case "press_key": {
15444
15535
  if (!wc) return "Error: No active tab";
15445
15536
  const beforeUrl = wc.getURL();
15446
- const result2 = await pressKey$1(wc, args);
15537
+ const result2 = await pressKey(wc, args);
15447
15538
  const key = typeof args.key === "string" ? args.key.trim() : "";
15448
15539
  if (key === "Enter") {
15449
15540
  await waitForPotentialNavigation(wc, beforeUrl, 3e3);
@@ -15460,20 +15551,20 @@ async function executeAction(name, rawArgs, ctx) {
15460
15551
  if (!wc) return "Error: No active tab";
15461
15552
  const pixels = coerceOptionalNumber(args.amount) ?? 500;
15462
15553
  const dir = args.direction === "up" ? -pixels : pixels;
15463
- const result2 = await scrollPage$1(wc, dir);
15554
+ const result2 = await scrollPage(wc, dir);
15464
15555
  return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
15465
15556
  }
15466
15557
  case "hover": {
15467
15558
  if (!wc) return "Error: No active tab";
15468
15559
  const selector = await resolveSelector(wc, args.index, args.selector);
15469
15560
  if (!selector) return "Error: No element index or selector provided";
15470
- return hoverElement$1(wc, selector);
15561
+ return hoverElement(wc, selector);
15471
15562
  }
15472
15563
  case "focus": {
15473
15564
  if (!wc) return "Error: No active tab";
15474
15565
  const selector = await resolveSelector(wc, args.index, args.selector);
15475
15566
  if (!selector) return "Error: No element index or selector provided";
15476
- return focusElement$1(wc, selector);
15567
+ return focusElement(wc, selector);
15477
15568
  }
15478
15569
  case "set_ad_blocking": {
15479
15570
  const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
@@ -15482,7 +15573,7 @@ async function executeAction(name, rawArgs, ctx) {
15482
15573
  }
15483
15574
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15484
15575
  if (!targetId) {
15485
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15576
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15486
15577
  }
15487
15578
  if (!targetId) {
15488
15579
  targetId = ctx.tabManager.getActiveTabId() || "";
@@ -15501,7 +15592,7 @@ async function executeAction(name, rawArgs, ctx) {
15501
15592
  }
15502
15593
  case "dismiss_popup": {
15503
15594
  if (!wc) return "Error: No active tab";
15504
- return dismissPopup$1(wc);
15595
+ return dismissPopup(wc);
15505
15596
  }
15506
15597
  case "clear_overlays": {
15507
15598
  if (!wc) return "Error: No active tab";
@@ -15524,7 +15615,8 @@ async function executeAction(name, rawArgs, ctx) {
15524
15615
  }, 6e3)
15525
15616
  )
15526
15617
  ]);
15527
- } catch {
15618
+ } catch (err) {
15619
+ logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
15528
15620
  content = null;
15529
15621
  }
15530
15622
  if (!content || content.content.length === 0) {
@@ -15540,11 +15632,13 @@ async function executeAction(name, rawArgs, ctx) {
15540
15632
  extractContent(wc),
15541
15633
  new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
15542
15634
  ]);
15543
- } catch {
15635
+ } catch (err) {
15636
+ logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
15544
15637
  content = null;
15545
15638
  }
15546
15639
  }
15547
- } catch {
15640
+ } catch (err) {
15641
+ logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
15548
15642
  }
15549
15643
  }
15550
15644
  if (content && content.content.length > 0) {
@@ -15584,7 +15678,7 @@ ${truncated}`;
15584
15678
  }
15585
15679
  case "wait_for": {
15586
15680
  if (!wc) return "Error: No active tab";
15587
- return waitForCondition$1(wc, args);
15681
+ return waitForCondition(wc, args);
15588
15682
  }
15589
15683
  case "wait_for_navigation": {
15590
15684
  if (!wc) return "Error: No active tab";
@@ -15727,12 +15821,12 @@ ${truncated}`;
15727
15821
  (folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
15728
15822
  );
15729
15823
  if (existing) {
15730
- return composeFolderAwareResponse$1(
15824
+ return composeFolderAwareResponse(
15731
15825
  `Folder "${existing.name}" already exists (id=${existing.id})`
15732
15826
  );
15733
15827
  }
15734
15828
  const folder = createFolderWithSummary(name2, summary);
15735
- return composeFolderAwareResponse$1(
15829
+ return composeFolderAwareResponse(
15736
15830
  `Created folder "${folder.name}" (id=${folder.id})`
15737
15831
  );
15738
15832
  }
@@ -15744,7 +15838,7 @@ ${truncated}`;
15744
15838
  resolvedSelector
15745
15839
  });
15746
15840
  if ("error" in source) return `Error: ${source.error}`;
15747
- const target = resolveBookmarkFolderTarget$1(args);
15841
+ const target = resolveBookmarkFolderTarget(args);
15748
15842
  if (target.error) return target.error;
15749
15843
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
15750
15844
  const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
@@ -15759,10 +15853,10 @@ ${truncated}`;
15759
15853
  }
15760
15854
  );
15761
15855
  if (result2.status === "conflict" && result2.existing) {
15762
- return composeFolderAwareResponse$1(
15763
- composeDuplicateBookmarkResponse$1({
15856
+ return composeFolderAwareResponse(
15857
+ composeDuplicateBookmarkResponse({
15764
15858
  url: source.url,
15765
- folderName: describeFolder$1(target.folderId),
15859
+ folderName: describeFolder(target.folderId),
15766
15860
  bookmarkId: result2.existing.id
15767
15861
  }),
15768
15862
  target.createdFolder
@@ -15771,13 +15865,13 @@ ${truncated}`;
15771
15865
  const bookmark = result2.bookmark;
15772
15866
  if (!bookmark) return "Error: Bookmark save failed";
15773
15867
  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})`,
15868
+ return composeFolderAwareResponse(
15869
+ `${verb} "${bookmark.title}" (${bookmark.url}) in "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15776
15870
  target.createdFolder
15777
15871
  );
15778
15872
  }
15779
15873
  case "organize_bookmark": {
15780
- const target = resolveBookmarkFolderTarget$1(args);
15874
+ const target = resolveBookmarkFolderTarget(args);
15781
15875
  if (target.error) return target.error;
15782
15876
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15783
15877
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15801,8 +15895,8 @@ ${truncated}`;
15801
15895
  if (!updated) {
15802
15896
  return `Bookmark ${existing.id} not found`;
15803
15897
  }
15804
- return composeFolderAwareResponse$1(
15805
- `Organized existing bookmark "${updated.title}" into "${describeFolder$1(updated.folderId)}" (id=${updated.id})`,
15898
+ return composeFolderAwareResponse(
15899
+ `Organized existing bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
15806
15900
  target.createdFolder
15807
15901
  );
15808
15902
  }
@@ -15819,13 +15913,13 @@ ${truncated}`;
15819
15913
  );
15820
15914
  const bookmark = result2.bookmark;
15821
15915
  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})`,
15916
+ return composeFolderAwareResponse(
15917
+ `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15824
15918
  target.createdFolder
15825
15919
  );
15826
15920
  }
15827
15921
  case "archive_bookmark": {
15828
- const target = resolveBookmarkFolderTarget$1({ archive: true });
15922
+ const target = resolveBookmarkFolderTarget({ archive: true });
15829
15923
  if (target.error) return target.error;
15830
15924
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15831
15925
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15848,8 +15942,8 @@ ${truncated}`;
15848
15942
  if (!updated) {
15849
15943
  return `Bookmark ${existing.id} not found`;
15850
15944
  }
15851
- return composeFolderAwareResponse$1(
15852
- `Archived bookmark "${updated.title}" into "${describeFolder$1(updated.folderId)}" (id=${updated.id})`,
15945
+ return composeFolderAwareResponse(
15946
+ `Archived bookmark "${updated.title}" into "${describeFolder(updated.folderId)}" (id=${updated.id})`,
15853
15947
  target.createdFolder
15854
15948
  );
15855
15949
  }
@@ -15862,8 +15956,8 @@ ${truncated}`;
15862
15956
  target.folderId,
15863
15957
  note
15864
15958
  );
15865
- return composeFolderAwareResponse$1(
15866
- `Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15959
+ return composeFolderAwareResponse(
15960
+ `Saved and archived "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
15867
15961
  target.createdFolder
15868
15962
  );
15869
15963
  }
@@ -15951,7 +16045,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
15951
16045
  let page;
15952
16046
  try {
15953
16047
  page = await extractContent(wc);
15954
- } catch {
16048
+ } catch (err) {
16049
+ logger$a.warn("Failed to extract content for suggest:", err);
15955
16050
  return "Could not read page. Try navigate to a working URL.";
15956
16051
  }
15957
16052
  const suggestions = [];
@@ -16056,7 +16151,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
16056
16151
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
16057
16152
  if (firstSel) {
16058
16153
  const beforeUrl = wc.getURL();
16059
- const submitResult = await submitForm$1(wc, { selector: firstSel });
16154
+ const submitResult = await submitForm(wc, { selector: firstSel });
16060
16155
  await waitForPotentialNavigation(wc, beforeUrl);
16061
16156
  const afterUrl = wc.getURL();
16062
16157
  results.push(
@@ -16104,13 +16199,13 @@ ${results.join("\n")}`;
16104
16199
  );
16105
16200
  if (!passSel)
16106
16201
  return "Error: Could not find password field. Try providing password_selector.";
16107
- const userResult = await setElementValue$1(
16202
+ const userResult = await setElementValue(
16108
16203
  wc,
16109
16204
  userSel,
16110
16205
  String(args.username || "")
16111
16206
  );
16112
16207
  steps.push(userResult);
16113
- const passResult = await setElementValue$1(
16208
+ const passResult = await setElementValue(
16114
16209
  wc,
16115
16210
  passSel,
16116
16211
  String(args.password || "")
@@ -16118,7 +16213,7 @@ ${results.join("\n")}`;
16118
16213
  steps.push(passResult);
16119
16214
  const beforeUrl = wc.getURL();
16120
16215
  if (args.submit_selector) {
16121
- await clickResolvedSelector$1(wc, args.submit_selector);
16216
+ await clickResolvedSelector(wc, args.submit_selector);
16122
16217
  } else {
16123
16218
  const clicked = await executePageScript(
16124
16219
  wc,
@@ -16157,7 +16252,7 @@ ${steps.join("\n")}`;
16157
16252
  if (!wc) return "Error: No active tab";
16158
16253
  const beforeUrl = wc.getURL();
16159
16254
  if (args.selector) {
16160
- return clickResolvedSelector$1(wc, args.selector);
16255
+ return clickResolvedSelector(wc, args.selector);
16161
16256
  }
16162
16257
  const isNext = args.direction === "next";
16163
16258
  const clicked = await executePageScript(
@@ -17282,6 +17377,7 @@ const ALGORITHM = "aes-256-gcm";
17282
17377
  const IV_LENGTH = 12;
17283
17378
  const AUTH_TAG_LENGTH = 16;
17284
17379
  let cachedEntries = null;
17380
+ const logger$9 = createLogger("Vault");
17285
17381
  function getVaultDir() {
17286
17382
  return electron.app.getPath("userData");
17287
17383
  }
@@ -17348,7 +17444,7 @@ function loadVault() {
17348
17444
  cachedEntries = JSON.parse(json);
17349
17445
  return cachedEntries;
17350
17446
  } catch (err) {
17351
- console.error("[Vessel Vault] Failed to load vault:", err);
17447
+ logger$9.error("Failed to load vault:", err);
17352
17448
  throw new Error(
17353
17449
  "Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
17354
17450
  );
@@ -17490,6 +17586,7 @@ async function requestConsent(request) {
17490
17586
  }
17491
17587
  const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
17492
17588
  const MAX_ENTRIES = 1e3;
17589
+ const logger$8 = createLogger("VaultAudit");
17493
17590
  function getAuditPath() {
17494
17591
  return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
17495
17592
  }
@@ -17499,7 +17596,7 @@ function appendAuditEntry(entry) {
17499
17596
  fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
17500
17597
  fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
17501
17598
  } catch (err) {
17502
- console.error("[Vessel Vault] Failed to write audit log:", err);
17599
+ logger$8.error("Failed to write audit log:", err);
17503
17600
  }
17504
17601
  }
17505
17602
  function readAuditLog(limit = 100) {
@@ -17509,20 +17606,13 @@ function readAuditLog(limit = 100) {
17509
17606
  const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
17510
17607
  return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
17511
17608
  } catch (err) {
17512
- console.error("[Vessel Vault] Failed to read audit log:", err);
17609
+ logger$8.error("Failed to read audit log:", err);
17513
17610
  return [];
17514
17611
  }
17515
17612
  }
17516
17613
  let httpServer = null;
17517
17614
  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
- }
17615
+ const logger$7 = createLogger("MCP");
17526
17616
  const MCP_AUTH_FILENAME = "mcp-auth.json";
17527
17617
  function getMcpAuthFilePath() {
17528
17618
  const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
@@ -17558,7 +17648,7 @@ function writeMcpAuthFile(endpoint, token) {
17558
17648
  { mode: 384 }
17559
17649
  );
17560
17650
  } catch (err) {
17561
- console.warn("[Vessel MCP] Failed to write auth file:", err);
17651
+ logger$7.warn("Failed to write auth file:", err);
17562
17652
  }
17563
17653
  }
17564
17654
  function clearMcpAuthFile() {
@@ -17583,15 +17673,17 @@ function clearMcpAuthFile() {
17583
17673
  { mode: 384 }
17584
17674
  );
17585
17675
  } catch (err) {
17586
- console.warn("[Vessel MCP] Failed to clear auth file:", err);
17676
+ logger$7.warn("Failed to clear auth file:", err);
17587
17677
  }
17588
17678
  }
17589
17679
  function asTextResponse(text) {
17590
17680
  return { content: [{ type: "text", text }] };
17591
17681
  }
17592
- async function loadPermittedUrl(wc, url) {
17593
- assertPermittedNavigationURL(url);
17594
- await wc.loadURL(url);
17682
+ function asErrorTextResponse(message) {
17683
+ return asTextResponse(`Error: ${message}`);
17684
+ }
17685
+ function asNoActiveTabResponse() {
17686
+ return asErrorTextResponse("No active tab");
17595
17687
  }
17596
17688
  function asPromptResponse(text) {
17597
17689
  return {
@@ -17606,6 +17698,9 @@ function asPromptResponse(text) {
17606
17698
  ]
17607
17699
  };
17608
17700
  }
17701
+ function isDangerousMcpAction(name) {
17702
+ return name === "close_tab" || isDangerousAction(name);
17703
+ }
17609
17704
  function getActiveTabSummary(tabManager) {
17610
17705
  const activeTab = tabManager.getActiveTab();
17611
17706
  const activeTabId = tabManager.getActiveTabId();
@@ -17622,1298 +17717,162 @@ function getActiveTabSummary(tabManager) {
17622
17717
  humanFocused: true
17623
17718
  };
17624
17719
  }
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` };
17720
+ async function getPostActionState(tabManager, name) {
17721
+ const tab = tabManager.getActiveTab();
17722
+ if (!tab) return "";
17723
+ const wc = tab.view.webContents;
17724
+ const navActions = [
17725
+ "navigate",
17726
+ "go_back",
17727
+ "go_forward",
17728
+ "click",
17729
+ "submit_form",
17730
+ "reload",
17731
+ "press_key"
17732
+ ];
17733
+ const interactActions = [
17734
+ "type",
17735
+ "type_text",
17736
+ "select_option",
17737
+ "hover",
17738
+ "focus"
17739
+ ];
17740
+ const tabActions = ["create_tab", "switch_tab", "close_tab"];
17741
+ if (navActions.includes(name)) {
17742
+ let warning = "";
17743
+ try {
17744
+ const page = await extractContent(wc);
17745
+ const issue = getRecoverableAccessIssue(page);
17746
+ if (issue) {
17747
+ const blockedUrl = wc.getURL();
17748
+ const canRecover = [
17749
+ "navigate",
17750
+ "open_bookmark",
17751
+ "click",
17752
+ "submit_form",
17753
+ "reload",
17754
+ "press_key"
17755
+ ].includes(name) && tab.canGoBack();
17756
+ if (canRecover && tab.goBack()) {
17757
+ await waitForLoad(wc);
17758
+ warning = `
17759
+ [warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
17760
+ } else {
17761
+ warning = `
17762
+ [warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
17763
+ }
17764
+ }
17765
+ } catch (err) {
17766
+ logger$7.warn("Failed to compute post-action state warning:", err);
17649
17767
  }
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
- };
17768
+ return `${warning}
17769
+ [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
17658
17770
  }
17659
- const existing = findFolderByName(requestedName);
17660
- if (existing) {
17661
- return { folderId: existing.id, folderName: existing.name };
17771
+ if (interactActions.includes(name)) {
17772
+ return `
17773
+ [state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
17662
17774
  }
17663
- const createIfMissing = args.create_folder_if_missing !== false;
17664
- if (!createIfMissing) {
17665
- return {
17666
- folderName: requestedName,
17667
- error: `Folder "${requestedName}" not found`
17668
- };
17775
+ if (tabActions.includes(name)) {
17776
+ const activeId = tabManager.getActiveTabId();
17777
+ const active = getActiveTabSummary(tabManager);
17778
+ const count = tabManager.getAllStates().length;
17779
+ return `
17780
+ [state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
17669
17781
  }
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
- `);
17782
+ return "";
18677
17783
  }
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}`;
17784
+ async function withAction(runtime2, tabManager, name, args, executor) {
17785
+ try {
17786
+ const result = await runtime2.runControlledAction({
17787
+ source: "mcp",
17788
+ name,
17789
+ args,
17790
+ tabId: tabManager.getActiveTabId(),
17791
+ dangerous: isDangerousMcpAction(name),
17792
+ executor
17793
+ });
17794
+ const stateInfo = await getPostActionState(tabManager, name);
17795
+ const flowCtx = runtime2.getFlowContext();
17796
+ return asTextResponse(result + stateInfo + flowCtx);
17797
+ } catch (error) {
17798
+ return asErrorTextResponse(getErrorMessage(error));
18805
17799
  }
18806
- return "Submitted form";
18807
- }
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
- `);
18844
17800
  }
18845
- async function waitForCondition(wc, text, selector, timeoutMs) {
17801
+ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
18846
17802
  const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
18847
17803
  const expectedText = (text || "").trim();
18848
17804
  const expectedSelector = (selector || "").trim();
18849
- if (!expectedText && !expectedSelector) {
17805
+ const startedAt = Date.now();
17806
+ const result = await waitForConditionDirect(
17807
+ wc,
17808
+ expectedText,
17809
+ expectedSelector,
17810
+ effectiveTimeout
17811
+ );
17812
+ const elapsedMs = Date.now() - startedAt;
17813
+ if (result === "Error: wait_for requires text or selector") {
18850
17814
  return JSON.stringify({
18851
17815
  matched: false,
18852
17816
  error: "wait_for requires text or selector"
18853
17817
  });
18854
17818
  }
18855
- if (wc.isLoading()) {
18856
- await waitForLoad(wc, Math.min(effectiveTimeout, 5e3));
17819
+ if (result.startsWith("Error: Invalid selector ")) {
17820
+ return JSON.stringify({
17821
+ matched: false,
17822
+ error: result.slice("Error: ".length)
17823
+ });
18857
17824
  }
18858
- const startedAt = Date.now();
18859
- while (Date.now() - startedAt < effectiveTimeout) {
18860
- const result = await wc.executeJavaScript(`
17825
+ if (result.startsWith("Error: Page is still busy; wait_for timed out")) {
17826
+ return JSON.stringify({
17827
+ matched: false,
17828
+ error: result.slice("Error: ".length),
17829
+ elapsed_ms: elapsedMs,
17830
+ timeout_ms: effectiveTimeout
17831
+ });
17832
+ }
17833
+ if (expectedSelector && result === `Matched selector ${expectedSelector}`) {
17834
+ return JSON.stringify({
17835
+ matched: true,
17836
+ type: "selector",
17837
+ value: expectedSelector,
17838
+ elapsed_ms: elapsedMs
17839
+ });
17840
+ }
17841
+ const matchedTextPrefix = 'Matched text "';
17842
+ if (result.startsWith(matchedTextPrefix) && result.endsWith('"')) {
17843
+ return JSON.stringify({
17844
+ matched: true,
17845
+ type: "text",
17846
+ value: result.slice(matchedTextPrefix.length, -1),
17847
+ elapsed_ms: elapsedMs
17848
+ });
17849
+ }
17850
+ const timeoutPayload = {
17851
+ matched: false,
17852
+ type: expectedSelector ? "selector" : "text",
17853
+ value: expectedSelector || expectedText.slice(0, 80),
17854
+ elapsed_ms: elapsedMs,
17855
+ timeout_ms: effectiveTimeout
17856
+ };
17857
+ if (expectedSelector) {
17858
+ const diagnostic = await wc.executeJavaScript(`
18861
17859
  (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
- }
17860
+ try {
17861
+ var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
17862
+ return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
17863
+ } catch (e) {
17864
+ return 'selector error: ' + e.message;
18870
17865
  }
18871
- if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
18872
- return '';
18873
17866
  })()
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
- });
17867
+ `).catch((err) => {
17868
+ logger$7.warn("Failed to gather wait_for timeout diagnostic:", err);
17869
+ return null;
17870
+ });
17871
+ if (typeof diagnostic === "string" && diagnostic.trim()) {
17872
+ timeoutPayload.diagnostic = diagnostic;
18897
17873
  }
18898
- await new Promise((resolve) => setTimeout(resolve, 150));
18899
17874
  }
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
- });
17875
+ return JSON.stringify(timeoutPayload);
18917
17876
  }
18918
17877
  function registerTools(server, tabManager, runtime2) {
18919
17878
  server.registerPrompt(
@@ -18992,7 +17951,8 @@ function registerTools(server, tabManager, runtime2) {
18992
17951
  pageTitle = wc.getTitle();
18993
17952
  const page = await extractContent(wc);
18994
17953
  pageType = detectPageType(page);
18995
- } catch {
17954
+ } catch (err) {
17955
+ logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
18996
17956
  }
18997
17957
  }
18998
17958
  const scored = TOOL_DEFINITIONS.map((def) => {
@@ -19044,7 +18004,7 @@ function registerTools(server, tabManager, runtime2) {
19044
18004
  },
19045
18005
  async () => {
19046
18006
  const activeTab = getActiveTabSummary(tabManager);
19047
- if (!activeTab) return asTextResponse("Error: No active tab");
18007
+ if (!activeTab) return asNoActiveTabResponse();
19048
18008
  return asTextResponse(JSON.stringify(activeTab, null, 2));
19049
18009
  }
19050
18010
  );
@@ -19153,7 +18113,7 @@ ${buildScopedContext(pageContent, mode)}`;
19153
18113
  },
19154
18114
  async ({ mode }) => {
19155
18115
  const tab = tabManager.getActiveTab();
19156
- if (!tab) return asTextResponse("Error: No active tab");
18116
+ if (!tab) return asNoActiveTabResponse();
19157
18117
  try {
19158
18118
  const pageContent = await extractContent(tab.view.webContents);
19159
18119
  const effectiveMode = mode || "full";
@@ -19185,7 +18145,7 @@ ${buildScopedContext(pageContent, mode)}`;
19185
18145
  },
19186
18146
  async ({ mode }) => {
19187
18147
  const tab = tabManager.getActiveTab();
19188
- if (!tab) return asTextResponse("Error: No active tab");
18148
+ if (!tab) return asNoActiveTabResponse();
19189
18149
  try {
19190
18150
  const pageContent = await extractContent(tab.view.webContents);
19191
18151
  const effectiveMode = mode || "full";
@@ -19234,7 +18194,7 @@ ${buildScopedContext(pageContent, mode)}`;
19234
18194
  },
19235
18195
  async ({ url, postBody }) => {
19236
18196
  const tab = tabManager.getActiveTab();
19237
- if (!tab) return asTextResponse("Error: No active tab");
18197
+ if (!tab) return asNoActiveTabResponse();
19238
18198
  const preCheck = await validateLinkDestination(url);
19239
18199
  if (preCheck.status === "dead") {
19240
18200
  return asTextResponse(
@@ -19276,7 +18236,7 @@ ${buildScopedContext(pageContent, mode)}`;
19276
18236
  async ({ enabled, tabId, match, reload }) => {
19277
18237
  const activeTab = tabManager.getActiveTab();
19278
18238
  if (!activeTab && !tabId && !match) {
19279
- return asTextResponse("Error: No active tab");
18239
+ return asNoActiveTabResponse();
19280
18240
  }
19281
18241
  return withAction(
19282
18242
  runtime2,
@@ -19319,7 +18279,7 @@ ${buildScopedContext(pageContent, mode)}`;
19319
18279
  },
19320
18280
  async ({ type }) => {
19321
18281
  const tab = tabManager.getActiveTab();
19322
- if (!tab) return asTextResponse("Error: No active tab");
18282
+ if (!tab) return asNoActiveTabResponse();
19323
18283
  try {
19324
18284
  const pageContent = await extractContent(tab.view.webContents);
19325
18285
  const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
@@ -19367,7 +18327,7 @@ ${buildScopedContext(pageContent, mode)}`;
19367
18327
  },
19368
18328
  async () => {
19369
18329
  const tab = tabManager.getActiveTab();
19370
- if (!tab) return asTextResponse("Error: No active tab");
18330
+ if (!tab) return asNoActiveTabResponse();
19371
18331
  return withAction(runtime2, tabManager, "go_back", {}, async () => {
19372
18332
  if (!tab.canGoBack()) {
19373
18333
  return "No previous page in history";
@@ -19388,7 +18348,7 @@ ${buildScopedContext(pageContent, mode)}`;
19388
18348
  },
19389
18349
  async () => {
19390
18350
  const tab = tabManager.getActiveTab();
19391
- if (!tab) return asTextResponse("Error: No active tab");
18351
+ if (!tab) return asNoActiveTabResponse();
19392
18352
  return withAction(runtime2, tabManager, "go_forward", {}, async () => {
19393
18353
  if (!tab.canGoForward()) {
19394
18354
  return "No forward page in history";
@@ -19409,7 +18369,7 @@ ${buildScopedContext(pageContent, mode)}`;
19409
18369
  },
19410
18370
  async () => {
19411
18371
  const tab = tabManager.getActiveTab();
19412
- if (!tab) return asTextResponse("Error: No active tab");
18372
+ if (!tab) return asNoActiveTabResponse();
19413
18373
  return withAction(runtime2, tabManager, "reload", {}, async () => {
19414
18374
  tabManager.reloadTab(tabManager.getActiveTabId());
19415
18375
  await waitForLoad(tab.view.webContents);
@@ -19429,7 +18389,7 @@ ${buildScopedContext(pageContent, mode)}`;
19429
18389
  },
19430
18390
  async ({ index, selector }) => {
19431
18391
  const tab = tabManager.getActiveTab();
19432
- if (!tab) return asTextResponse("Error: No active tab");
18392
+ if (!tab) return asNoActiveTabResponse();
19433
18393
  return withAction(
19434
18394
  runtime2,
19435
18395
  tabManager,
@@ -19458,7 +18418,7 @@ ${buildScopedContext(pageContent, mode)}`;
19458
18418
  },
19459
18419
  async ({ index, selector }) => {
19460
18420
  const tab = tabManager.getActiveTab();
19461
- if (!tab) return asTextResponse("Error: No active tab");
18421
+ if (!tab) return asNoActiveTabResponse();
19462
18422
  return withAction(
19463
18423
  runtime2,
19464
18424
  tabManager,
@@ -19487,7 +18447,7 @@ ${buildScopedContext(pageContent, mode)}`;
19487
18447
  },
19488
18448
  async ({ index, selector }) => {
19489
18449
  const tab = tabManager.getActiveTab();
19490
- if (!tab) return asTextResponse("Error: No active tab");
18450
+ if (!tab) return asNoActiveTabResponse();
19491
18451
  return withAction(
19492
18452
  runtime2,
19493
18453
  tabManager,
@@ -19516,11 +18476,11 @@ ${buildScopedContext(pageContent, mode)}`;
19516
18476
  },
19517
18477
  async ({ index, selector }) => {
19518
18478
  const tab = tabManager.getActiveTab();
19519
- if (!tab) return asTextResponse("Error: No active tab");
18479
+ if (!tab) return asNoActiveTabResponse();
19520
18480
  const wc = tab.view.webContents;
19521
18481
  const resolvedSelector = await resolveSelector(wc, index, selector);
19522
18482
  if (!resolvedSelector) {
19523
- return asTextResponse("Error: No index or selector provided");
18483
+ return asErrorTextResponse("No index or selector provided");
19524
18484
  }
19525
18485
  const result = await wc.executeJavaScript(`
19526
18486
  (function() {
@@ -19570,7 +18530,7 @@ ${buildScopedContext(pageContent, mode)}`;
19570
18530
  );
19571
18531
  }
19572
18532
  if ("error" in result && typeof result.error === "string") {
19573
- return asTextResponse(`Error: ${result.error}`);
18533
+ return asErrorTextResponse(result.error);
19574
18534
  }
19575
18535
  const parts = [`<${result.tag}>`];
19576
18536
  if ("role" in result && typeof result.role === "string" && result.role.trim()) {
@@ -19601,7 +18561,7 @@ ${buildScopedContext(pageContent, mode)}`;
19601
18561
  },
19602
18562
  async ({ index, selector, text, mode }) => {
19603
18563
  const tab = tabManager.getActiveTab();
19604
- if (!tab) return asTextResponse("Error: No active tab");
18564
+ if (!tab) return asNoActiveTabResponse();
19605
18565
  return withAction(
19606
18566
  runtime2,
19607
18567
  tabManager,
@@ -19640,7 +18600,7 @@ ${buildScopedContext(pageContent, mode)}`;
19640
18600
  },
19641
18601
  async ({ index, selector, text, mode }) => {
19642
18602
  const tab = tabManager.getActiveTab();
19643
- if (!tab) return asTextResponse("Error: No active tab");
18603
+ if (!tab) return asNoActiveTabResponse();
19644
18604
  return withAction(
19645
18605
  runtime2,
19646
18606
  tabManager,
@@ -19677,13 +18637,13 @@ ${buildScopedContext(pageContent, mode)}`;
19677
18637
  },
19678
18638
  async ({ index, selector, label, value }) => {
19679
18639
  const tab = tabManager.getActiveTab();
19680
- if (!tab) return asTextResponse("Error: No active tab");
18640
+ if (!tab) return asNoActiveTabResponse();
19681
18641
  return withAction(
19682
18642
  runtime2,
19683
18643
  tabManager,
19684
18644
  "select_option",
19685
18645
  { index, selector, label, value },
19686
- async () => selectOption(tab.view.webContents, index, selector, label, value)
18646
+ async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
19687
18647
  );
19688
18648
  }
19689
18649
  );
@@ -19699,23 +18659,13 @@ ${buildScopedContext(pageContent, mode)}`;
19699
18659
  },
19700
18660
  async ({ index, selector }) => {
19701
18661
  const tab = tabManager.getActiveTab();
19702
- if (!tab) return asTextResponse("Error: No active tab");
18662
+ if (!tab) return asNoActiveTabResponse();
19703
18663
  return withAction(
19704
18664
  runtime2,
19705
18665
  tabManager,
19706
18666
  "submit_form",
19707
18667
  { 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
- }
18668
+ async () => submitFormDirect(tab.view.webContents, index, selector)
19719
18669
  );
19720
18670
  }
19721
18671
  );
@@ -19732,7 +18682,7 @@ ${buildScopedContext(pageContent, mode)}`;
19732
18682
  },
19733
18683
  async ({ key, index, selector }) => {
19734
18684
  const tab = tabManager.getActiveTab();
19735
- if (!tab) return asTextResponse("Error: No active tab");
18685
+ if (!tab) return asNoActiveTabResponse();
19736
18686
  return withAction(
19737
18687
  runtime2,
19738
18688
  tabManager,
@@ -19741,7 +18691,7 @@ ${buildScopedContext(pageContent, mode)}`;
19741
18691
  async () => {
19742
18692
  const wc = tab.view.webContents;
19743
18693
  const beforeUrl = wc.getURL();
19744
- const result = await pressKey(wc, key, index, selector);
18694
+ const result = await pressKeyDirect(wc, key, index, selector);
19745
18695
  if (key === "Enter") {
19746
18696
  await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
19747
18697
  const afterUrl = wc.getURL();
@@ -19768,7 +18718,7 @@ ${buildScopedContext(pageContent, mode)}`;
19768
18718
  },
19769
18719
  async ({ direction, amount }) => {
19770
18720
  const tab = tabManager.getActiveTab();
19771
- if (!tab) return asTextResponse("Error: No active tab");
18721
+ if (!tab) return asNoActiveTabResponse();
19772
18722
  return withAction(
19773
18723
  runtime2,
19774
18724
  tabManager,
@@ -19791,7 +18741,7 @@ ${buildScopedContext(pageContent, mode)}`;
19791
18741
  },
19792
18742
  async () => {
19793
18743
  const tab = tabManager.getActiveTab();
19794
- if (!tab) return asTextResponse("Error: No active tab");
18744
+ if (!tab) return asNoActiveTabResponse();
19795
18745
  return withAction(
19796
18746
  runtime2,
19797
18747
  tabManager,
@@ -19814,7 +18764,7 @@ ${buildScopedContext(pageContent, mode)}`;
19814
18764
  },
19815
18765
  async ({ strategy }) => {
19816
18766
  const tab = tabManager.getActiveTab();
19817
- if (!tab) return asTextResponse("Error: No active tab");
18767
+ if (!tab) return asNoActiveTabResponse();
19818
18768
  return withAction(
19819
18769
  runtime2,
19820
18770
  tabManager,
@@ -19840,13 +18790,13 @@ ${buildScopedContext(pageContent, mode)}`;
19840
18790
  },
19841
18791
  async ({ text, selector, timeoutMs }) => {
19842
18792
  const tab = tabManager.getActiveTab();
19843
- if (!tab) return asTextResponse("Error: No active tab");
18793
+ if (!tab) return asNoActiveTabResponse();
19844
18794
  return withAction(
19845
18795
  runtime2,
19846
18796
  tabManager,
19847
18797
  "wait_for",
19848
18798
  { text, selector, timeoutMs },
19849
- async () => waitForCondition(tab.view.webContents, text, selector, timeoutMs)
18799
+ async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
19850
18800
  );
19851
18801
  }
19852
18802
  );
@@ -20027,7 +18977,7 @@ ${buildScopedContext(pageContent, mode)}`;
20027
18977
  },
20028
18978
  async () => {
20029
18979
  const tab = tabManager.getActiveTab();
20030
- if (!tab) return asTextResponse("Error: No active tab");
18980
+ if (!tab) return asNoActiveTabResponse();
20031
18981
  try {
20032
18982
  const bounds = tab.view.getBounds();
20033
18983
  if (bounds.width <= 0 || bounds.height <= 0) {
@@ -20096,7 +19046,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20096
19046
  },
20097
19047
  async ({ index, selector, text, label, durationMs, persist, color }) => {
20098
19048
  const tab = tabManager.getActiveTab();
20099
- if (!tab) return asTextResponse("Error: No active tab");
19049
+ if (!tab) return asNoActiveTabResponse();
20100
19050
  const normalizedText = normalizeLooseString(text);
20101
19051
  return withAction(
20102
19052
  runtime2,
@@ -20146,7 +19096,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20146
19096
  },
20147
19097
  async () => {
20148
19098
  const tab = tabManager.getActiveTab();
20149
- if (!tab) return asTextResponse("Error: No active tab");
19099
+ if (!tab) return asNoActiveTabResponse();
20150
19100
  return withAction(
20151
19101
  runtime2,
20152
19102
  tabManager,
@@ -20285,8 +19235,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20285
19235
  h.label,
20286
19236
  void 0,
20287
19237
  h.color
20288
- ).catch(() => {
20289
- });
19238
+ ).catch(
19239
+ (err) => logger$7.warn("Failed to restore highlight after removal:", err)
19240
+ );
20290
19241
  }
20291
19242
  }
20292
19243
  return asTextResponse(`Removed highlight ${id}`);
@@ -20426,7 +19377,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20426
19377
  note,
20427
19378
  {
20428
19379
  onDuplicate: on_duplicate ?? "ask",
20429
- extra: getBookmarkMetadataFromToolArgs({
19380
+ extra: getBookmarkMetadataFromArgs({
20430
19381
  intent,
20431
19382
  expected_content,
20432
19383
  key_fields,
@@ -20570,7 +19521,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20570
19521
  folderId: target.folderId,
20571
19522
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
20572
19523
  note,
20573
- ...getBookmarkMetadataFromToolArgs(args)
19524
+ ...getBookmarkMetadataFromArgs(args)
20574
19525
  });
20575
19526
  if (!updated) {
20576
19527
  return `Bookmark ${existing.id} not found`;
@@ -20588,7 +19539,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20588
19539
  note,
20589
19540
  {
20590
19541
  onDuplicate: "update",
20591
- extra: getBookmarkMetadataFromToolArgs(args)
19542
+ extra: getBookmarkMetadataFromArgs(args)
20592
19543
  }
20593
19544
  );
20594
19545
  const bookmark = result.bookmark;
@@ -20971,7 +19922,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20971
19922
  },
20972
19923
  async ({ title, folder, summary, note, tags }) => {
20973
19924
  const tab = tabManager.getActiveTab();
20974
- if (!tab) return asTextResponse("Error: No active tab");
19925
+ if (!tab) return asNoActiveTabResponse();
20975
19926
  return withAction(
20976
19927
  runtime2,
20977
19928
  tabManager,
@@ -21115,7 +20066,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21115
20066
  let page;
21116
20067
  try {
21117
20068
  page = await extractContent(wc);
21118
- } catch {
20069
+ } catch (err) {
20070
+ logger$7.warn("Failed to extract page while generating suggestions:", err);
21119
20071
  return asTextResponse(
21120
20072
  "Could not read page. Try navigate to a working URL."
21121
20073
  );
@@ -21222,7 +20174,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21222
20174
  },
21223
20175
  async ({ fields, submit }) => {
21224
20176
  const tab = tabManager.getActiveTab();
21225
- if (!tab) return asTextResponse("Error: No active tab");
20177
+ if (!tab) return asNoActiveTabResponse();
21226
20178
  return withAction(
21227
20179
  runtime2,
21228
20180
  tabManager,
@@ -21236,7 +20188,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21236
20188
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
21237
20189
  if (firstSel) {
21238
20190
  const beforeUrl = wc.getURL();
21239
- const submitResult = await submitForm(wc, void 0, firstSel);
20191
+ const submitResult = await submitFormDirect(wc, void 0, firstSel);
21240
20192
  await waitForPotentialNavigation$1(wc, beforeUrl);
21241
20193
  const afterUrl = wc.getURL();
21242
20194
  results.push(
@@ -21279,7 +20231,7 @@ ${results.join("\n")}`;
21279
20231
  submit_selector
21280
20232
  }) => {
21281
20233
  const tab = tabManager.getActiveTab();
21282
- if (!tab) return asTextResponse("Error: No active tab");
20234
+ if (!tab) return asNoActiveTabResponse();
21283
20235
  return withAction(
21284
20236
  runtime2,
21285
20237
  tabManager,
@@ -21353,7 +20305,7 @@ ${steps.join("\n")}`;
21353
20305
  },
21354
20306
  async ({ query, selector }) => {
21355
20307
  const tab = tabManager.getActiveTab();
21356
- if (!tab) return asTextResponse("Error: No active tab");
20308
+ if (!tab) return asNoActiveTabResponse();
21357
20309
  const qLower = query.toLowerCase().trim();
21358
20310
  const buttonLabels = [
21359
20311
  "add to cart",
@@ -21438,7 +20390,7 @@ ${steps.join("\n")}`;
21438
20390
  },
21439
20391
  async ({ direction, selector }) => {
21440
20392
  const tab = tabManager.getActiveTab();
21441
- if (!tab) return asTextResponse("Error: No active tab");
20393
+ if (!tab) return asNoActiveTabResponse();
21442
20394
  return withAction(
21443
20395
  runtime2,
21444
20396
  tabManager,
@@ -21489,7 +20441,7 @@ ${steps.join("\n")}`;
21489
20441
  },
21490
20442
  async () => {
21491
20443
  const tab = tabManager.getActiveTab();
21492
- if (!tab) return asTextResponse("Error: No active tab");
20444
+ if (!tab) return asNoActiveTabResponse();
21493
20445
  return withAction(
21494
20446
  runtime2,
21495
20447
  tabManager,
@@ -21547,7 +20499,7 @@ ${steps.join("\n")}`;
21547
20499
  },
21548
20500
  async ({ index, selector: rawSelector }) => {
21549
20501
  const tab = tabManager.getActiveTab();
21550
- if (!tab) return asTextResponse("Error: No active tab");
20502
+ if (!tab) return asNoActiveTabResponse();
21551
20503
  return withAction(
21552
20504
  runtime2,
21553
20505
  tabManager,
@@ -21602,7 +20554,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21602
20554
  },
21603
20555
  async ({ index, selector: rawSelector, position }) => {
21604
20556
  const tab = tabManager.getActiveTab();
21605
- if (!tab) return asTextResponse("Error: No active tab");
20557
+ if (!tab) return asNoActiveTabResponse();
21606
20558
  return withAction(
21607
20559
  runtime2,
21608
20560
  tabManager,
@@ -21660,7 +20612,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21660
20612
  },
21661
20613
  async ({ timeoutMs }) => {
21662
20614
  const tab = tabManager.getActiveTab();
21663
- if (!tab) return asTextResponse("Error: No active tab");
20615
+ if (!tab) return asNoActiveTabResponse();
21664
20616
  return withAction(
21665
20617
  runtime2,
21666
20618
  tabManager,
@@ -21720,11 +20672,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
21720
20672
  let targetDomain = domain;
21721
20673
  if (!targetDomain) {
21722
20674
  const tab = tabManager.getActiveTab();
21723
- if (!tab) return asTextResponse("Error: No active tab and no domain specified");
20675
+ if (!tab) return asErrorTextResponse("No active tab and no domain specified");
21724
20676
  try {
21725
20677
  targetDomain = new URL(tab.state.url).hostname;
21726
- } catch {
21727
- return asTextResponse("Error: Could not parse active tab URL");
20678
+ } catch (err) {
20679
+ logger$7.warn("Failed to parse active tab URL for vault_status:", err);
20680
+ return asErrorTextResponse("Could not parse active tab URL");
21728
20681
  }
21729
20682
  }
21730
20683
  const matches = findEntriesForDomain(
@@ -21783,13 +20736,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21783
20736
  submit_index
21784
20737
  }) => {
21785
20738
  const tab = tabManager.getActiveTab();
21786
- if (!tab) return asTextResponse("Error: No active tab");
20739
+ if (!tab) return asNoActiveTabResponse();
21787
20740
  const wc = tab.view.webContents;
21788
20741
  let hostname;
21789
20742
  try {
21790
20743
  hostname = new URL(tab.state.url).hostname;
21791
- } catch {
21792
- return asTextResponse("Error: Could not parse active tab URL");
20744
+ } catch (err) {
20745
+ logger$7.warn("Failed to parse active tab URL for vault_login:", err);
20746
+ return asErrorTextResponse("Could not parse active tab URL");
21793
20747
  }
21794
20748
  const matches = findEntriesForDomain(`https://${hostname}`);
21795
20749
  if (matches.length === 0) {
@@ -21825,7 +20779,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21825
20779
  }
21826
20780
  const creds = getCredential(match.id);
21827
20781
  if (!creds) {
21828
- return asTextResponse("Error: Credential not found in vault");
20782
+ return asErrorTextResponse("Credential not found in vault");
21829
20783
  }
21830
20784
  const results = [];
21831
20785
  if (username_index != null) {
@@ -21876,13 +20830,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21876
20830
  },
21877
20831
  async ({ credential_label, code_index, submit_after, submit_index }) => {
21878
20832
  const tab = tabManager.getActiveTab();
21879
- if (!tab) return asTextResponse("Error: No active tab");
20833
+ if (!tab) return asNoActiveTabResponse();
21880
20834
  const wc = tab.view.webContents;
21881
20835
  let hostname;
21882
20836
  try {
21883
20837
  hostname = new URL(tab.state.url).hostname;
21884
- } catch {
21885
- return asTextResponse("Error: Could not parse active tab URL");
20838
+ } catch (err) {
20839
+ logger$7.warn("Failed to parse active tab URL for vault_totp:", err);
20840
+ return asErrorTextResponse("Could not parse active tab URL");
21886
20841
  }
21887
20842
  const matches = findEntriesForDomain(`https://${hostname}`);
21888
20843
  const match = credential_label ? matches.find(
@@ -22053,7 +21008,7 @@ function startMcpServer(tabManager, runtime2, port) {
22053
21008
  await mcpServer.connect(transport);
22054
21009
  await transport.handleRequest(req, res);
22055
21010
  } catch (error) {
22056
- console.error("[Vessel MCP] Error handling request:", error);
21011
+ logger$7.error("Error handling request:", error);
22057
21012
  if (!res.headersSent) {
22058
21013
  res.writeHead(500, { "Content-Type": "application/json" });
22059
21014
  res.end(
@@ -22072,7 +21027,7 @@ function startMcpServer(tabManager, runtime2, port) {
22072
21027
  };
22073
21028
  server.once("error", (error) => {
22074
21029
  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);
21030
+ logger$7.error("Server error:", error);
22076
21031
  clearMcpAuthFile();
22077
21032
  setMcpHealth({
22078
21033
  configuredPort: port,
@@ -22084,14 +21039,12 @@ function startMcpServer(tabManager, runtime2, port) {
22084
21039
  if (httpServer === server) {
22085
21040
  httpServer = null;
22086
21041
  }
22087
- finish({
22088
- ok: false,
21042
+ finish(errorResult(message, {
22089
21043
  configuredPort: port,
22090
21044
  activePort: null,
22091
21045
  endpoint: null,
22092
- authToken: null,
22093
- error: message
22094
- });
21046
+ authToken: null
21047
+ }));
22095
21048
  });
22096
21049
  server.listen(port, "127.0.0.1", () => {
22097
21050
  httpServer = server;
@@ -22106,7 +21059,7 @@ function startMcpServer(tabManager, runtime2, port) {
22106
21059
  message: `MCP server listening on ${endpoint}.`
22107
21060
  });
22108
21061
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22109
- console.log(`[Vessel MCP] Server listening on ${endpoint} (auth enabled)`);
21062
+ logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
22110
21063
  }
22111
21064
  if (mcpAuthToken) {
22112
21065
  writeMcpAuthFile(endpoint, mcpAuthToken);
@@ -22145,7 +21098,7 @@ function stopMcpServer() {
22145
21098
  message: "MCP server is stopped."
22146
21099
  });
22147
21100
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22148
- console.log("[Vessel MCP] Server stopped");
21101
+ logger$7.info("Server stopped");
22149
21102
  }
22150
21103
  resolve();
22151
21104
  });
@@ -22166,6 +21119,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
22166
21119
  function isSafeAutomationKitId(id) {
22167
21120
  return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
22168
21121
  }
21122
+ const logger$6 = createLogger("KitRegistry");
22169
21123
  function getUserKitsDir() {
22170
21124
  return path$1.join(electron.app.getPath("userData"), "kits");
22171
21125
  }
@@ -22203,10 +21157,10 @@ function getInstalledKits() {
22203
21157
  if (isValidKit(parsed)) {
22204
21158
  kits.push(parsed);
22205
21159
  } else {
22206
- console.warn(`[kit-registry] Skipping invalid kit file: ${file}`);
21160
+ logger$6.warn(`Skipping invalid kit file: ${file}`);
22207
21161
  }
22208
- } catch {
22209
- console.warn(`[kit-registry] Failed to read kit file: ${file}`);
21162
+ } catch (err) {
21163
+ logger$6.warn(`Failed to read kit file: ${file}`, err);
22210
21164
  }
22211
21165
  }
22212
21166
  return kits;
@@ -22218,69 +21172,67 @@ async function installKitFromFile() {
22218
21172
  properties: ["openFile"]
22219
21173
  });
22220
21174
  if (canceled || filePaths.length === 0) {
22221
- return { ok: false, error: "canceled" };
21175
+ return errorResult("canceled");
22222
21176
  }
22223
21177
  let raw;
22224
21178
  try {
22225
21179
  raw = fs$1.readFileSync(filePaths[0], "utf-8");
22226
21180
  } catch {
22227
- return { ok: false, error: "Could not read the selected file." };
21181
+ return errorResult("Could not read the selected file.");
22228
21182
  }
22229
21183
  let parsed;
22230
21184
  try {
22231
21185
  parsed = JSON.parse(raw);
22232
21186
  } catch {
22233
- return { ok: false, error: "File is not valid JSON." };
21187
+ return errorResult("File is not valid JSON.");
22234
21188
  }
22235
21189
  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
- };
21190
+ return errorResult(
21191
+ "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
21192
+ );
22240
21193
  }
22241
21194
  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
- };
21195
+ return errorResult(
21196
+ `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
21197
+ );
22246
21198
  }
22247
21199
  ensureKitsDir();
22248
21200
  const dest = getKitFilePath(parsed.id);
22249
21201
  if (!dest) {
22250
- return { ok: false, error: "Kit id contains unsupported characters." };
21202
+ return errorResult("Kit id contains unsupported characters.");
22251
21203
  }
22252
21204
  try {
22253
21205
  fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
22254
21206
  } catch {
22255
- return { ok: false, error: "Failed to save the kit file." };
21207
+ return errorResult("Failed to save the kit file.");
22256
21208
  }
22257
- return { ok: true, kit: parsed };
21209
+ return okResult({ kit: parsed });
22258
21210
  }
22259
21211
  function uninstallKit(id, scheduledKitIds) {
22260
21212
  if (BUNDLED_KIT_IDS.has(id)) {
22261
- return { ok: false, error: "Built-in kits cannot be removed." };
21213
+ return errorResult("Built-in kits cannot be removed.");
22262
21214
  }
22263
21215
  if (scheduledKitIds?.has(id)) {
22264
- return {
22265
- ok: false,
22266
- error: "This kit has active scheduled jobs. Delete or reassign them first."
22267
- };
21216
+ return errorResult(
21217
+ "This kit has active scheduled jobs. Delete or reassign them first."
21218
+ );
22268
21219
  }
22269
21220
  ensureKitsDir();
22270
21221
  const target = getKitFilePath(id);
22271
21222
  if (!target) {
22272
- return { ok: false, error: "Kit id contains unsupported characters." };
21223
+ return errorResult("Kit id contains unsupported characters.");
22273
21224
  }
22274
21225
  if (!fs$1.existsSync(target)) {
22275
- return { ok: false, error: "Kit not found." };
21226
+ return errorResult("Kit not found.");
22276
21227
  }
22277
21228
  try {
22278
21229
  fs$1.unlinkSync(target);
22279
- return { ok: true };
21230
+ return okResult();
22280
21231
  } catch {
22281
- return { ok: false, error: "Failed to remove the kit file." };
21232
+ return errorResult("Failed to remove the kit file.");
22282
21233
  }
22283
21234
  }
21235
+ const logger$5 = createLogger("Scheduler");
22284
21236
  let jobs = [];
22285
21237
  let removeIdleListener = null;
22286
21238
  let broadcastFn = null;
@@ -22305,7 +21257,7 @@ function saveJobs() {
22305
21257
  try {
22306
21258
  fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
22307
21259
  } catch (err) {
22308
- console.warn("[scheduler] Failed to save jobs:", err);
21260
+ logger$5.warn("Failed to save jobs:", err);
22309
21261
  }
22310
21262
  }
22311
21263
  function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
@@ -22427,7 +21379,7 @@ async function fireJob(job, windowState, runtime2) {
22427
21379
  };
22428
21380
  startActivity();
22429
21381
  if (!settings2.chatProvider) {
22430
- console.warn(`[scheduler] Job "${job.kitName}" skipped — no chat provider configured`);
21382
+ logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
22431
21383
  appendActivity(
22432
21384
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
22433
21385
  );
@@ -22435,7 +21387,7 @@ async function fireJob(job, windowState, runtime2) {
22435
21387
  return;
22436
21388
  }
22437
21389
  if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
22438
- console.log(`[scheduler] Firing scheduled job: ${job.kitName} (${job.id})`);
21390
+ logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
22439
21391
  }
22440
21392
  try {
22441
21393
  const provider = createProvider(settings2.chatProvider);
@@ -22488,7 +21440,7 @@ function tick(windowState, runtime2) {
22488
21440
  saveJobs();
22489
21441
  broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
22490
21442
  void fireJob(job, windowState, runtime2).catch((err) => {
22491
- console.warn("[scheduler] Unexpected error firing job:", err);
21443
+ logger$5.warn("Unexpected error firing job:", err);
22492
21444
  }).finally(fireNext);
22493
21445
  };
22494
21446
  fireNext();
@@ -23046,6 +21998,7 @@ function registerWindowControlHandlers(mainWindow) {
23046
21998
  });
23047
21999
  }
23048
22000
  let activeChatProvider = null;
22001
+ const logger$4 = createLogger("IPC");
23049
22002
  const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
23050
22003
  function registerIpcHandlers(windowState, runtime2) {
23051
22004
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
@@ -23122,7 +22075,8 @@ function registerIpcHandlers(windowState, runtime2) {
23122
22075
  let parsed;
23123
22076
  try {
23124
22077
  parsed = new URL(rawUrl);
23125
- } catch {
22078
+ } catch (err) {
22079
+ logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
23126
22080
  return;
23127
22081
  }
23128
22082
  if (parsed.origin !== premiumApiOrigin) return;
@@ -23179,7 +22133,8 @@ function registerIpcHandlers(windowState, runtime2) {
23179
22133
  if (wc.isDestroyed()) return 0;
23180
22134
  try {
23181
22135
  return await getHighlightCount(wc) ?? 0;
23182
- } catch {
22136
+ } catch (err) {
22137
+ logger$4.warn("Failed to get active highlight count:", err);
23183
22138
  return 0;
23184
22139
  }
23185
22140
  };
@@ -23287,13 +22242,13 @@ function registerIpcHandlers(windowState, runtime2) {
23287
22242
  electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
23288
22243
  try {
23289
22244
  if (!config || typeof config !== "object" || !("id" in config)) {
23290
- return { ok: false, models: [], error: "Invalid provider configuration" };
22245
+ return errorResult("Invalid provider configuration", { models: [] });
23291
22246
  }
23292
22247
  return await fetchProviderModels(
23293
22248
  config
23294
22249
  );
23295
22250
  } catch (err) {
23296
- return { ok: false, models: [], error: err instanceof Error ? err.message : "Unknown error" };
22251
+ return errorResult(getErrorMessage(err), { models: [] });
23297
22252
  }
23298
22253
  });
23299
22254
  electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
@@ -23488,12 +22443,14 @@ function registerIpcHandlers(windowState, runtime2) {
23488
22443
  const wc = activeTab.view.webContents;
23489
22444
  const result = await captureSelectionHighlight(wc);
23490
22445
  if (result.success && result.text) {
23491
- await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
23492
- });
22446
+ await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
22447
+ (err) => logger$4.warn("Failed to highlight captured selection:", err)
22448
+ );
23493
22449
  await emitHighlightCount();
23494
22450
  }
23495
22451
  return result;
23496
- } catch {
22452
+ } catch (err) {
22453
+ logger$4.warn("Failed to capture highlight from active tab:", err);
23497
22454
  return { success: false, message: "Could not capture selection" };
23498
22455
  }
23499
22456
  });
@@ -23517,7 +22474,8 @@ function registerIpcHandlers(windowState, runtime2) {
23517
22474
  chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
23518
22475
  }
23519
22476
  });
23520
- } catch {
22477
+ } catch (err) {
22478
+ logger$4.warn("Failed to persist auto-highlight selection:", err);
23521
22479
  }
23522
22480
  });
23523
22481
  electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
@@ -23530,7 +22488,8 @@ function registerIpcHandlers(windowState, runtime2) {
23530
22488
  if (wc.isDestroyed()) return false;
23531
22489
  try {
23532
22490
  return scrollToHighlight(wc, index);
23533
- } catch {
22491
+ } catch (err) {
22492
+ logger$4.warn("Failed to scroll to highlight:", err);
23534
22493
  return false;
23535
22494
  }
23536
22495
  });
@@ -23545,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
23545
22504
  await emitHighlightCount();
23546
22505
  }
23547
22506
  return removed;
23548
- } catch {
22507
+ } catch (err) {
22508
+ logger$4.warn("Failed to remove highlight at index:", err);
23549
22509
  return false;
23550
22510
  }
23551
22511
  });
@@ -23560,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
23560
22520
  await emitHighlightCount();
23561
22521
  }
23562
22522
  return cleared;
23563
- } catch {
22523
+ } catch (err) {
22524
+ logger$4.warn("Failed to clear highlight elements:", err);
23564
22525
  return false;
23565
22526
  }
23566
22527
  });
@@ -23644,7 +22605,7 @@ function registerIpcHandlers(windowState, runtime2) {
23644
22605
  electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
23645
22606
  assertString(email, "email");
23646
22607
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23647
- return { ok: false, error: "Invalid email format" };
22608
+ return errorResult("Invalid email format");
23648
22609
  }
23649
22610
  trackPremiumFunnel("activation_attempted");
23650
22611
  const result = await requestActivationCode(email);
@@ -23660,11 +22621,9 @@ function registerIpcHandlers(windowState, runtime2) {
23660
22621
  assertString(code, "code");
23661
22622
  assertString(challengeToken, "challengeToken");
23662
22623
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23663
- return {
23664
- ok: false,
23665
- state: getPremiumState(),
23666
- error: "Invalid email format"
23667
- };
22624
+ return errorResult("Invalid email format", {
22625
+ state: getPremiumState()
22626
+ });
23668
22627
  }
23669
22628
  trackPremiumFunnel("activation_attempted");
23670
22629
  const result = await verifyActivationCode(email, code, challengeToken);
@@ -24099,6 +23058,7 @@ ${lines.join("\n")}
24099
23058
  }
24100
23059
  const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
24101
23060
  const PERSIST_DEBOUNCE_MS = 500;
23061
+ const logger$3 = createLogger("Runtime");
24102
23062
  function clone(value) {
24103
23063
  return JSON.parse(JSON.stringify(value));
24104
23064
  }
@@ -24488,9 +23448,7 @@ ${progress}
24488
23448
  JSON.stringify(persisted, null, 2),
24489
23449
  "utf-8"
24490
23450
  )
24491
- ).catch(
24492
- (err) => console.error("[Vessel] Failed to persist runtime state:", err)
24493
- );
23451
+ ).catch((err) => logger$3.error("Failed to persist runtime state:", err));
24494
23452
  }
24495
23453
  schedulePersist() {
24496
23454
  this.persistDirty = true;
@@ -24755,6 +23713,7 @@ function installDownloadHandler(chromeView) {
24755
23713
  });
24756
23714
  });
24757
23715
  }
23716
+ const logger$2 = createLogger("Shortcuts");
24758
23717
  function registerHighlightShortcut(mainWindow, tabManager) {
24759
23718
  const register = () => {
24760
23719
  electron.globalShortcut.unregister("CommandOrControl+H");
@@ -24764,7 +23723,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
24764
23723
  tabManager.captureHighlightFromActiveTab();
24765
23724
  });
24766
23725
  if (!success) {
24767
- console.warn("[Vessel] Failed to register Ctrl+H shortcut");
23726
+ logger$2.warn("Failed to register Ctrl+H shortcut");
24768
23727
  }
24769
23728
  };
24770
23729
  register();
@@ -24833,6 +23792,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
24833
23792
  });
24834
23793
  }
24835
23794
  }
23795
+ const logger$1 = createLogger("Splash");
24836
23796
  function findIconBase64() {
24837
23797
  const candidates = [
24838
23798
  path$1.join(process.resourcesPath, "vessel-icon.png"),
@@ -24995,7 +23955,7 @@ function createSplashWindow() {
24995
23955
  fs$1.writeFileSync(tmpPath, html, "utf-8");
24996
23956
  void splash.loadFile(tmpPath);
24997
23957
  } catch (err) {
24998
- console.warn("[splash] Failed to write temp HTML, using fallback:", err);
23958
+ logger$1.warn("Failed to write temp HTML, using fallback:", err);
24999
23959
  void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
25000
23960
  }
25001
23961
  return splash;
@@ -25005,6 +23965,7 @@ function closeSplash(splash, delayMs = 0) {
25005
23965
  if (!splash.isDestroyed()) splash.close();
25006
23966
  }, delayMs);
25007
23967
  }
23968
+ const logger = createLogger("Bootstrap");
25008
23969
  let runtime = null;
25009
23970
  function checkWritableUserData(userDataPath) {
25010
23971
  const issues = [];
@@ -25124,7 +24085,7 @@ async function bootstrap() {
25124
24085
  };
25125
24086
  let didInitializeChromeRenderer = false;
25126
24087
  const splashTimeout = setTimeout(() => {
25127
- console.warn("[bootstrap] Renderer did not finish loading before splash timeout");
24088
+ logger.warn("Renderer did not finish loading before splash timeout");
25128
24089
  revealMainWindow();
25129
24090
  }, 8e3);
25130
24091
  const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
@@ -25175,8 +24136,8 @@ async function bootstrap() {
25175
24136
  "did-fail-load",
25176
24137
  (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
25177
24138
  if (!isMainFrame) return;
25178
- console.error(
25179
- "[bootstrap] Chrome renderer failed to load:",
24139
+ logger.error(
24140
+ "Chrome renderer failed to load:",
25180
24141
  errorCode,
25181
24142
  errorDescription,
25182
24143
  validatedURL
@@ -25187,21 +24148,21 @@ async function bootstrap() {
25187
24148
  );
25188
24149
  loadRenderers(chromeView, sidebarView, devtoolsPanelView);
25189
24150
  startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
25190
- console.error("[bootstrap] MCP server failed to start:", err);
24151
+ logger.error("MCP server failed to start:", err);
25191
24152
  });
25192
24153
  }
25193
24154
  process.on("uncaughtException", (error) => {
25194
- console.error("[Vessel] Uncaught exception:", error.message, error.stack);
24155
+ logger.error("Uncaught exception:", error.message, error.stack);
25195
24156
  electron.app.quit();
25196
24157
  });
25197
24158
  process.on("unhandledRejection", (reason) => {
25198
- console.error(
25199
- "[Vessel] Unhandled rejection:",
24159
+ logger.error(
24160
+ "Unhandled rejection:",
25200
24161
  reason instanceof Error ? reason.message : reason
25201
24162
  );
25202
24163
  });
25203
24164
  electron.app.whenReady().then(bootstrap).catch((error) => {
25204
- console.error("[Vessel] Failed to bootstrap application:", error);
24165
+ logger.error("Failed to bootstrap application:", error);
25205
24166
  electron.app.quit();
25206
24167
  });
25207
24168
  electron.app.on("window-all-closed", () => {