@quanta-intellect/vessel-browser 0.1.61 → 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;
@@ -3938,6 +3981,7 @@ let flushTimer = null;
3938
3981
  let sessionStartedAt = null;
3939
3982
  function isEnabled() {
3940
3983
  if (POSTHOG_API_KEY === "YOUR_POSTHOG_KEY_HERE") return false;
3984
+ if (process.env.VESSEL_DEV === "1") return false;
3941
3985
  return loadSettings().telemetryEnabled !== false;
3942
3986
  }
3943
3987
  function trackEvent(event, properties = {}) {
@@ -4199,6 +4243,37 @@ function mapActionButtons(interactiveElements) {
4199
4243
  }
4200
4244
  return buttons;
4201
4245
  }
4246
+ function asStructuredObject(value) {
4247
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4248
+ }
4249
+ function stringifyStructuredScalar(value) {
4250
+ if (typeof value === "string") {
4251
+ const trimmed = value.trim();
4252
+ return trimmed || void 0;
4253
+ }
4254
+ if (typeof value === "number" && Number.isFinite(value)) {
4255
+ return String(value);
4256
+ }
4257
+ return void 0;
4258
+ }
4259
+ function firstStructuredString(...values) {
4260
+ for (const value of values) {
4261
+ const normalized = stringifyStructuredScalar(value);
4262
+ if (normalized) return normalized;
4263
+ }
4264
+ return void 0;
4265
+ }
4266
+ function getOfferPrice(offers) {
4267
+ if (Array.isArray(offers)) {
4268
+ for (const offer2 of offers) {
4269
+ const price = getOfferPrice(offer2);
4270
+ if (price) return price;
4271
+ }
4272
+ return void 0;
4273
+ }
4274
+ const offer = asStructuredObject(offers);
4275
+ return firstStructuredString(offer?.price);
4276
+ }
4202
4277
  function extractPrimaryEntity(pageType, structuredData, metaTags) {
4203
4278
  if (pageType === "product") {
4204
4279
  const product = structuredData?.find(
@@ -4206,14 +4281,27 @@ function extractPrimaryEntity(pageType, structuredData, metaTags) {
4206
4281
  );
4207
4282
  if (product) {
4208
4283
  const attrs = product.attributes ?? {};
4284
+ const aggregateRating = asStructuredObject(attrs.aggregateRating);
4209
4285
  return {
4210
4286
  type: "Product",
4211
- nameField: typeof attrs.name === "string" ? attrs.name : void 0,
4212
- priceField: typeof attrs.price === "string" ? attrs.price : typeof attrs.offers === "object" && attrs.offers !== null ? String(attrs.offers["price"] ?? "") : void 0,
4213
- imageField: typeof attrs.image === "string" ? attrs.image : Array.isArray(attrs.image) ? String(attrs.image[0]) : void 0,
4214
- descriptionField: typeof attrs.description === "string" ? attrs.description : void 0,
4215
- reviewsField: typeof attrs.reviews === "string" ? attrs.reviews : void 0,
4216
- ratingField: typeof attrs.rating === "string" ? attrs.rating : void 0,
4287
+ nameField: firstStructuredString(attrs.name),
4288
+ priceField: firstStructuredString(attrs.price) ?? getOfferPrice(attrs.offers),
4289
+ imageField: firstStructuredString(
4290
+ attrs.image,
4291
+ Array.isArray(attrs.image) ? attrs.image[0] : void 0
4292
+ ),
4293
+ descriptionField: firstStructuredString(attrs.description),
4294
+ reviewsField: firstStructuredString(
4295
+ attrs.reviews,
4296
+ attrs.reviewCount,
4297
+ aggregateRating?.reviewCount,
4298
+ aggregateRating?.ratingCount
4299
+ ),
4300
+ ratingField: firstStructuredString(
4301
+ attrs.rating,
4302
+ attrs.ratingValue,
4303
+ aggregateRating?.ratingValue
4304
+ ),
4217
4305
  addToCartField: void 0
4218
4306
  };
4219
4307
  }
@@ -4331,6 +4419,7 @@ function inferPageSchema(page) {
4331
4419
  confidence
4332
4420
  };
4333
4421
  }
4422
+ const logger$e = createLogger("Extractor");
4334
4423
  const EMPTY_PAGE_CONTENT = {
4335
4424
  title: "",
4336
4425
  content: "",
@@ -5077,7 +5166,8 @@ async function executeScript(webContents, script) {
5077
5166
  timer = setTimeout(() => resolve(null), EXECUTE_SCRIPT_TIMEOUT_MS);
5078
5167
  })
5079
5168
  ]);
5080
- } catch {
5169
+ } catch (err) {
5170
+ logger$e.warn("Failed to execute page script:", err);
5081
5171
  return null;
5082
5172
  } finally {
5083
5173
  if (timer) {
@@ -5185,7 +5275,8 @@ async function estimateExtractionTimeout(webContents) {
5185
5275
  );
5186
5276
  return EXTRACT_TIMEOUT_BASE_MS + extra;
5187
5277
  }
5188
- } catch {
5278
+ } catch (err) {
5279
+ logger$e.warn("Failed to estimate extraction timeout, using base timeout:", err);
5189
5280
  }
5190
5281
  return EXTRACT_TIMEOUT_BASE_MS;
5191
5282
  }
@@ -5974,6 +6065,9 @@ function isClickReadLoop(names) {
5974
6065
  }
5975
6066
  return clickReadPairs >= 2;
5976
6067
  }
6068
+ function isRecord(value) {
6069
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6070
+ }
5977
6071
  class AnthropicProvider {
5978
6072
  agentToolProfile = "default";
5979
6073
  client;
@@ -6070,10 +6164,14 @@ class AnthropicProvider {
6070
6164
  }
6071
6165
  } else if (event.type === "content_block_stop" && currentToolUse) {
6072
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
+ }
6073
6171
  toolUseBlocks.push({
6074
6172
  id: currentToolUse.id,
6075
6173
  name: currentToolUse.name,
6076
- input: JSON.parse(currentToolUse.inputJson || "{}")
6174
+ input
6077
6175
  });
6078
6176
  } catch {
6079
6177
  toolUseBlocks.push({
@@ -6120,7 +6218,7 @@ class AnthropicProvider {
6120
6218
  });
6121
6219
  continue;
6122
6220
  }
6123
- 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) ?? "";
6124
6222
  onChunk(`
6125
6223
  <<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
6126
6224
  `);
@@ -6347,6 +6445,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
6347
6445
  const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
6348
6446
  const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
6349
6447
  const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
6448
+ const logger$d = createLogger("OpenAIProvider");
6350
6449
  function shouldDebugAgentLoop() {
6351
6450
  const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
6352
6451
  return value === "1" || value === "true";
@@ -6753,9 +6852,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
6753
6852
  function logAgentLoopDebug(payload) {
6754
6853
  if (!shouldDebugAgentLoop()) return;
6755
6854
  try {
6756
- console.log(`[Vessel agent-debug] ${JSON.stringify(payload)}`);
6855
+ logger$d.info(`[agent-debug] ${JSON.stringify(payload)}`);
6757
6856
  } catch (err) {
6758
- console.warn("[Vessel agent-debug] Failed to serialize debug payload:", err);
6857
+ logger$d.warn("Failed to serialize debug payload:", err);
6759
6858
  }
6760
6859
  }
6761
6860
  function recoverTextEncodedToolCalls(text, availableToolNames) {
@@ -6775,7 +6874,10 @@ function recoverTextEncodedToolCalls(text, availableToolNames) {
6775
6874
  const argsJson = match[2] ?? "{}";
6776
6875
  let parsedArgs = {};
6777
6876
  try {
6778
- parsedArgs = JSON.parse(argsJson);
6877
+ const raw = JSON.parse(argsJson);
6878
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
6879
+ parsedArgs = raw;
6880
+ }
6779
6881
  } catch {
6780
6882
  continue;
6781
6883
  }
@@ -7172,7 +7274,7 @@ class OpenAICompatProvider {
7172
7274
  }
7173
7275
  continue;
7174
7276
  }
7175
- 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) ?? "";
7176
7278
  onChunk(`
7177
7279
  <<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
7178
7280
  `);
@@ -7330,7 +7432,7 @@ async function fetchProviderModels(config) {
7330
7432
  if (normalized.id === "anthropic") {
7331
7433
  const client2 = new Anthropic({ apiKey: normalized.apiKey });
7332
7434
  const page2 = await client2.models.list();
7333
- return { ok: true, models: page2.data.map((model) => model.id) };
7435
+ return okResult({ models: page2.data.map((model) => model.id) });
7334
7436
  }
7335
7437
  const meta = PROVIDERS[normalized.id];
7336
7438
  const baseURL = normalized.baseUrl || meta?.defaultBaseUrl || "https://api.openai.com/v1";
@@ -7342,9 +7444,10 @@ async function fetchProviderModels(config) {
7342
7444
  const models = page.data.map((model) => model.id);
7343
7445
  const warning = normalized.id === "llama_cpp" ? await probeLlamaCppCtxWarning(baseURL) : void 0;
7344
7446
  return {
7345
- ok: true,
7346
- models,
7347
- ...warning ? { warning } : {}
7447
+ ...okResult({
7448
+ models,
7449
+ ...warning ? { warning } : {}
7450
+ })
7348
7451
  };
7349
7452
  }
7350
7453
  function createProvider(config) {
@@ -7359,6 +7462,7 @@ function createProvider(config) {
7359
7462
  return new OpenAICompatProvider(normalized);
7360
7463
  }
7361
7464
  const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
7465
+ const logger$c = createLogger("DevTrace");
7362
7466
  let cachedFactory;
7363
7467
  function createNoopTraceSession() {
7364
7468
  return {
@@ -7391,7 +7495,7 @@ function loadLocalFactory() {
7391
7495
  return cachedFactory;
7392
7496
  }
7393
7497
  } catch (err) {
7394
- console.warn("[dev-trace] Failed to load local trace logger:", err);
7498
+ logger$c.warn("Failed to load local trace logger:", err);
7395
7499
  }
7396
7500
  }
7397
7501
  return cachedFactory;
@@ -11126,6 +11230,7 @@ function formatDeadLinkMessage(label, result) {
11126
11230
  const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
11127
11231
  return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
11128
11232
  }
11233
+ const logger$b = createLogger("Screenshot");
11129
11234
  async function captureScreenshot(wc) {
11130
11235
  for (let attempt = 0; attempt < 3; attempt += 1) {
11131
11236
  await new Promise((resolve) => setTimeout(resolve, 120 * (attempt + 1)));
@@ -11135,13 +11240,21 @@ async function captureScreenshot(wc) {
11135
11240
  const size = image.getSize();
11136
11241
  const base64 = image.toPNG().toString("base64");
11137
11242
  if (base64) {
11138
- return { ok: true, base64, width: size.width, height: size.height };
11243
+ return okResult({
11244
+ base64,
11245
+ width: size.width,
11246
+ height: size.height
11247
+ });
11139
11248
  }
11140
11249
  }
11141
- } catch {
11250
+ } catch (err) {
11251
+ logger$b.debug(
11252
+ `capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
11253
+ getErrorMessage(err)
11254
+ );
11142
11255
  }
11143
11256
  }
11144
- return { ok: false, error: "Page image was empty after 3 attempts" };
11257
+ return errorResult("Page image was empty after 3 attempts");
11145
11258
  }
11146
11259
  const SESSION_VERSION = 1;
11147
11260
  function getSessionsDir() {
@@ -11749,6 +11862,33 @@ function formatCompactToolResult(name, result) {
11749
11862
  return limitText(result, 18, 1400);
11750
11863
  }
11751
11864
  }
11865
+ function normalizeOptionalString(value) {
11866
+ if (typeof value !== "string") return void 0;
11867
+ const trimmed = value.trim();
11868
+ return trimmed || void 0;
11869
+ }
11870
+ function normalizeKeyFields(value) {
11871
+ if (!Array.isArray(value)) return void 0;
11872
+ const normalized = value.filter((field) => typeof field === "string").map((field) => field.trim()).filter(Boolean);
11873
+ return normalized.length > 0 ? normalized : void 0;
11874
+ }
11875
+ function normalizeAgentHints(value) {
11876
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
11877
+ return void 0;
11878
+ }
11879
+ const normalized = Object.fromEntries(
11880
+ Object.entries(value).map(([key, hint]) => [key.trim(), normalizeOptionalString(hint)]).filter((entry) => Boolean(entry[0] && entry[1]))
11881
+ );
11882
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
11883
+ }
11884
+ function normalizeBookmarkMetadata(input) {
11885
+ return {
11886
+ intent: normalizeOptionalString(input.intent),
11887
+ expectedContent: normalizeOptionalString(input.expectedContent),
11888
+ keyFields: normalizeKeyFields(input.keyFields),
11889
+ agentHints: normalizeAgentHints(input.agentHints)
11890
+ };
11891
+ }
11752
11892
  const HUGGING_FACE_HUB_HOSTS = /* @__PURE__ */ new Set(["huggingface.co", "www.huggingface.co"]);
11753
11893
  const HUGGING_FACE_MODEL_TASKS = [
11754
11894
  {
@@ -12009,9 +12149,18 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
12009
12149
  appliedFilters
12010
12150
  };
12011
12151
  }
12152
+ const logger$a = createLogger("PageActions");
12153
+ function getBookmarkMetadataFromArgs(args) {
12154
+ return normalizeBookmarkMetadata({
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
12159
+ });
12160
+ }
12012
12161
  const DEFAULT_PAGE_SCRIPT_TIMEOUT_MS = 1500;
12013
12162
  const PAGE_SCRIPT_TIMEOUT = /* @__PURE__ */ Symbol("page-script-timeout");
12014
- async function loadPermittedUrl$1(wc, url) {
12163
+ async function loadPermittedUrl(wc, url) {
12015
12164
  assertPermittedNavigationURL(url);
12016
12165
  await wc.loadURL(url);
12017
12166
  }
@@ -12184,7 +12333,9 @@ async function executePageScript(wc, script, options) {
12184
12333
  return PAGE_SCRIPT_TIMEOUT;
12185
12334
  }
12186
12335
  return result;
12187
- } catch {
12336
+ } catch (err) {
12337
+ const label = options?.label ? ` (${options.label})` : "";
12338
+ logger$a.warn(`Failed to execute page script${label}:`, err);
12188
12339
  return null;
12189
12340
  } finally {
12190
12341
  if (timer) {
@@ -12284,7 +12435,8 @@ async function getPostSearchSummary(wc) {
12284
12435
  Search results snapshot:
12285
12436
  ${truncated}`;
12286
12437
  }
12287
- } catch {
12438
+ } catch (err) {
12439
+ logger$a.warn("Failed to build post-search summary, falling back to nav summary:", err);
12288
12440
  }
12289
12441
  const fallback = await getPostNavSummary(wc);
12290
12442
  return fallback ? `${fallback}
@@ -12306,11 +12458,12 @@ async function getPostClickNavSummary(wc, toolProfile) {
12306
12458
  Page snapshot after navigation:
12307
12459
  ${truncated}`;
12308
12460
  }
12309
- } catch {
12461
+ } catch (err) {
12462
+ logger$a.warn("Failed to build post-click navigation summary:", err);
12310
12463
  }
12311
12464
  return "";
12312
12465
  }
12313
- async function scrollPage$1(wc, deltaY) {
12466
+ async function scrollPage(wc, deltaY) {
12314
12467
  const getScrollY = async () => {
12315
12468
  const scrollY = await executePageScript(
12316
12469
  wc,
@@ -12354,7 +12507,7 @@ async function scrollPage$1(wc, deltaY) {
12354
12507
  movedY: Math.round(afterY - beforeY)
12355
12508
  };
12356
12509
  }
12357
- async function clickElement$1(wc, selector) {
12510
+ async function clickElement(wc, selector) {
12358
12511
  const target = await executePageScript(
12359
12512
  wc,
12360
12513
  `
@@ -12443,7 +12596,7 @@ async function clickElement$1(wc, selector) {
12443
12596
  return "Error: Could not resolve click coordinates";
12444
12597
  }
12445
12598
  if (hiddenWindow) {
12446
- const activationResult = await activateElement$1(wc, selector);
12599
+ const activationResult = await activateElement(wc, selector);
12447
12600
  if (activationResult.startsWith("Error:")) {
12448
12601
  return activationResult;
12449
12602
  }
@@ -12458,7 +12611,7 @@ async function clickElement$1(wc, selector) {
12458
12611
  await sleep(80);
12459
12612
  return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
12460
12613
  }
12461
- async function activateElement$1(wc, selector) {
12614
+ async function activateElement(wc, selector) {
12462
12615
  const activated = await executePageScript(
12463
12616
  wc,
12464
12617
  `
@@ -12490,7 +12643,7 @@ async function activateElement$1(wc, selector) {
12490
12643
  }
12491
12644
  return "Activated element via DOM click";
12492
12645
  }
12493
- async function describeElementForClick$1(wc, selector) {
12646
+ async function describeElementForClick(wc, selector) {
12494
12647
  const result = await executePageScript(
12495
12648
  wc,
12496
12649
  `
@@ -12799,7 +12952,8 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12799
12952
  return;
12800
12953
  }
12801
12954
  }
12802
- } catch {
12955
+ } catch (err) {
12956
+ logger$a.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
12803
12957
  }
12804
12958
  if (snapshot.url && snapshot.url !== wc.getURL()) {
12805
12959
  try {
@@ -12807,14 +12961,16 @@ async function restoreLocaleSnapshot(wc, snapshot) {
12807
12961
  await wc.loadURL(snapshot.url);
12808
12962
  await waitForLoad(wc, 3e3);
12809
12963
  return;
12810
- } catch {
12964
+ } catch (err) {
12965
+ logger$a.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
12811
12966
  }
12812
12967
  }
12813
12968
  if (snapshot.url) {
12814
12969
  try {
12815
12970
  await wc.reload();
12816
12971
  await waitForLoad(wc, 3e3);
12817
- } catch {
12972
+ } catch (err) {
12973
+ logger$a.warn("Failed to restore locale via page reload:", err);
12818
12974
  }
12819
12975
  }
12820
12976
  }
@@ -12945,14 +13101,14 @@ Go back to search results to select the next product.`;
12945
13101
  if (!overlayHint) {
12946
13102
  return cartSummary;
12947
13103
  }
12948
- const dialogActions = await getCartDialogActions$1(wc);
13104
+ const dialogActions = await getCartDialogActions(wc);
12949
13105
  const actionsSuffix = dialogActions ? `
12950
13106
  ${dialogActions}
12951
13107
  Click one of these dialog actions. Do NOT click any other element.` : "";
12952
13108
  return `
12953
13109
  ${overlayHint}${actionsSuffix}${cartSummary}`;
12954
13110
  }
12955
- async function clickResolvedSelector$1(wc, selector) {
13111
+ async function clickResolvedSelector(wc, selector) {
12956
13112
  if (selector.startsWith("__vessel_idx:")) {
12957
13113
  const idx = Number(selector.slice("__vessel_idx:".length));
12958
13114
  const beforeUrl2 = wc.getURL();
@@ -12985,10 +13141,10 @@ Go back and select a different product.`;
12985
13141
  await waitForPotentialNavigation(wc, beforeUrl2);
12986
13142
  const afterUrl2 = wc.getURL();
12987
13143
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
12988
- let idxOverlay = await detectPostClickOverlay$1(wc);
13144
+ let idxOverlay = await detectPostClickOverlay(wc);
12989
13145
  if (!idxOverlay && idxCartMatch) {
12990
13146
  await sleep(1200);
12991
- idxOverlay = await detectPostClickOverlay$1(wc);
13147
+ idxOverlay = await detectPostClickOverlay(wc);
12992
13148
  }
12993
13149
  if (idxCartMatch) {
12994
13150
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, idxOverlay)}`;
@@ -12997,7 +13153,7 @@ Go back and select a different product.`;
12997
13153
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
12998
13154
  if (hrefMatch) {
12999
13155
  try {
13000
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13156
+ await loadPermittedUrl(wc, hrefMatch[1]);
13001
13157
  await waitForLoad(wc, 8e3);
13002
13158
  const hrefUrl = wc.getURL();
13003
13159
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13052,10 +13208,10 @@ Go back and select a different product.`;
13052
13208
  await waitForPotentialNavigation(wc, beforeUrl2);
13053
13209
  const afterUrl2 = wc.getURL();
13054
13210
  if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
13055
- let shadowOverlay = await detectPostClickOverlay$1(wc);
13211
+ let shadowOverlay = await detectPostClickOverlay(wc);
13056
13212
  if (!shadowOverlay && shadowCartMatch) {
13057
13213
  await sleep(1200);
13058
- shadowOverlay = await detectPostClickOverlay$1(wc);
13214
+ shadowOverlay = await detectPostClickOverlay(wc);
13059
13215
  }
13060
13216
  if (shadowCartMatch) {
13061
13217
  return `${result}${await buildCartSuccessSuffix(wc, beforeUrl2, shadowOverlay)}`;
@@ -13064,7 +13220,7 @@ Go back and select a different product.`;
13064
13220
  const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
13065
13221
  if (hrefMatch) {
13066
13222
  try {
13067
- await loadPermittedUrl$1(wc, hrefMatch[1]);
13223
+ await loadPermittedUrl(wc, hrefMatch[1]);
13068
13224
  await waitForLoad(wc, 8e3);
13069
13225
  const hrefUrl = wc.getURL();
13070
13226
  if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
@@ -13077,14 +13233,14 @@ ${shadowOverlay}` : `${result}
13077
13233
  Note: Page did not change after click.`;
13078
13234
  }
13079
13235
  const beforeUrl = wc.getURL();
13080
- const elInfo = await describeElementForClick$1(wc, selector);
13236
+ const elInfo = await describeElementForClick(wc, selector);
13081
13237
  if ("error" in elInfo) return `Error: ${elInfo.error}`;
13082
13238
  const cartMatch = isAddToCartText(elInfo.text);
13083
13239
  if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
13084
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).`;
13085
13241
  }
13086
13242
  if (!cartMatch && recentCartClicks.has(beforeUrl)) {
13087
- const dialogActions = await getCartDialogActions$1(wc);
13243
+ const dialogActions = await getCartDialogActions(wc);
13088
13244
  if (dialogActions) {
13089
13245
  return `Blocked: a cart confirmation dialog is open. Do not click background elements.
13090
13246
  ${dialogActions}
@@ -13107,14 +13263,14 @@ Go back and select a different product.`;
13107
13263
  }
13108
13264
  const tagLabel = elInfo.tag && elInfo.tag !== "a" && elInfo.tag !== "button" ? ` <${elInfo.tag}>` : "";
13109
13265
  const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
13110
- const clickResult = await clickElement$1(wc, selector);
13266
+ const clickResult = await clickElement(wc, selector);
13111
13267
  if (clickResult.startsWith("Error:")) return clickResult;
13112
13268
  await waitForPotentialNavigation(wc, beforeUrl);
13113
13269
  const afterUrl = wc.getURL();
13114
13270
  if (afterUrl !== beforeUrl) {
13115
13271
  return `${clickText} -> ${afterUrl}`;
13116
13272
  }
13117
- const overlayHint = await detectPostClickOverlay$1(wc);
13273
+ const overlayHint = await detectPostClickOverlay(wc);
13118
13274
  if (overlayHint) {
13119
13275
  if (cartMatch) {
13120
13276
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
@@ -13128,7 +13284,7 @@ ${overlayHint}`;
13128
13284
  }
13129
13285
  if (cartMatch) {
13130
13286
  await sleep(1200);
13131
- const delayedOverlayHint = await detectPostClickOverlay$1(wc);
13287
+ const delayedOverlayHint = await detectPostClickOverlay(wc);
13132
13288
  if (delayedOverlayHint) {
13133
13289
  return `${clickText} (${clickResult})${await buildCartSuccessSuffix(
13134
13290
  wc,
@@ -13141,7 +13297,7 @@ ${overlayHint}`;
13141
13297
  beforeUrl
13142
13298
  )}`;
13143
13299
  }
13144
- const activationResult = await activateElement$1(wc, selector);
13300
+ const activationResult = await activateElement(wc, selector);
13145
13301
  if (!activationResult.startsWith("Error:")) {
13146
13302
  await waitForPotentialNavigation(wc, beforeUrl);
13147
13303
  const fallbackUrl = wc.getURL();
@@ -13149,7 +13305,7 @@ ${overlayHint}`;
13149
13305
  return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
13150
13306
  }
13151
13307
  }
13152
- const postActivationOverlayHint = await detectPostClickOverlay$1(wc);
13308
+ const postActivationOverlayHint = await detectPostClickOverlay(wc);
13153
13309
  if (postActivationOverlayHint) {
13154
13310
  return `${clickText} (${clickResult})
13155
13311
  ${postActivationOverlayHint}`;
@@ -13159,13 +13315,14 @@ ${postActivationOverlayHint}`;
13159
13315
  const validation = await validateLinkDestination(elInfo.href);
13160
13316
  if (validation.status !== "dead") {
13161
13317
  try {
13162
- await loadPermittedUrl$1(wc, elInfo.href);
13318
+ await loadPermittedUrl(wc, elInfo.href);
13163
13319
  await waitForLoad(wc, 8e3);
13164
13320
  const hrefFallbackUrl = wc.getURL();
13165
13321
  if (hrefFallbackUrl !== beforeUrl) {
13166
13322
  return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
13167
13323
  }
13168
- } catch {
13324
+ } catch (err) {
13325
+ logger$a.warn("Failed href fallback after click, returning generic click result:", err);
13169
13326
  }
13170
13327
  }
13171
13328
  }
@@ -13209,11 +13366,12 @@ async function tryAutoDismissCartDialog(wc) {
13209
13366
  await sleep(500);
13210
13367
  return result;
13211
13368
  }
13212
- } catch {
13369
+ } catch (err) {
13370
+ logger$a.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
13213
13371
  }
13214
13372
  return null;
13215
13373
  }
13216
- async function getCartDialogActions$1(wc) {
13374
+ async function getCartDialogActions(wc) {
13217
13375
  const result = await executePageScript(
13218
13376
  wc,
13219
13377
  `
@@ -13258,7 +13416,7 @@ async function getCartDialogActions$1(wc) {
13258
13416
  return `Available dialog actions:
13259
13417
  ${result.actions.join("\n")}`;
13260
13418
  }
13261
- async function detectPostClickOverlay$1(wc) {
13419
+ async function detectPostClickOverlay(wc) {
13262
13420
  const result = await executePageScript(
13263
13421
  wc,
13264
13422
  `
@@ -13364,7 +13522,7 @@ async function detectPostClickOverlay$1(wc) {
13364
13522
  const desc = result.label ? ` ("${result.label}")` : "";
13365
13523
  return `A dialog or overlay appeared${desc}. Call read_page to see available actions.`;
13366
13524
  }
13367
- async function dismissPopup$1(wc) {
13525
+ async function dismissPopup(wc) {
13368
13526
  const before = await extractContent(wc);
13369
13527
  const initialBlocking = before.overlays.filter(
13370
13528
  (overlay) => overlay.blocksInteraction
@@ -13417,7 +13575,7 @@ async function dismissPopup$1(wc) {
13417
13575
  if (continueResult && continueResult !== PAGE_SCRIPT_TIMEOUT && typeof continueResult === "string" && !continueResult.startsWith("Error")) {
13418
13576
  return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
13419
13577
  }
13420
- const dialogActions = await getCartDialogActions$1(wc);
13578
+ const dialogActions = await getCartDialogActions(wc);
13421
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."}`;
13422
13580
  }
13423
13581
  }
@@ -13577,7 +13735,7 @@ async function dismissPopup$1(wc) {
13577
13735
  if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
13578
13736
  continue;
13579
13737
  }
13580
- const result = await clickElement$1(wc, candidate.selector);
13738
+ const result = await clickElement(wc, candidate.selector);
13581
13739
  if (result.startsWith("Error:")) continue;
13582
13740
  await sleep(250);
13583
13741
  const postClickLocale = await getLocaleSnapshot(wc);
@@ -13619,7 +13777,7 @@ function describeOverlayState(page) {
13619
13777
  }
13620
13778
  async function clickOverlayCandidate(wc, action) {
13621
13779
  if (!action?.selector) return null;
13622
- const result = await clickResolvedSelector$1(wc, action.selector);
13780
+ const result = await clickResolvedSelector(wc, action.selector);
13623
13781
  return `${action.label || action.selector}: ${result}`;
13624
13782
  }
13625
13783
  async function tryDismissConsentIframe(wc) {
@@ -13830,7 +13988,7 @@ Submitted modal: ${submitResult}`;
13830
13988
  }
13831
13989
  }
13832
13990
  if (!actionMessage) {
13833
- actionMessage = `Fallback popup handling: ${await dismissPopup$1(wc)}`;
13991
+ actionMessage = `Fallback popup handling: ${await dismissPopup(wc)}`;
13834
13992
  }
13835
13993
  steps.push(actionMessage);
13836
13994
  if (overlay.kind === "cookie_consent") {
@@ -14005,7 +14163,7 @@ async function fillFormFields(wc, fields) {
14005
14163
  });
14006
14164
  continue;
14007
14165
  }
14008
- const result = await setElementValue$1(
14166
+ const result = await setElementValue(
14009
14167
  wc,
14010
14168
  selector,
14011
14169
  String(field.value || "")
@@ -14014,14 +14172,14 @@ async function fillFormFields(wc, fields) {
14014
14172
  }
14015
14173
  return results;
14016
14174
  }
14017
- function getTabByMatch$1(tabManager, match) {
14175
+ function getTabByMatch(tabManager, match) {
14018
14176
  if (!match) return null;
14019
14177
  const lowered = match.toLowerCase();
14020
14178
  return tabManager.getAllStates().find(
14021
14179
  (tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
14022
14180
  ) || null;
14023
14181
  }
14024
- function isDangerousAction$1(name) {
14182
+ function isDangerousAction(name) {
14025
14183
  return [
14026
14184
  "navigate",
14027
14185
  "open_bookmark",
@@ -14032,6 +14190,7 @@ function isDangerousAction$1(name) {
14032
14190
  "press_key",
14033
14191
  "create_tab",
14034
14192
  "switch_tab",
14193
+ "close_tab",
14035
14194
  "restore_checkpoint",
14036
14195
  "load_session",
14037
14196
  "login",
@@ -14040,7 +14199,7 @@ function isDangerousAction$1(name) {
14040
14199
  "paginate"
14041
14200
  ].includes(name);
14042
14201
  }
14043
- async function setElementValue$1(wc, selector, value) {
14202
+ async function setElementValue(wc, selector, value) {
14044
14203
  if (selector.startsWith("__vessel_idx:")) {
14045
14204
  const idx = Number(selector.slice("__vessel_idx:".length));
14046
14205
  const result2 = await executePageScript(
@@ -14144,7 +14303,7 @@ async function setElementValue$1(wc, selector, value) {
14144
14303
  );
14145
14304
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14146
14305
  }
14147
- async function typeKeystroke$1(wc, selector, value) {
14306
+ async function typeKeystroke(wc, selector, value) {
14148
14307
  const result = await executePageScript(
14149
14308
  wc,
14150
14309
  `
@@ -14192,7 +14351,7 @@ async function typeKeystroke$1(wc, selector, value) {
14192
14351
  );
14193
14352
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("type_text") : result || "Error: Could not type into element";
14194
14353
  }
14195
- async function hoverElement$1(wc, selector) {
14354
+ async function hoverElement(wc, selector) {
14196
14355
  const pos = await wc.executeJavaScript(`
14197
14356
  (function() {
14198
14357
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14222,7 +14381,7 @@ async function hoverElement$1(wc, selector) {
14222
14381
  const label = typeof pos.label === "string" ? pos.label : "element";
14223
14382
  return `Hovered: ${label}`;
14224
14383
  }
14225
- async function focusElement$1(wc, selector) {
14384
+ async function focusElement(wc, selector) {
14226
14385
  return wc.executeJavaScript(`
14227
14386
  (function() {
14228
14387
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -14236,7 +14395,7 @@ async function focusElement$1(wc, selector) {
14236
14395
  })()
14237
14396
  `);
14238
14397
  }
14239
- async function waitForCondition$1(wc, args) {
14398
+ async function waitForCondition(wc, args) {
14240
14399
  const timeoutMs = Math.max(250, Number(args.timeoutMs) || 5e3);
14241
14400
  const selector = typeof args.selector === "string" && args.selector.trim() ? args.selector.trim() : "";
14242
14401
  const text = typeof args.text === "string" && args.text.trim() ? args.text.trim() : "";
@@ -14295,8 +14454,8 @@ function findCheckpoint(checkpoints, args) {
14295
14454
  }
14296
14455
  return null;
14297
14456
  }
14298
- function resolveBookmarkFolderTarget$1(args) {
14299
- 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() : "";
14300
14459
  if (folderId) {
14301
14460
  if (folderId === UNSORTED_ID) {
14302
14461
  return {
@@ -14310,7 +14469,7 @@ function resolveBookmarkFolderTarget$1(args) {
14310
14469
  }
14311
14470
  return { folderId: folder2.id, folderName: folder2.name };
14312
14471
  }
14313
- 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 : "";
14314
14473
  if (!folderName || folderName.toLowerCase() === "unsorted") {
14315
14474
  return {
14316
14475
  folderId: UNSORTED_ID,
@@ -14321,10 +14480,11 @@ function resolveBookmarkFolderTarget$1(args) {
14321
14480
  if (existing) {
14322
14481
  return { folderId: existing.id, folderName: existing.name };
14323
14482
  }
14324
- if (args.createFolderIfMissing === false) {
14483
+ const createIfMissing = args.createFolderIfMissing ?? args.create_folder_if_missing;
14484
+ if (createIfMissing === false) {
14325
14485
  return { folderName, error: `Folder "${folderName}" not found` };
14326
14486
  }
14327
- 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;
14328
14488
  const { folder } = ensureFolder(folderName, folderSummary);
14329
14489
  return {
14330
14490
  folderId: folder.id,
@@ -14332,27 +14492,27 @@ function resolveBookmarkFolderTarget$1(args) {
14332
14492
  createdFolder: folder.name
14333
14493
  };
14334
14494
  }
14335
- function formatFolderStatus$1(limit = 6) {
14495
+ function formatFolderStatus(limit = 6) {
14336
14496
  const folders = listFolderOverviews();
14337
14497
  const summary = folders.slice(0, limit).map((folder) => `${folder.name} (${folder.count})`).join(", ");
14338
14498
  return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
14339
14499
  }
14340
- function describeFolder$1(folderId) {
14500
+ function describeFolder(folderId) {
14341
14501
  if (!folderId || folderId === UNSORTED_ID) {
14342
14502
  return "Unsorted";
14343
14503
  }
14344
14504
  return getFolder(folderId)?.name ?? folderId;
14345
14505
  }
14346
- function composeDuplicateBookmarkResponse$1(args) {
14506
+ function composeDuplicateBookmarkResponse(args) {
14347
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.`;
14348
14508
  }
14349
- function composeFolderAwareResponse$1(message, createdFolder) {
14509
+ function composeFolderAwareResponse(message, createdFolder) {
14350
14510
  const prefix = createdFolder ? `Created folder "${createdFolder}".
14351
14511
  ` : "";
14352
14512
  return `${prefix}${message}
14353
- ${formatFolderStatus$1()}`;
14513
+ ${formatFolderStatus()}`;
14354
14514
  }
14355
- async function selectOption$1(wc, args) {
14515
+ async function selectOption(wc, args) {
14356
14516
  const selector = await resolveSelector(wc, args.index, args.selector);
14357
14517
  if (!selector) return "Error: No select element index or selector provided";
14358
14518
  const result = await executePageScript(
@@ -14388,7 +14548,7 @@ async function selectOption$1(wc, args) {
14388
14548
  );
14389
14549
  return result === PAGE_SCRIPT_TIMEOUT ? pageBusyError("select_option") : result || "Error: Could not select option";
14390
14550
  }
14391
- async function submitForm$1(wc, args) {
14551
+ async function submitForm(wc, args) {
14392
14552
  const beforeUrl = wc.getURL();
14393
14553
  let selector = await resolveSelector(wc, args.index, args.selector);
14394
14554
  if (!selector) {
@@ -14529,7 +14689,7 @@ async function submitForm$1(wc, args) {
14529
14689
  if (formInfo.params) {
14530
14690
  url.search = formInfo.params;
14531
14691
  }
14532
- await loadPermittedUrl$1(wc, url.toString());
14692
+ await loadPermittedUrl(wc, url.toString());
14533
14693
  await waitForPotentialNavigation(wc, beforeUrl);
14534
14694
  const afterUrl = wc.getURL();
14535
14695
  return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
@@ -14571,8 +14731,20 @@ async function submitForm$1(wc, args) {
14571
14731
  }
14572
14732
  return "Submitted form";
14573
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
+ }
14574
14746
  async function clickElementBySelector(wc, selector) {
14575
- return clickResolvedSelector$1(wc, selector);
14747
+ return clickResolvedSelector(wc, selector);
14576
14748
  }
14577
14749
  function normalizeSearchQuery(query) {
14578
14750
  return query.replace(/\s+/g, " ").trim();
@@ -14856,7 +15028,7 @@ async function searchPage(wc, args) {
14856
15028
  const shortcut = buildSearchShortcut(wc.getURL(), query);
14857
15029
  if (shortcut) {
14858
15030
  const beforeUrl2 = wc.getURL();
14859
- await loadPermittedUrl$1(wc, shortcut.url);
15031
+ await loadPermittedUrl(wc, shortcut.url);
14860
15032
  await waitForPotentialNavigation(wc, beforeUrl2, 4e3);
14861
15033
  const afterUrl2 = wc.getURL();
14862
15034
  const applied = shortcut.appliedFilters.length > 0 ? ` (${shortcut.appliedFilters.join(", ")})` : "";
@@ -14874,13 +15046,13 @@ async function searchPage(wc, args) {
14874
15046
  if (!searchInfo?.selector) {
14875
15047
  return 'Error: Could not find a visible search input. Try read_page(mode="visible_only") or provide a selector.';
14876
15048
  }
14877
- const fillResult = await setElementValue$1(wc, searchInfo.selector, query);
15049
+ const fillResult = await setElementValue(wc, searchInfo.selector, query);
14878
15050
  if (fillResult.startsWith("Error:")) {
14879
15051
  return fillResult;
14880
15052
  }
14881
15053
  await sleep(100);
14882
15054
  const beforeUrl = wc.getURL();
14883
- const keyResult = await pressKey$1(wc, {
15055
+ const keyResult = await pressKey(wc, {
14884
15056
  key: "Enter",
14885
15057
  selector: searchInfo.selector
14886
15058
  });
@@ -14904,7 +15076,7 @@ async function searchPage(wc, args) {
14904
15076
  }
14905
15077
  return `Searched "${query}" (same page — results may have loaded dynamically)${await getPostSearchSummary(wc)}`;
14906
15078
  }
14907
- async function pressKey$1(wc, args) {
15079
+ async function pressKey(wc, args) {
14908
15080
  const key = typeof args.key === "string" ? args.key.trim() : "";
14909
15081
  if (!key) return "Error: No key provided";
14910
15082
  const selector = await resolveSelector(wc, args.index, args.selector);
@@ -15116,9 +15288,8 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
15116
15288
  "metrics",
15117
15289
  "wait_for_navigation"
15118
15290
  ]);
15119
- async function executeAction(name, rawArgs, ctx) {
15291
+ async function executeAction(name, args, ctx) {
15120
15292
  name = normalizeToolAlias(name);
15121
- const args = rawArgs;
15122
15293
  if (!KNOWN_TOOLS.has(name)) {
15123
15294
  for (const known of KNOWN_TOOLS) {
15124
15295
  if (name.startsWith(known) && name.length > known.length) {
@@ -15167,7 +15338,7 @@ async function executeAction(name, rawArgs, ctx) {
15167
15338
  name,
15168
15339
  args,
15169
15340
  tabId,
15170
- dangerous: isDangerousAction$1(name),
15341
+ dangerous: isDangerousAction(name),
15171
15342
  executor: async () => {
15172
15343
  switch (name) {
15173
15344
  case "screenshot": {
@@ -15215,7 +15386,7 @@ async function executeAction(name, rawArgs, ctx) {
15215
15386
  case "switch_tab": {
15216
15387
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15217
15388
  if (!targetId) {
15218
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15389
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15219
15390
  }
15220
15391
  if (!targetId) return "Error: No matching tab found";
15221
15392
  ctx.tabManager.switchTab(targetId);
@@ -15309,7 +15480,7 @@ async function executeAction(name, rawArgs, ctx) {
15309
15480
  if (!selector) {
15310
15481
  return "Error: No element index, selector, or visible text provided";
15311
15482
  }
15312
- return clickResolvedSelector$1(wc, selector);
15483
+ return clickResolvedSelector(wc, selector);
15313
15484
  }
15314
15485
  case "inspect_element": {
15315
15486
  if (!wc) return "Error: No active tab";
@@ -15341,18 +15512,18 @@ async function executeAction(name, rawArgs, ctx) {
15341
15512
  if (!selector) return "Error: No element index or selector provided";
15342
15513
  const mode = typeof args.mode === "string" ? args.mode : "default";
15343
15514
  if (mode === "keystroke") {
15344
- return typeKeystroke$1(wc, selector, String(args.text || ""));
15515
+ return typeKeystroke(wc, selector, String(args.text || ""));
15345
15516
  }
15346
- return setElementValue$1(wc, selector, String(args.text || ""));
15517
+ return setElementValue(wc, selector, String(args.text || ""));
15347
15518
  }
15348
15519
  case "select_option": {
15349
15520
  if (!wc) return "Error: No active tab";
15350
- return selectOption$1(wc, args);
15521
+ return selectOption(wc, args);
15351
15522
  }
15352
15523
  case "submit_form": {
15353
15524
  if (!wc) return "Error: No active tab";
15354
15525
  const beforeUrl = wc.getURL();
15355
- const result2 = await submitForm$1(wc, args);
15526
+ const result2 = await submitForm(wc, args);
15356
15527
  if (result2.startsWith("Error") || result2.startsWith("Target") || result2.startsWith("No parent") || result2.startsWith("Submit control")) {
15357
15528
  return result2;
15358
15529
  }
@@ -15363,7 +15534,7 @@ async function executeAction(name, rawArgs, ctx) {
15363
15534
  case "press_key": {
15364
15535
  if (!wc) return "Error: No active tab";
15365
15536
  const beforeUrl = wc.getURL();
15366
- const result2 = await pressKey$1(wc, args);
15537
+ const result2 = await pressKey(wc, args);
15367
15538
  const key = typeof args.key === "string" ? args.key.trim() : "";
15368
15539
  if (key === "Enter") {
15369
15540
  await waitForPotentialNavigation(wc, beforeUrl, 3e3);
@@ -15380,20 +15551,20 @@ async function executeAction(name, rawArgs, ctx) {
15380
15551
  if (!wc) return "Error: No active tab";
15381
15552
  const pixels = coerceOptionalNumber(args.amount) ?? 500;
15382
15553
  const dir = args.direction === "up" ? -pixels : pixels;
15383
- const result2 = await scrollPage$1(wc, dir);
15554
+ const result2 = await scrollPage(wc, dir);
15384
15555
  return `Scrolled ${args.direction} by ${pixels}px (moved ${Math.abs(result2.movedY)}px, now at y=${Math.round(result2.afterY)})`;
15385
15556
  }
15386
15557
  case "hover": {
15387
15558
  if (!wc) return "Error: No active tab";
15388
15559
  const selector = await resolveSelector(wc, args.index, args.selector);
15389
15560
  if (!selector) return "Error: No element index or selector provided";
15390
- return hoverElement$1(wc, selector);
15561
+ return hoverElement(wc, selector);
15391
15562
  }
15392
15563
  case "focus": {
15393
15564
  if (!wc) return "Error: No active tab";
15394
15565
  const selector = await resolveSelector(wc, args.index, args.selector);
15395
15566
  if (!selector) return "Error: No element index or selector provided";
15396
- return focusElement$1(wc, selector);
15567
+ return focusElement(wc, selector);
15397
15568
  }
15398
15569
  case "set_ad_blocking": {
15399
15570
  const enabled = typeof args.enabled === "boolean" ? args.enabled : null;
@@ -15402,7 +15573,7 @@ async function executeAction(name, rawArgs, ctx) {
15402
15573
  }
15403
15574
  let targetId = typeof args.tabId === "string" ? args.tabId.trim() : "";
15404
15575
  if (!targetId) {
15405
- targetId = getTabByMatch$1(ctx.tabManager, args.match)?.id || "";
15576
+ targetId = getTabByMatch(ctx.tabManager, args.match)?.id || "";
15406
15577
  }
15407
15578
  if (!targetId) {
15408
15579
  targetId = ctx.tabManager.getActiveTabId() || "";
@@ -15421,7 +15592,7 @@ async function executeAction(name, rawArgs, ctx) {
15421
15592
  }
15422
15593
  case "dismiss_popup": {
15423
15594
  if (!wc) return "Error: No active tab";
15424
- return dismissPopup$1(wc);
15595
+ return dismissPopup(wc);
15425
15596
  }
15426
15597
  case "clear_overlays": {
15427
15598
  if (!wc) return "Error: No active tab";
@@ -15444,7 +15615,8 @@ async function executeAction(name, rawArgs, ctx) {
15444
15615
  }, 6e3)
15445
15616
  )
15446
15617
  ]);
15447
- } catch {
15618
+ } catch (err) {
15619
+ logger$a.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
15448
15620
  content = null;
15449
15621
  }
15450
15622
  if (!content || content.content.length === 0) {
@@ -15460,11 +15632,13 @@ async function executeAction(name, rawArgs, ctx) {
15460
15632
  extractContent(wc),
15461
15633
  new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
15462
15634
  ]);
15463
- } catch {
15635
+ } catch (err) {
15636
+ logger$a.warn("Failed to re-extract content after iframe consent dismissal:", err);
15464
15637
  content = null;
15465
15638
  }
15466
15639
  }
15467
- } catch {
15640
+ } catch (err) {
15641
+ logger$a.warn("Failed iframe consent dismissal during read_page recovery:", err);
15468
15642
  }
15469
15643
  }
15470
15644
  if (content && content.content.length > 0) {
@@ -15504,7 +15678,7 @@ ${truncated}`;
15504
15678
  }
15505
15679
  case "wait_for": {
15506
15680
  if (!wc) return "Error: No active tab";
15507
- return waitForCondition$1(wc, args);
15681
+ return waitForCondition(wc, args);
15508
15682
  }
15509
15683
  case "wait_for_navigation": {
15510
15684
  if (!wc) return "Error: No active tab";
@@ -15647,12 +15821,12 @@ ${truncated}`;
15647
15821
  (folder2) => folder2.name.toLowerCase() === name2.toLowerCase()
15648
15822
  );
15649
15823
  if (existing) {
15650
- return composeFolderAwareResponse$1(
15824
+ return composeFolderAwareResponse(
15651
15825
  `Folder "${existing.name}" already exists (id=${existing.id})`
15652
15826
  );
15653
15827
  }
15654
15828
  const folder = createFolderWithSummary(name2, summary);
15655
- return composeFolderAwareResponse$1(
15829
+ return composeFolderAwareResponse(
15656
15830
  `Created folder "${folder.name}" (id=${folder.id})`
15657
15831
  );
15658
15832
  }
@@ -15664,7 +15838,7 @@ ${truncated}`;
15664
15838
  resolvedSelector
15665
15839
  });
15666
15840
  if ("error" in source) return `Error: ${source.error}`;
15667
- const target = resolveBookmarkFolderTarget$1(args);
15841
+ const target = resolveBookmarkFolderTarget(args);
15668
15842
  if (target.error) return target.error;
15669
15843
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
15670
15844
  const onDuplicate = typeof args.onDuplicate === "string" && ["ask", "update", "duplicate"].includes(args.onDuplicate) ? args.onDuplicate : "ask";
@@ -15675,21 +15849,14 @@ ${truncated}`;
15675
15849
  note,
15676
15850
  {
15677
15851
  onDuplicate,
15678
- extra: {
15679
- intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15680
- expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15681
- keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15682
- (f) => typeof f === "string"
15683
- ) : void 0,
15684
- agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15685
- }
15852
+ extra: getBookmarkMetadataFromArgs(args)
15686
15853
  }
15687
15854
  );
15688
15855
  if (result2.status === "conflict" && result2.existing) {
15689
- return composeFolderAwareResponse$1(
15690
- composeDuplicateBookmarkResponse$1({
15856
+ return composeFolderAwareResponse(
15857
+ composeDuplicateBookmarkResponse({
15691
15858
  url: source.url,
15692
- folderName: describeFolder$1(target.folderId),
15859
+ folderName: describeFolder(target.folderId),
15693
15860
  bookmarkId: result2.existing.id
15694
15861
  }),
15695
15862
  target.createdFolder
@@ -15698,13 +15865,13 @@ ${truncated}`;
15698
15865
  const bookmark = result2.bookmark;
15699
15866
  if (!bookmark) return "Error: Bookmark save failed";
15700
15867
  const verb = result2.status === "updated" ? "Updated" : "Saved";
15701
- return composeFolderAwareResponse$1(
15702
- `${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})`,
15703
15870
  target.createdFolder
15704
15871
  );
15705
15872
  }
15706
15873
  case "organize_bookmark": {
15707
- const target = resolveBookmarkFolderTarget$1(args);
15874
+ const target = resolveBookmarkFolderTarget(args);
15708
15875
  if (target.error) return target.error;
15709
15876
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15710
15877
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15723,18 +15890,13 @@ ${truncated}`;
15723
15890
  folderId: target.folderId,
15724
15891
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
15725
15892
  note,
15726
- intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15727
- expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15728
- keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15729
- (f) => typeof f === "string"
15730
- ) : void 0,
15731
- agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15893
+ ...getBookmarkMetadataFromArgs(args)
15732
15894
  });
15733
15895
  if (!updated) {
15734
15896
  return `Bookmark ${existing.id} not found`;
15735
15897
  }
15736
- return composeFolderAwareResponse$1(
15737
- `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})`,
15738
15900
  target.createdFolder
15739
15901
  );
15740
15902
  }
@@ -15746,25 +15908,18 @@ ${truncated}`;
15746
15908
  note,
15747
15909
  {
15748
15910
  onDuplicate: "update",
15749
- extra: {
15750
- intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15751
- expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15752
- keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15753
- (f) => typeof f === "string"
15754
- ) : void 0,
15755
- agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15756
- }
15911
+ extra: getBookmarkMetadataFromArgs(args)
15757
15912
  }
15758
15913
  );
15759
15914
  const bookmark = result2.bookmark;
15760
15915
  if (!bookmark) return "Error: Bookmark save failed";
15761
- return composeFolderAwareResponse$1(
15762
- `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})`,
15763
15918
  target.createdFolder
15764
15919
  );
15765
15920
  }
15766
15921
  case "archive_bookmark": {
15767
- const target = resolveBookmarkFolderTarget$1({ archive: true });
15922
+ const target = resolveBookmarkFolderTarget({ archive: true });
15768
15923
  if (target.error) return target.error;
15769
15924
  const bookmarkId = typeof args.bookmarkId === "string" ? args.bookmarkId.trim() : "";
15770
15925
  const note = typeof args.note === "string" && args.note.trim() ? args.note.trim() : void 0;
@@ -15787,8 +15942,8 @@ ${truncated}`;
15787
15942
  if (!updated) {
15788
15943
  return `Bookmark ${existing.id} not found`;
15789
15944
  }
15790
- return composeFolderAwareResponse$1(
15791
- `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})`,
15792
15947
  target.createdFolder
15793
15948
  );
15794
15949
  }
@@ -15801,8 +15956,8 @@ ${truncated}`;
15801
15956
  target.folderId,
15802
15957
  note
15803
15958
  );
15804
- return composeFolderAwareResponse$1(
15805
- `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})`,
15806
15961
  target.createdFolder
15807
15962
  );
15808
15963
  }
@@ -15890,7 +16045,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
15890
16045
  let page;
15891
16046
  try {
15892
16047
  page = await extractContent(wc);
15893
- } catch {
16048
+ } catch (err) {
16049
+ logger$a.warn("Failed to extract content for suggest:", err);
15894
16050
  return "Could not read page. Try navigate to a working URL.";
15895
16051
  }
15896
16052
  const suggestions = [];
@@ -15995,7 +16151,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
15995
16151
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
15996
16152
  if (firstSel) {
15997
16153
  const beforeUrl = wc.getURL();
15998
- const submitResult = await submitForm$1(wc, { selector: firstSel });
16154
+ const submitResult = await submitForm(wc, { selector: firstSel });
15999
16155
  await waitForPotentialNavigation(wc, beforeUrl);
16000
16156
  const afterUrl = wc.getURL();
16001
16157
  results.push(
@@ -16043,13 +16199,13 @@ ${results.join("\n")}`;
16043
16199
  );
16044
16200
  if (!passSel)
16045
16201
  return "Error: Could not find password field. Try providing password_selector.";
16046
- const userResult = await setElementValue$1(
16202
+ const userResult = await setElementValue(
16047
16203
  wc,
16048
16204
  userSel,
16049
16205
  String(args.username || "")
16050
16206
  );
16051
16207
  steps.push(userResult);
16052
- const passResult = await setElementValue$1(
16208
+ const passResult = await setElementValue(
16053
16209
  wc,
16054
16210
  passSel,
16055
16211
  String(args.password || "")
@@ -16057,7 +16213,7 @@ ${results.join("\n")}`;
16057
16213
  steps.push(passResult);
16058
16214
  const beforeUrl = wc.getURL();
16059
16215
  if (args.submit_selector) {
16060
- await clickResolvedSelector$1(wc, args.submit_selector);
16216
+ await clickResolvedSelector(wc, args.submit_selector);
16061
16217
  } else {
16062
16218
  const clicked = await executePageScript(
16063
16219
  wc,
@@ -16096,7 +16252,7 @@ ${steps.join("\n")}`;
16096
16252
  if (!wc) return "Error: No active tab";
16097
16253
  const beforeUrl = wc.getURL();
16098
16254
  if (args.selector) {
16099
- return clickResolvedSelector$1(wc, args.selector);
16255
+ return clickResolvedSelector(wc, args.selector);
16100
16256
  }
16101
16257
  const isNext = args.direction === "next";
16102
16258
  const clicked = await executePageScript(
@@ -17221,6 +17377,7 @@ const ALGORITHM = "aes-256-gcm";
17221
17377
  const IV_LENGTH = 12;
17222
17378
  const AUTH_TAG_LENGTH = 16;
17223
17379
  let cachedEntries = null;
17380
+ const logger$9 = createLogger("Vault");
17224
17381
  function getVaultDir() {
17225
17382
  return electron.app.getPath("userData");
17226
17383
  }
@@ -17287,7 +17444,7 @@ function loadVault() {
17287
17444
  cachedEntries = JSON.parse(json);
17288
17445
  return cachedEntries;
17289
17446
  } catch (err) {
17290
- console.error("[Vessel Vault] Failed to load vault:", err);
17447
+ logger$9.error("Failed to load vault:", err);
17291
17448
  throw new Error(
17292
17449
  "Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
17293
17450
  );
@@ -17429,6 +17586,7 @@ async function requestConsent(request) {
17429
17586
  }
17430
17587
  const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
17431
17588
  const MAX_ENTRIES = 1e3;
17589
+ const logger$8 = createLogger("VaultAudit");
17432
17590
  function getAuditPath() {
17433
17591
  return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
17434
17592
  }
@@ -17438,7 +17596,7 @@ function appendAuditEntry(entry) {
17438
17596
  fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
17439
17597
  fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
17440
17598
  } catch (err) {
17441
- console.error("[Vessel Vault] Failed to write audit log:", err);
17599
+ logger$8.error("Failed to write audit log:", err);
17442
17600
  }
17443
17601
  }
17444
17602
  function readAuditLog(limit = 100) {
@@ -17448,12 +17606,13 @@ function readAuditLog(limit = 100) {
17448
17606
  const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
17449
17607
  return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
17450
17608
  } catch (err) {
17451
- console.error("[Vessel Vault] Failed to read audit log:", err);
17609
+ logger$8.error("Failed to read audit log:", err);
17452
17610
  return [];
17453
17611
  }
17454
17612
  }
17455
17613
  let httpServer = null;
17456
17614
  let mcpAuthToken = null;
17615
+ const logger$7 = createLogger("MCP");
17457
17616
  const MCP_AUTH_FILENAME = "mcp-auth.json";
17458
17617
  function getMcpAuthFilePath() {
17459
17618
  const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
@@ -17489,7 +17648,7 @@ function writeMcpAuthFile(endpoint, token) {
17489
17648
  { mode: 384 }
17490
17649
  );
17491
17650
  } catch (err) {
17492
- console.warn("[Vessel MCP] Failed to write auth file:", err);
17651
+ logger$7.warn("Failed to write auth file:", err);
17493
17652
  }
17494
17653
  }
17495
17654
  function clearMcpAuthFile() {
@@ -17514,15 +17673,17 @@ function clearMcpAuthFile() {
17514
17673
  { mode: 384 }
17515
17674
  );
17516
17675
  } catch (err) {
17517
- console.warn("[Vessel MCP] Failed to clear auth file:", err);
17676
+ logger$7.warn("Failed to clear auth file:", err);
17518
17677
  }
17519
17678
  }
17520
17679
  function asTextResponse(text) {
17521
17680
  return { content: [{ type: "text", text }] };
17522
17681
  }
17523
- async function loadPermittedUrl(wc, url) {
17524
- assertPermittedNavigationURL(url);
17525
- await wc.loadURL(url);
17682
+ function asErrorTextResponse(message) {
17683
+ return asTextResponse(`Error: ${message}`);
17684
+ }
17685
+ function asNoActiveTabResponse() {
17686
+ return asErrorTextResponse("No active tab");
17526
17687
  }
17527
17688
  function asPromptResponse(text) {
17528
17689
  return {
@@ -17537,6 +17698,9 @@ function asPromptResponse(text) {
17537
17698
  ]
17538
17699
  };
17539
17700
  }
17701
+ function isDangerousMcpAction(name) {
17702
+ return name === "close_tab" || isDangerousAction(name);
17703
+ }
17540
17704
  function getActiveTabSummary(tabManager) {
17541
17705
  const activeTab = tabManager.getActiveTab();
17542
17706
  const activeTabId = tabManager.getActiveTabId();
@@ -17553,1298 +17717,162 @@ function getActiveTabSummary(tabManager) {
17553
17717
  humanFocused: true
17554
17718
  };
17555
17719
  }
17556
- function formatFolderStatus(limit = 6) {
17557
- const folders = listFolderOverviews();
17558
- const visible = folders.slice(0, limit);
17559
- const summary = visible.map((folder) => `${folder.name} (${folder.count})`).join(", ");
17560
- return `Folder status: ${summary}${folders.length > limit ? ", ..." : ""}`;
17561
- }
17562
- function describeFolder(folderId) {
17563
- if (!folderId || folderId === UNSORTED_ID) {
17564
- return "Unsorted";
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);
17767
+ }
17768
+ return `${warning}
17769
+ [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
17565
17770
  }
17566
- return getFolder(folderId)?.name ?? folderId;
17567
- }
17568
- function resolveBookmarkFolderTarget(args) {
17569
- const folderId = typeof args.folder_id === "string" ? args.folder_id.trim() : "";
17570
- if (folderId) {
17571
- if (folderId === UNSORTED_ID) {
17572
- return {
17573
- folderId: UNSORTED_ID,
17574
- folderName: "Unsorted"
17575
- };
17576
- }
17577
- const folder2 = getFolder(folderId);
17578
- if (!folder2) {
17579
- return { folderName: "Unsorted", error: `Folder ${folderId} not found` };
17580
- }
17581
- return { folderId: folder2.id, folderName: folder2.name };
17582
- }
17583
- const requestedName = typeof args.folder_name === "string" && args.folder_name.trim() ? args.folder_name.trim() : args.archive ? ARCHIVE_FOLDER_NAME : "";
17584
- if (!requestedName || requestedName.toLowerCase() === "unsorted") {
17585
- return {
17586
- folderId: UNSORTED_ID,
17587
- folderName: "Unsorted"
17588
- };
17589
- }
17590
- const existing = findFolderByName(requestedName);
17591
- if (existing) {
17592
- return { folderId: existing.id, folderName: existing.name };
17593
- }
17594
- const createIfMissing = args.create_folder_if_missing !== false;
17595
- if (!createIfMissing) {
17596
- return {
17597
- folderName: requestedName,
17598
- error: `Folder "${requestedName}" not found`
17599
- };
17600
- }
17601
- const folderSummary = typeof args.folder_summary === "string" && args.folder_summary.trim() ? args.folder_summary.trim() : void 0;
17602
- const { folder } = ensureFolder(requestedName, folderSummary);
17603
- return {
17604
- folderId: folder.id,
17605
- folderName: folder.name,
17606
- createdFolder: folder.name
17607
- };
17608
- }
17609
- function composeFolderAwareResponse(message, createdFolder) {
17610
- const prefix = createdFolder ? `Created folder "${createdFolder}".
17611
- ` : "";
17612
- return `${prefix}${message}
17613
- ${formatFolderStatus()}`;
17614
- }
17615
- function composeDuplicateBookmarkResponse(args) {
17616
- 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.`;
17617
- }
17618
- async function scrollPage(wc, deltaY) {
17619
- const getScrollY = () => wc.executeJavaScript(`
17620
- (function() {
17621
- return Math.max(
17622
- window.scrollY || 0,
17623
- window.pageYOffset || 0,
17624
- document.scrollingElement?.scrollTop || 0,
17625
- document.documentElement?.scrollTop || 0,
17626
- document.body?.scrollTop || 0,
17627
- );
17628
- })()
17629
- `);
17630
- const beforeY = await getScrollY();
17631
- await wc.executeJavaScript(`window.scrollBy(0, ${deltaY})`);
17632
- await sleep(100);
17633
- const afterY = await getScrollY();
17634
- return {
17635
- beforeY,
17636
- afterY,
17637
- movedY: Math.round(afterY - beforeY)
17638
- };
17639
- }
17640
- async function clickElement(wc, selector) {
17641
- const target = await wc.executeJavaScript(`
17642
- (async function() {
17643
- function matchesTarget(candidate, el) {
17644
- return !!candidate && (candidate === el || el.contains(candidate) || candidate.contains(el));
17645
- }
17646
-
17647
- function samplePoints(rect) {
17648
- const width = window.innerWidth || document.documentElement?.clientWidth || 0;
17649
- const height = window.innerHeight || document.documentElement?.clientHeight || 0;
17650
- const insetX = Math.min(12, rect.width / 4);
17651
- const insetY = Math.min(12, rect.height / 4);
17652
- const raw = [
17653
- [rect.left + rect.width / 2, rect.top + rect.height / 2],
17654
- [rect.left + insetX, rect.top + insetY],
17655
- [rect.right - insetX, rect.top + insetY],
17656
- [rect.left + insetX, rect.bottom - insetY],
17657
- [rect.right - insetX, rect.bottom - insetY],
17658
- ];
17659
- return raw.map(([x, y]) => ({
17660
- x: Math.min(Math.max(1, x), Math.max(1, width - 1)),
17661
- y: Math.min(Math.max(1, y), Math.max(1, height - 1)),
17662
- }));
17663
- }
17664
-
17665
- const el = document.querySelector(${JSON.stringify(selector)});
17666
- if (!el) return { error: "Element not found" };
17667
-
17668
- if (el instanceof HTMLElement) {
17669
- el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
17670
- }
17671
-
17672
- await new Promise((resolve) => {
17673
- let settled = false;
17674
- const finish = () => {
17675
- if (settled) return;
17676
- settled = true;
17677
- resolve(undefined);
17678
- };
17679
- if (
17680
- typeof requestAnimationFrame === "function" &&
17681
- document.visibilityState === "visible"
17682
- ) {
17683
- requestAnimationFrame(() => finish());
17684
- }
17685
- setTimeout(finish, 32);
17686
- });
17687
-
17688
- const rect = el.getBoundingClientRect();
17689
- if (rect.width <= 0 || rect.height <= 0) {
17690
- 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." };
17691
- }
17692
-
17693
- const points = samplePoints(rect);
17694
- const hit = points.find((point) => matchesTarget(document.elementFromPoint(point.x, point.y), el));
17695
- const chosen = hit || points[0];
17696
- const top = document.elementFromPoint(chosen.x, chosen.y);
17697
-
17698
- return {
17699
- x: Math.round(chosen.x),
17700
- y: Math.round(chosen.y),
17701
- obstructed: !matchesTarget(top, el),
17702
- hiddenWindow: document.visibilityState !== "visible",
17703
- };
17704
- })()
17705
- `);
17706
- if (!target || typeof target !== "object") {
17707
- return "Error: Could not resolve click target";
17708
- }
17709
- if ("error" in target && typeof target.error === "string") {
17710
- return `Error: ${target.error}`;
17711
- }
17712
- const x = typeof target.x === "number" ? target.x : null;
17713
- const y = typeof target.y === "number" ? target.y : null;
17714
- const hiddenWindow = target.hiddenWindow === true;
17715
- if (x == null || y == null) {
17716
- return "Error: Could not resolve click coordinates";
17717
- }
17718
- if (hiddenWindow) {
17719
- const activationResult = await activateElement(wc, selector);
17720
- if (activationResult.startsWith("Error:")) {
17721
- return activationResult;
17722
- }
17723
- await sleep(80);
17724
- return "Clicked via DOM activation";
17725
- }
17726
- wc.sendInputEvent({ type: "mouseMove", x, y });
17727
- await sleep(16);
17728
- wc.sendInputEvent({ type: "mouseDown", x, y, button: "left", clickCount: 1 });
17729
- await sleep(24);
17730
- wc.sendInputEvent({ type: "mouseUp", x, y, button: "left", clickCount: 1 });
17731
- await sleep(80);
17732
- return target.obstructed ? "Clicked via pointer events (target may be partially obstructed)" : "Clicked via pointer events";
17733
- }
17734
- async function activateElement(wc, selector) {
17735
- const activated = await wc.executeJavaScript(`
17736
- (function() {
17737
- const el = document.querySelector(${JSON.stringify(selector)});
17738
- if (!el) return { error: "Element not found" };
17739
- if (el instanceof HTMLElement) {
17740
- el.focus({ preventScroll: true });
17741
- }
17742
- if (typeof el.click === "function") {
17743
- el.click();
17744
- return { ok: true };
17745
- }
17746
- return { error: "Element is not clickable" };
17747
- })()
17748
- `);
17749
- if (!activated || typeof activated !== "object") {
17750
- return "Error: Could not activate element";
17751
- }
17752
- if ("error" in activated && typeof activated.error === "string") {
17753
- return `Error: ${activated.error}`;
17754
- }
17755
- return "Activated element via DOM click";
17756
- }
17757
- async function describeElementForClick(wc, selector) {
17758
- const result = await wc.executeJavaScript(`
17759
- (function() {
17760
- const el = document.querySelector(${JSON.stringify(selector)});
17761
- if (!el) return { error: "Element not found" };
17762
- const anchor = el instanceof HTMLAnchorElement ? el : el.closest("a[href]");
17763
- const text = (el.textContent || el.tagName || "Element").trim().slice(0, 100);
17764
- return {
17765
- text: text || "Element",
17766
- href: anchor instanceof HTMLAnchorElement ? anchor.href : undefined,
17767
- };
17768
- })()
17769
- `);
17770
- if (!result || typeof result !== "object") {
17771
- return { error: "Element not found" };
17772
- }
17773
- if ("error" in result && typeof result.error === "string") {
17774
- return { error: result.error };
17775
- }
17776
- return {
17777
- text: "text" in result && typeof result.text === "string" ? result.text : "Element",
17778
- href: "href" in result && typeof result.href === "string" ? result.href : void 0
17779
- };
17780
- }
17781
- async function clickResolvedSelector(wc, selector) {
17782
- if (selector.startsWith("__vessel_idx:")) {
17783
- const idx = Number(selector.slice("__vessel_idx:".length));
17784
- const beforeUrl2 = wc.getURL();
17785
- const idxLabel = await wc.executeJavaScript(
17786
- `window.__vessel?.getElementText?.(${idx}) || ""`
17787
- );
17788
- if (typeof idxLabel === "string" && isAddToCartText(idxLabel) && isDuplicateCartClick(beforeUrl2, idxLabel)) {
17789
- 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).`;
17790
- }
17791
- const result = await wc.executeJavaScript(
17792
- `window.__vessel?.interactByIndex?.(${idx}, "click") || "Error: interactByIndex not available"`
17793
- );
17794
- if (typeof result === "string" && result.startsWith("Error")) return result;
17795
- if (typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
17796
- recordCartClick(beforeUrl2, idxLabel);
17797
- }
17798
- await waitForPotentialNavigation$1(wc, beforeUrl2);
17799
- const afterUrl2 = wc.getURL();
17800
- if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
17801
- let overlayHint2 = await detectPostClickOverlay(wc);
17802
- if (!overlayHint2 && typeof idxLabel === "string" && isAddToCartText(idxLabel)) {
17803
- await sleep(1200);
17804
- overlayHint2 = await detectPostClickOverlay(wc);
17805
- }
17806
- if (!overlayHint2) {
17807
- const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
17808
- if (hrefMatch) {
17809
- try {
17810
- await loadPermittedUrl(wc, hrefMatch[1]);
17811
- await waitForLoad(wc, 8e3);
17812
- const hrefUrl = wc.getURL();
17813
- if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
17814
- } catch {
17815
- }
17816
- }
17817
- return result;
17818
- }
17819
- const dialogActions = typeof idxLabel === "string" && isAddToCartText(idxLabel) ? await getCartDialogActions(wc) : null;
17820
- const actionsSuffix = dialogActions ? `
17821
- ${dialogActions}
17822
- Click one of these dialog actions. Do NOT click any other element.` : "";
17823
- return `${result}
17824
- ${overlayHint2}${actionsSuffix}`;
17825
- }
17826
- if (selector.includes(" >>> ")) {
17827
- const beforeUrl2 = wc.getURL();
17828
- const shadowLabel = await wc.executeJavaScript(`
17829
- (function() {
17830
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
17831
- return el ? (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || "") : "";
17832
- })()
17833
- `);
17834
- if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel) && isDuplicateCartClick(beforeUrl2, shadowLabel)) {
17835
- 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).`;
17836
- }
17837
- const result = await wc.executeJavaScript(`
17838
- (function() {
17839
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
17840
- if (!el || !document.contains(el)) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
17841
- if (el instanceof HTMLElement) { el.focus(); el.click(); }
17842
- var anchor = el instanceof HTMLAnchorElement ? el : el.closest('a[href]');
17843
- var href = anchor instanceof HTMLAnchorElement ? anchor.href : null;
17844
- return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase()) + (href ? "\\nhref: " + href : "");
17845
- })()
17846
- `);
17847
- if (typeof result === "string" && result.startsWith("Error")) return result;
17848
- if (typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
17849
- recordCartClick(beforeUrl2, shadowLabel);
17850
- }
17851
- await waitForPotentialNavigation$1(wc, beforeUrl2);
17852
- const afterUrl2 = wc.getURL();
17853
- if (afterUrl2 !== beforeUrl2) return `${result} -> ${afterUrl2}`;
17854
- let overlayHint2 = await detectPostClickOverlay(wc);
17855
- if (!overlayHint2 && typeof shadowLabel === "string" && isAddToCartText(shadowLabel)) {
17856
- await sleep(1200);
17857
- overlayHint2 = await detectPostClickOverlay(wc);
17858
- }
17859
- if (!overlayHint2) {
17860
- const hrefMatch = typeof result === "string" ? result.match(/\nhref: (https?:\/\/\S+)/) : null;
17861
- if (hrefMatch) {
17862
- try {
17863
- await loadPermittedUrl(wc, hrefMatch[1]);
17864
- await waitForLoad(wc, 8e3);
17865
- const hrefUrl = wc.getURL();
17866
- if (hrefUrl !== beforeUrl2) return `${result.split("\n")[0]} -> ${hrefUrl}`;
17867
- } catch {
17868
- }
17869
- }
17870
- return result;
17871
- }
17872
- const dialogActions2 = typeof shadowLabel === "string" && isAddToCartText(shadowLabel) ? await getCartDialogActions(wc) : null;
17873
- const actionsSuffix2 = dialogActions2 ? `
17874
- ${dialogActions2}
17875
- Click one of these dialog actions. Do NOT click any other element.` : "";
17876
- return `${result}
17877
- ${overlayHint2}${actionsSuffix2}`;
17878
- }
17879
- const beforeUrl = wc.getURL();
17880
- const elInfo = await describeElementForClick(wc, selector);
17881
- if ("error" in elInfo) return `Error: ${elInfo.error}`;
17882
- const cartMatch = isAddToCartText(elInfo.text);
17883
- if (cartMatch && isDuplicateCartClick(beforeUrl, elInfo.text)) {
17884
- 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).`;
17885
- }
17886
- if (!cartMatch) {
17887
- const dialogActions = await getCartDialogActions(wc);
17888
- if (dialogActions) {
17889
- return `Blocked: a cart confirmation dialog is open. Do not click background elements.
17890
- ${dialogActions}
17891
- Click one of these dialog actions instead.`;
17892
- }
17893
- }
17894
- if (elInfo.href) {
17895
- const validation = await validateLinkDestination(elInfo.href);
17896
- if (validation.status === "dead") {
17897
- return formatDeadLinkMessage(elInfo.text, validation);
17898
- }
17899
- }
17900
- if (cartMatch) {
17901
- recordCartClick(beforeUrl, elInfo.text);
17902
- }
17903
- const clickText = `Clicked: ${elInfo.text}`;
17904
- const clickResult = await clickElement(wc, selector);
17905
- if (clickResult.startsWith("Error:")) return clickResult;
17906
- await waitForPotentialNavigation$1(wc, beforeUrl);
17907
- const afterUrl = wc.getURL();
17908
- if (afterUrl !== beforeUrl) {
17909
- return `${clickText} -> ${afterUrl}`;
17910
- }
17911
- const overlayHint = await detectPostClickOverlay(wc);
17912
- if (overlayHint) {
17913
- const dialogActions = cartMatch ? await getCartDialogActions(wc) : null;
17914
- const actionsSuffix = dialogActions ? `
17915
- ${dialogActions}
17916
- Click one of these dialog actions. Do NOT click any other element.` : "";
17917
- return `${clickText} (${clickResult})
17918
- ${overlayHint}${actionsSuffix}`;
17919
- }
17920
- if (cartMatch) {
17921
- await sleep(1200);
17922
- const delayedOverlayHint = await detectPostClickOverlay(wc);
17923
- if (delayedOverlayHint) {
17924
- const dialogActions = await getCartDialogActions(wc);
17925
- const actionsSuffix = dialogActions ? `
17926
- ${dialogActions}
17927
- Click one of these dialog actions. Do NOT click any other element.` : "";
17928
- return `${clickText} (${clickResult})
17929
- ${delayedOverlayHint}${actionsSuffix}`;
17930
- }
17931
- return `${clickText} (${clickResult})`;
17932
- }
17933
- const activationResult = await activateElement(wc, selector);
17934
- if (!activationResult.startsWith("Error:")) {
17935
- await waitForPotentialNavigation$1(wc, beforeUrl);
17936
- const fallbackUrl = wc.getURL();
17937
- if (fallbackUrl !== beforeUrl) {
17938
- return `${clickText} -> ${fallbackUrl} (recovered via DOM activation)`;
17939
- }
17940
- }
17941
- const postActivationOverlayHint = await detectPostClickOverlay(wc);
17942
- if (postActivationOverlayHint) {
17943
- return `${clickText} (${clickResult})
17944
- ${postActivationOverlayHint}`;
17945
- }
17946
- return `${clickText} (${clickResult})`;
17947
- }
17948
- async function getCartDialogActions(wc) {
17949
- const result = await wc.executeJavaScript(`
17950
- (function() {
17951
- function isVisible(el) {
17952
- if (!(el instanceof HTMLElement)) return false;
17953
- const style = getComputedStyle(el);
17954
- if (style.display === "none" || style.visibility === "hidden") return false;
17955
- const rect = el.getBoundingClientRect();
17956
- return rect.width >= 20 && rect.height >= 10;
17957
- }
17958
-
17959
- function findDialogRoot() {
17960
- const selectors = [
17961
- '[data-test="basket-flyout"]',
17962
- '[role="dialog"]',
17963
- 'dialog[open]',
17964
- '[role="alertdialog"]',
17965
- '[aria-modal="true"]',
17966
- ];
17967
- for (const selector of selectors) {
17968
- const nodes = document.querySelectorAll(selector);
17969
- for (const node of nodes) {
17970
- if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
17971
- const text = (node.textContent || "").slice(0, 800).toLowerCase();
17972
- const cartSignals = [
17973
- "added to cart", "added to bag", "added to basket",
17974
- "item added", "your basket", "your cart", "your bag",
17975
- "view basket", "view cart", "continue shopping",
17976
- ];
17977
- if (cartSignals.some((signal) => text.includes(signal))) {
17978
- return node;
17979
- }
17980
- }
17981
- }
17982
- return null;
17983
- }
17984
-
17985
- const dialog = findDialogRoot();
17986
- if (!dialog) return { found: false, actions: [] };
17987
-
17988
- const actions = [];
17989
- dialog.querySelectorAll('button, a[href], [role="button"]').forEach((el) => {
17990
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
17991
- const label = (el.getAttribute("aria-label") || el.textContent || "").trim().slice(0, 80);
17992
- if (!label || label.length < 2) return;
17993
- const href = el.getAttribute("href") || "";
17994
- const selector = el.id ? "#" + el.id
17995
- : el.getAttribute("data-test") ? '[data-test="' + el.getAttribute("data-test") + '"]'
17996
- : el.getAttribute("aria-label") ? '[aria-label="' + el.getAttribute("aria-label") + '"]'
17997
- : null;
17998
- if (selector) {
17999
- actions.push({ label: label, href: href, selector: selector });
18000
- }
18001
- });
18002
-
18003
- return {
18004
- found: true,
18005
- actions: actions.map((action) =>
18006
- '- "' + action.label + '"' +
18007
- (action.href ? ' -> ' + action.href : "") +
18008
- (action.selector ? ' (selector: ' + action.selector + ')' : "")
18009
- ),
18010
- };
18011
- })()
18012
- `);
18013
- if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
18014
- return null;
18015
- }
18016
- if (!("actions" in result) || !Array.isArray(result.actions) || result.actions.length === 0) {
18017
- return null;
18018
- }
18019
- return `Available dialog actions:
18020
- ${result.actions.join("\n")}`;
18021
- }
18022
- async function detectPostClickOverlay(wc) {
18023
- const result = await wc.executeJavaScript(`
18024
- (function() {
18025
- var vw = window.innerWidth || document.documentElement.clientWidth;
18026
- var vh = window.innerHeight || document.documentElement.clientHeight;
18027
- var vpArea = Math.max(1, vw * vh);
18028
-
18029
- function isVisible(el) {
18030
- if (!(el instanceof HTMLElement)) return false;
18031
- var style = getComputedStyle(el);
18032
- if (style.display === "none" || style.visibility === "hidden") return false;
18033
- return el.getBoundingClientRect().width > 0;
18034
- }
18035
-
18036
- function hasFixedAncestor(el) {
18037
- var current = el.parentElement;
18038
- while (current && current !== document.body) {
18039
- var position = getComputedStyle(current).position;
18040
- if (position === "fixed" || position === "sticky") return true;
18041
- current = current.parentElement;
18042
- }
18043
- return false;
18044
- }
18045
-
18046
- function effectiveZ(el) {
18047
- var current = el;
18048
- while (current && current !== document.body) {
18049
- var z = parseInt(getComputedStyle(current).zIndex, 10);
18050
- if (z > 0) return z;
18051
- current = current.parentElement;
18052
- }
18053
- return 0;
18054
- }
18055
-
18056
- function touchesViewportEdge(rect) {
18057
- return rect.left <= 24 || rect.top <= 24 ||
18058
- rect.right >= vw - 24 || rect.bottom >= vh - 24;
18059
- }
18060
-
18061
- var cartPhrases = [
18062
- "added to cart", "added to bag", "added to basket",
18063
- "added to your cart", "added to your bag", "added to your basket",
18064
- ];
18065
- var cartActions = [
18066
- "view cart", "go to cart", "view basket", "go to basket",
18067
- "continue shopping", "keep shopping", "checkout",
18068
- ];
18069
-
18070
- var selectors = 'dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"], [data-test="basket-flyout"]';
18071
- var candidates = document.querySelectorAll(selectors);
18072
- var hit = null;
18073
- for (var i = 0; i < candidates.length; i++) {
18074
- if (isVisible(candidates[i])) {
18075
- hit = candidates[i];
18076
- break;
18077
- }
18078
- }
18079
-
18080
- if (!hit) {
18081
- var elements = document.querySelectorAll("*");
18082
- for (var j = 0; j < elements.length; j++) {
18083
- var el = elements[j];
18084
- if (!(el instanceof HTMLElement) || !isVisible(el)) continue;
18085
- var style = getComputedStyle(el);
18086
- var position = style.position;
18087
- var isFixed = position === "fixed" || position === "sticky";
18088
- var isAbsolute = position === "absolute";
18089
- if (!isFixed && !isAbsolute) continue;
18090
- if (isAbsolute && !hasFixedAncestor(el)) continue;
18091
- if (effectiveZ(el) < 5) continue;
18092
- var rect = el.getBoundingClientRect();
18093
- var areaRatio = (rect.width * rect.height) / vpArea;
18094
- if (rect.width >= 160 && rect.height >= 100 && areaRatio >= 0.05 && touchesViewportEdge(rect)) {
18095
- hit = el;
18096
- break;
18097
- }
18098
- }
18099
- }
18100
-
18101
- if (!hit) return { found: false, label: "", cartLike: false };
18102
- var text = (hit.textContent || "").slice(0, 800).toLowerCase();
18103
- var cartLike = cartPhrases.concat(cartActions).some(function(signal) {
18104
- return text.indexOf(signal) !== -1;
18105
- });
18106
- var heading = hit.querySelector("h1,h2,h3,h4");
18107
- var label = (hit.getAttribute("aria-label") || (heading && heading.textContent) || "").trim().slice(0, 80);
18108
- return { found: true, label: label, cartLike: cartLike };
18109
- })()
18110
- `);
18111
- if (!result || typeof result !== "object" || !("found" in result) || !result.found) {
18112
- return null;
18113
- }
18114
- const label = typeof result.label === "string" && result.label ? ` ("${result.label}")` : "";
18115
- if ("cartLike" in result && result.cartLike) {
18116
- return `A cart confirmation dialog appeared${label}. Call read_page to see available actions — do not click Add to Cart again.`;
18117
- }
18118
- return `A dialog or overlay appeared${label}. Call read_page to see available actions.`;
18119
- }
18120
- async function dismissPopup(wc) {
18121
- const before = await extractContent(wc);
18122
- const initialBlocking = before.overlays.filter(
18123
- (overlay) => overlay.blocksInteraction
18124
- ).length;
18125
- if (initialBlocking > 0) {
18126
- const overlayText = before.overlays.map(
18127
- (o) => [o.label, o.text].filter(Boolean).join(" ")
18128
- ).join(" ").toLowerCase();
18129
- const cartSignals = [
18130
- "added to cart",
18131
- "added to bag",
18132
- "added to basket",
18133
- "item added",
18134
- "items in your basket",
18135
- "items in your cart",
18136
- "items in your bag",
18137
- "your basket",
18138
- "your cart",
18139
- "your bag",
18140
- "view basket",
18141
- "view cart",
18142
- "continue shopping"
18143
- ];
18144
- if (cartSignals.some((s) => overlayText.includes(s))) {
18145
- const continueResult = await wc.executeJavaScript(`
18146
- (function() {
18147
- var dialog = document.querySelector('[role="dialog"], dialog[open], [role="alertdialog"], [aria-modal="true"]');
18148
- if (!dialog) return "Error: dialog not found";
18149
- var buttons = dialog.querySelectorAll('button, a[href], [role="button"]');
18150
- var continueBtn = null;
18151
- var viewCartBtn = null;
18152
- for (var i = 0; i < buttons.length; i++) {
18153
- var label = (buttons[i].getAttribute('aria-label') || buttons[i].textContent || '').trim().toLowerCase();
18154
- if (/continue shopping|keep shopping/.test(label)) { continueBtn = buttons[i]; break; }
18155
- if (/view (basket|cart|bag)|checkout/.test(label) && !viewCartBtn) { viewCartBtn = buttons[i]; }
18156
- }
18157
- var target = continueBtn || viewCartBtn;
18158
- if (!target) return "Error: no dialog action found";
18159
- var actionLabel = (target.getAttribute('aria-label') || target.textContent || '').trim();
18160
- if (target.tagName === 'A' && target.href) {
18161
- window.location.href = target.href;
18162
- return "Clicked: " + actionLabel + " -> " + target.href;
18163
- }
18164
- target.click();
18165
- return "Clicked: " + actionLabel;
18166
- })()
18167
- `);
18168
- if (typeof continueResult === "string" && !continueResult.startsWith("Error")) {
18169
- return `Cart confirmation handled: ${continueResult}. Item was already added to your cart.`;
18170
- }
18171
- 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.";
18172
- }
18173
- }
18174
- const initialDormant = before.dormantOverlays.length;
18175
- const candidates = await wc.executeJavaScript(`
18176
- (function() {
18177
- function text(value) {
18178
- const trimmed = value == null ? "" : String(value).trim();
18179
- return trimmed || "";
18180
- }
18181
-
18182
- ${selectorHelpersJS(["data-testid", "data-test", "aria-label", "name", "title"])}
18183
-
18184
- function isVisible(el) {
18185
- if (!(el instanceof HTMLElement)) return true;
18186
- const style = window.getComputedStyle(el);
18187
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
18188
- return false;
18189
- }
18190
- if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") {
18191
- return false;
18192
- }
18193
- const rect = el.getBoundingClientRect();
18194
- return rect.width > 0 && rect.height > 0;
18195
- }
18196
-
18197
- function overlayRoots() {
18198
- const nodes = [];
18199
- document.querySelectorAll("dialog, [role='dialog'], [role='alertdialog'], [aria-modal='true']").forEach((el) => {
18200
- if (isVisible(el)) nodes.push(el);
18201
- });
18202
- // Detect known consent manager containers
18203
- 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) => {
18204
- if (el instanceof HTMLElement && isVisible(el)) nodes.push(el);
18205
- });
18206
- document.querySelectorAll("body *").forEach((el) => {
18207
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
18208
- const style = window.getComputedStyle(el);
18209
- const rect = el.getBoundingClientRect();
18210
- const zIndex = Number.parseInt(style.zIndex, 10);
18211
- const coversCenter =
18212
- rect.left <= (window.innerWidth || 0) / 2 &&
18213
- rect.right >= (window.innerWidth || 0) / 2 &&
18214
- rect.top <= (window.innerHeight || 0) / 2 &&
18215
- rect.bottom >= (window.innerHeight || 0) / 2;
18216
- if (
18217
- (style.position === "fixed" || style.position === "sticky") &&
18218
- Number.isFinite(zIndex) &&
18219
- zIndex >= 10 &&
18220
- coversCenter
18221
- ) {
18222
- nodes.push(el);
18223
- }
18224
- });
18225
- return Array.from(new Set(nodes));
18226
- }
18227
-
18228
- function scoreCandidate(el, rooted) {
18229
- const label = text(
18230
- el.getAttribute("aria-label") ||
18231
- el.getAttribute("title") ||
18232
- el.textContent ||
18233
- el.getAttribute("value"),
18234
- ).toLowerCase();
18235
- const classText = text(typeof el.className === "string" ? el.className : "").toLowerCase();
18236
- const idText = text(el.id).toLowerCase();
18237
- const combined = classText + " " + idText;
18238
- let score = rooted ? 30 : 0;
18239
- if (/^x$|^×$/.test(label)) score += 120;
18240
- if (/no thanks|no, thanks|not now|maybe later|dismiss|close|skip|cancel|continue without|no thank you|reject|decline/.test(label)) score += 100;
18241
- if (/close|dismiss|modal-close|overlay-close/.test(combined)) score += 90;
18242
- if (/onetrust-close|onetrust-reject|cookie.*close|consent.*close|cookie.*reject|consent.*reject/.test(combined)) score += 110;
18243
- if (/onetrust-accept|cookie.*accept|consent.*accept/.test(combined)) score += 80;
18244
- if (el.getAttribute("aria-label")) score += 20;
18245
- if (/accept|continue|submit|sign up|subscribe|join|start|next/.test(label) && !/cookie|consent|onetrust/.test(combined)) score -= 80;
18246
- const rect = el.getBoundingClientRect();
18247
- if (rect.top < 120) score += 10;
18248
- if (rect.right > (window.innerWidth || 0) - 120) score += 15;
18249
- return score;
18250
- }
18251
-
18252
- const selector = "button, [role='button'], a[href], input[type='button'], input[type='submit'], [aria-label], [title]";
18253
- const results = [];
18254
- const roots = overlayRoots();
18255
-
18256
- function collect(container, rooted) {
18257
- container.querySelectorAll(selector).forEach((el) => {
18258
- if (!(el instanceof HTMLElement) || !isVisible(el)) return;
18259
- const candidateSelector = selectorFor(el);
18260
- if (!candidateSelector) return;
18261
- var label = text(
18262
- el.getAttribute("aria-label") ||
18263
- el.getAttribute("title") ||
18264
- el.textContent ||
18265
- el.getAttribute("value"),
18266
- );
18267
- if (!label) {
18268
- var idLower = (el.id || "").toLowerCase();
18269
- var classLower = (typeof el.className === "string" ? el.className : "").toLowerCase();
18270
- var combined = idLower + " " + classLower;
18271
- if (/onetrust|consent|cookie|banner|gdpr|trustarc|cookiebot/.test(combined)) {
18272
- label = idLower.includes("accept") ? "Accept cookies"
18273
- : idLower.includes("reject") ? "Reject cookies"
18274
- : idLower.includes("close") || classLower.includes("close") ? "Close"
18275
- : "Consent button";
18276
- } else {
18277
- return;
18278
- }
18279
- }
18280
- results.push({
18281
- selector: candidateSelector,
18282
- label: label.slice(0, 120),
18283
- score: scoreCandidate(el, rooted),
18284
- });
18285
- });
18286
- }
18287
-
18288
- roots.forEach((root) => collect(root, true));
18289
- if (results.length === 0) {
18290
- collect(document, false);
18291
- }
18292
-
18293
- const seen = new Set();
18294
- return results
18295
- .filter((candidate) => {
18296
- if (seen.has(candidate.selector)) return false;
18297
- seen.add(candidate.selector);
18298
- return candidate.score > 0;
18299
- })
18300
- .sort((a, b) => b.score - a.score)
18301
- .slice(0, 8);
18302
- })()
18303
- `);
18304
- if (Array.isArray(candidates)) {
18305
- for (const candidate of candidates) {
18306
- if (!candidate || typeof candidate !== "object" || typeof candidate.selector !== "string") {
18307
- continue;
18308
- }
18309
- const result = await clickElement(wc, candidate.selector);
18310
- if (result.startsWith("Error:")) continue;
18311
- await sleep(250);
18312
- const after = await extractContent(wc);
18313
- const blocking = after.overlays.filter(
18314
- (overlay) => overlay.blocksInteraction
18315
- ).length;
18316
- if (blocking < initialBlocking || initialBlocking > 0 && blocking === 0) {
18317
- const label = typeof candidate.label === "string" && candidate.label ? candidate.label : "popup control";
18318
- return `Dismissed popup using "${label}"`;
18319
- }
18320
- }
18321
- }
18322
- wc.sendInputEvent({ type: "keyDown", keyCode: "Escape" });
18323
- await sleep(16);
18324
- wc.sendInputEvent({ type: "keyUp", keyCode: "Escape" });
18325
- await sleep(200);
18326
- const afterEscape = await extractContent(wc);
18327
- const escapeBlocking = afterEscape.overlays.filter(
18328
- (overlay) => overlay.blocksInteraction
18329
- ).length;
18330
- if (escapeBlocking < initialBlocking || initialBlocking > 0 && escapeBlocking === 0) {
18331
- return "Dismissed popup with Escape";
18332
- }
18333
- 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";
18334
- }
18335
- function isDangerousAction(name) {
18336
- return [
18337
- "navigate",
18338
- "click",
18339
- "type",
18340
- "select_option",
18341
- "submit_form",
18342
- "press_key",
18343
- "create_tab",
18344
- "switch_tab",
18345
- "close_tab",
18346
- "restore_checkpoint",
18347
- "login",
18348
- "fill_form",
18349
- "search",
18350
- "paginate"
18351
- ].includes(name);
18352
- }
18353
- function getTabByMatch(tabManager, match) {
18354
- const lowered = match.toLowerCase();
18355
- return tabManager.getAllStates().find(
18356
- (tab) => tab.title.toLowerCase().includes(lowered) || tab.url.toLowerCase().includes(lowered)
18357
- ) || null;
18358
- }
18359
- async function getPostActionState(tabManager, name) {
18360
- const tab = tabManager.getActiveTab();
18361
- if (!tab) return "";
18362
- const wc = tab.view.webContents;
18363
- const navActions = [
18364
- "navigate",
18365
- "go_back",
18366
- "go_forward",
18367
- "click",
18368
- "submit_form",
18369
- "reload",
18370
- "press_key"
18371
- ];
18372
- const interactActions = [
18373
- "type",
18374
- "type_text",
18375
- "select_option",
18376
- "hover",
18377
- "focus"
18378
- ];
18379
- const tabActions = ["create_tab", "switch_tab", "close_tab"];
18380
- if (navActions.includes(name)) {
18381
- let warning = "";
18382
- try {
18383
- const page = await extractContent(wc);
18384
- const issue = getRecoverableAccessIssue(page);
18385
- if (issue) {
18386
- const blockedUrl = wc.getURL();
18387
- const canRecover = [
18388
- "navigate",
18389
- "open_bookmark",
18390
- "click",
18391
- "submit_form",
18392
- "reload",
18393
- "press_key"
18394
- ].includes(name) && tab.canGoBack();
18395
- if (canRecover && tab.goBack()) {
18396
- await waitForLoad(wc);
18397
- warning = `
18398
- [warning: ${issue.summary} ${issue.recommendation ?? ""} Automatically returned to ${wc.getURL()} after landing on ${blockedUrl}.]`;
18399
- } else {
18400
- warning = `
18401
- [warning: ${issue.summary} ${issue.recommendation ?? ""}${tab.canGoBack() ? "" : " No previous page was available for automatic recovery."}]`;
18402
- }
18403
- }
18404
- } catch {
18405
- }
18406
- return `${warning}
18407
- [state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
18408
- }
18409
- if (interactActions.includes(name)) {
18410
- return `
18411
- [state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
18412
- }
18413
- if (tabActions.includes(name)) {
18414
- const activeId = tabManager.getActiveTabId();
18415
- const active = getActiveTabSummary(tabManager);
18416
- const count = tabManager.getAllStates().length;
18417
- return `
18418
- [state: activeTab=${activeId}, title=${JSON.stringify(active?.title ?? "")}, url=${active?.url ?? ""}, totalTabs=${count}]`;
18419
- }
18420
- return "";
18421
- }
18422
- async function withAction(runtime2, tabManager, name, args, executor) {
18423
- try {
18424
- const result = await runtime2.runControlledAction({
18425
- source: "mcp",
18426
- name,
18427
- args,
18428
- tabId: tabManager.getActiveTabId(),
18429
- dangerous: isDangerousAction(name),
18430
- executor
18431
- });
18432
- const stateInfo = await getPostActionState(tabManager, name);
18433
- const flowCtx = runtime2.getFlowContext();
18434
- return asTextResponse(result + stateInfo + flowCtx);
18435
- } catch (error) {
18436
- return asTextResponse(
18437
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
18438
- );
18439
- }
18440
- }
18441
- async function setElementValue(wc, selector, value) {
18442
- if (selector.startsWith("__vessel_idx:")) {
18443
- const idx = Number(selector.slice("__vessel_idx:".length));
18444
- return wc.executeJavaScript(
18445
- `window.__vessel?.interactByIndex?.(${idx}, "value", ${JSON.stringify(value)}) || "Error: interactByIndex not available"`
18446
- );
18447
- }
18448
- if (selector.includes(" >>> ")) {
18449
- return wc.executeJavaScript(`
18450
- (function() {
18451
- var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
18452
- if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
18453
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a text input";
18454
- var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
18455
- var desc = Object.getOwnPropertyDescriptor(proto, "value");
18456
- if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
18457
- el.focus();
18458
- el.dispatchEvent(new Event("input", { bubbles: true }));
18459
- el.dispatchEvent(new Event("change", { bubbles: true }));
18460
- return "Typed into: " + (el.getAttribute("aria-label") || el.placeholder || el.name || "input");
18461
- })()
18462
- `);
18463
- }
18464
- return wc.executeJavaScript(`
18465
- (function() {
18466
- const el = document.querySelector(${JSON.stringify(selector)});
18467
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18468
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
18469
- return 'Error[not-input]: Element is not a text input';
18470
- }
18471
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18472
- return 'Error[disabled]: Input is disabled';
18473
- }
18474
- const prototype = el instanceof HTMLTextAreaElement
18475
- ? HTMLTextAreaElement.prototype
18476
- : HTMLInputElement.prototype;
18477
- const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
18478
- if (descriptor && descriptor.set) {
18479
- descriptor.set.call(el, ${JSON.stringify(value)});
18480
- } else {
18481
- el.value = ${JSON.stringify(value)};
18482
- }
18483
- el.focus();
18484
- el.dispatchEvent(new InputEvent('input', {
18485
- bubbles: true,
18486
- cancelable: true,
18487
- data: ${JSON.stringify(value)},
18488
- inputType: 'insertText',
18489
- }));
18490
- el.dispatchEvent(new Event('change', { bubbles: true }));
18491
- return 'Typed into: ' +
18492
- (el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
18493
- ' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
18494
- })()
18495
- `);
18496
- }
18497
- async function typeKeystroke(wc, selector, value) {
18498
- return wc.executeJavaScript(`
18499
- (async function() {
18500
- const el = document.querySelector(${JSON.stringify(selector)});
18501
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18502
- if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
18503
- return 'Error[not-input]: Element is not a text input';
18504
- }
18505
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18506
- return 'Error[disabled]: Input is disabled';
18507
- }
18508
- el.focus();
18509
- const prototype = el instanceof HTMLTextAreaElement
18510
- ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
18511
- const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
18512
- if (descriptor && descriptor.set) {
18513
- descriptor.set.call(el, '');
18514
- } else {
18515
- el.value = '';
18516
- }
18517
- el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: '', inputType: 'deleteContentBackward' }));
18518
- const chars = ${JSON.stringify(value)}.split('');
18519
- for (const ch of chars) {
18520
- el.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true, cancelable: true }));
18521
- el.dispatchEvent(new KeyboardEvent('keypress', { key: ch, bubbles: true, cancelable: true }));
18522
- if (descriptor && descriptor.set) {
18523
- descriptor.set.call(el, el.value + ch);
18524
- } else {
18525
- el.value += ch;
18526
- }
18527
- el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: ch, inputType: 'insertText' }));
18528
- el.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true, cancelable: true }));
18529
- }
18530
- el.dispatchEvent(new Event('change', { bubbles: true }));
18531
- return 'Typed into: ' +
18532
- (el.getAttribute('aria-label') || el.placeholder || el.name || 'input') +
18533
- ' = ' + (el.type === 'password' ? '[hidden]' : String(el.value).slice(0, 80));
18534
- })()
18535
- `);
18536
- }
18537
- async function hoverElement(wc, selector) {
18538
- const pos = await wc.executeJavaScript(`
18539
- (function() {
18540
- const el = document.querySelector(${JSON.stringify(selector)});
18541
- if (!el) return { error: 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.' };
18542
- if (el instanceof HTMLElement) {
18543
- el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
18544
- }
18545
- const rect = el.getBoundingClientRect();
18546
- 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.' };
18547
- el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }));
18548
- el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
18549
- const label = (el.textContent || el.tagName || 'Element').trim().slice(0, 80);
18550
- return {
18551
- x: Math.round(rect.left + rect.width / 2),
18552
- y: Math.round(rect.top + rect.height / 2),
18553
- label: label,
18554
- };
18555
- })()
18556
- `);
18557
- if (!pos || typeof pos !== "object") return "Error: Could not hover element";
18558
- if ("error" in pos && typeof pos.error === "string") return pos.error;
18559
- const x = typeof pos.x === "number" ? pos.x : null;
18560
- const y = typeof pos.y === "number" ? pos.y : null;
18561
- if (x == null || y == null)
18562
- return "Error: Could not resolve hover coordinates";
18563
- wc.sendInputEvent({ type: "mouseMove", x, y });
18564
- const label = typeof pos.label === "string" ? pos.label : "element";
18565
- return `Hovered: ${label}`;
18566
- }
18567
- async function focusElement(wc, selector) {
18568
- return wc.executeJavaScript(`
18569
- (function() {
18570
- const el = document.querySelector(${JSON.stringify(selector)});
18571
- if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
18572
- if (!(el instanceof HTMLElement)) return 'Error[not-interactive]: Element is not focusable';
18573
- if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') {
18574
- return 'Error[disabled]: Element is disabled';
18575
- }
18576
- el.focus({ preventScroll: false });
18577
- return 'Focused: ' + (el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
18578
- })()
18579
- `);
18580
- }
18581
- async function selectOption(wc, index, selector, label, value) {
18582
- const resolvedSelector = await resolveSelector(wc, index, selector);
18583
- if (!resolvedSelector)
18584
- return "Error: No select element index or selector provided";
18585
- return wc.executeJavaScript(`
18586
- (function() {
18587
- const el = document.querySelector(${JSON.stringify(resolvedSelector)});
18588
- if (!(el instanceof HTMLSelectElement)) {
18589
- return 'Element is not a select dropdown';
18590
- }
18591
- if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
18592
- return 'Select is disabled';
18593
- }
18594
- const requestedLabel = ${JSON.stringify(label || "")}.trim().toLowerCase();
18595
- const requestedValue = ${JSON.stringify(value || "")}.trim();
18596
- const option = Array.from(el.options).find((item) => {
18597
- const optionLabel = (item.textContent || '').trim().toLowerCase();
18598
- return (requestedLabel && optionLabel === requestedLabel) ||
18599
- (requestedValue && item.value === requestedValue);
18600
- });
18601
- if (!option) return 'Option not found';
18602
- el.value = option.value;
18603
- el.dispatchEvent(new Event('input', { bubbles: true }));
18604
- el.dispatchEvent(new Event('change', { bubbles: true }));
18605
- return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
18606
- })()
18607
- `);
18608
- }
18609
- async function submitForm(wc, index, selector) {
18610
- const beforeUrl = wc.getURL();
18611
- let resolvedSelector = await resolveSelector(wc, index, selector);
18612
- if (!resolvedSelector) {
18613
- resolvedSelector = await wc.executeJavaScript(`
18614
- (function() {
18615
- var forms = document.querySelectorAll('form');
18616
- for (var i = 0; i < forms.length; i++) {
18617
- var f = forms[i];
18618
- var rect = f.getBoundingClientRect();
18619
- if (rect.width > 0 && rect.height > 0) return 'form';
18620
- }
18621
- return forms.length > 0 ? 'form' : null;
18622
- })()
18623
- `);
18624
- if (!resolvedSelector) return "Error: No form found on the page";
18625
- }
18626
- const formInfo = await wc.executeJavaScript(`
18627
- (function() {
18628
- const target = document.querySelector(${JSON.stringify(resolvedSelector)});
18629
- if (!target) return { error: 'Target not found' };
18630
- // Find the form: nested, or linked via form="id" attribute
18631
- var form = target instanceof HTMLFormElement ? target : target.closest('form');
18632
- if (!form) {
18633
- const formId = target.getAttribute('form');
18634
- if (formId) {
18635
- const linked = document.getElementById(formId);
18636
- if (linked instanceof HTMLFormElement) form = linked;
18637
- }
18638
- }
18639
- if (!form) return { error: 'No parent form found' };
18640
- function isSubmitControl(el) {
18641
- return (
18642
- (el instanceof HTMLButtonElement &&
18643
- ((el.getAttribute('type') || '').trim().toLowerCase() === '' ||
18644
- el.type === 'submit')) ||
18645
- (el instanceof HTMLInputElement &&
18646
- (el.type === 'submit' || el.type === 'image'))
18647
- );
18648
- }
18649
- const submitter = isSubmitControl(target)
18650
- ? target
18651
- : Array.from(document.querySelectorAll('button, input[type="submit"], input[type="image"]')).find(
18652
- (candidate) => isSubmitControl(candidate) && candidate.form === form,
18653
- );
18654
- if (
18655
- submitter instanceof HTMLElement &&
18656
- (submitter.hasAttribute('disabled') ||
18657
- submitter.getAttribute('aria-disabled') === 'true')
18658
- ) {
18659
- return { error: 'Submit control is disabled' };
18660
- }
18661
- // Collect form data and determine method
18662
- const submitterActionAttr =
18663
- (submitter instanceof HTMLButtonElement ||
18664
- submitter instanceof HTMLInputElement
18665
- ? submitter.getAttribute('formaction')?.trim()
18666
- : '') || '';
18667
- const action = submitterActionAttr
18668
- ? new URL(submitterActionAttr, document.baseURI).toString()
18669
- : form.action || window.location.href;
18670
- const submitterMethodAttr =
18671
- (submitter instanceof HTMLButtonElement ||
18672
- submitter instanceof HTMLInputElement
18673
- ? submitter.getAttribute('formmethod')?.trim()
18674
- : '') || '';
18675
- const method = (
18676
- submitterMethodAttr ||
18677
- form.getAttribute('method') ||
18678
- form.method ||
18679
- 'GET'
18680
- ).toUpperCase();
18681
- let fd;
18682
- try {
18683
- fd = submitter instanceof HTMLElement
18684
- ? new FormData(form, submitter)
18685
- : new FormData(form);
18686
- } catch {
18687
- fd = new FormData(form);
18688
- }
18689
- const params = new URLSearchParams();
18690
- for (const [k, v] of fd.entries()) {
18691
- if (typeof v === 'string') params.append(k, v);
18692
- }
18693
- // Use requestSubmit to fire JS submit handlers for all methods
18694
- if (typeof form.requestSubmit === 'function') {
18695
- try {
18696
- if (
18697
- submitter instanceof HTMLButtonElement ||
18698
- submitter instanceof HTMLInputElement
18699
- ) {
18700
- form.requestSubmit(submitter);
18701
- } else {
18702
- form.requestSubmit();
18703
- }
18704
- } catch {
18705
- form.requestSubmit();
18706
- }
18707
- return { submitted: true, method };
18708
- }
18709
- if (submitter instanceof HTMLElement && typeof submitter.click === 'function') {
18710
- submitter.click();
18711
- return { submitted: true, method };
18712
- }
18713
- // Last resort: form.submit() bypasses JS handlers but at least submits
18714
- if (method === 'GET') {
18715
- return { action, method, params: params.toString(), found: true };
18716
- }
18717
- form.submit();
18718
- return { submitted: true, method };
18719
- })()
18720
- `);
18721
- if (formInfo.error) return formInfo.error;
18722
- if (formInfo.found && formInfo.method === "GET") {
18723
- const url = new URL(formInfo.action);
18724
- if (formInfo.params) {
18725
- url.search = formInfo.params;
18726
- }
18727
- await loadPermittedUrl(wc, url.toString());
18728
- await waitForPotentialNavigation$1(wc, beforeUrl);
18729
- const afterUrl = wc.getURL();
18730
- return afterUrl !== beforeUrl ? `Submitted form via GET -> ${afterUrl}` : "Submitted form via GET";
17771
+ if (interactActions.includes(name)) {
17772
+ return `
17773
+ [state: url=${wc.getURL()}, title=${JSON.stringify(wc.getTitle() || "")}, tabId=${tabManager.getActiveTabId()}]`;
18731
17774
  }
18732
- if (formInfo.submitted) {
18733
- await waitForPotentialNavigation$1(wc, beforeUrl);
18734
- const afterUrl = wc.getURL();
18735
- return afterUrl !== beforeUrl ? `Submitted form via ${formInfo.method} -> ${afterUrl}` : `Submitted form via ${formInfo.method}`;
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}]`;
18736
17781
  }
18737
- return "Submitted form";
17782
+ return "";
18738
17783
  }
18739
- async function pressKey(wc, key, index, selector) {
18740
- const resolvedSelector = await resolveSelector(wc, index, selector);
18741
- return wc.executeJavaScript(`
18742
- (function() {
18743
- const key = ${JSON.stringify(key)};
18744
- const selector = ${JSON.stringify(resolvedSelector)};
18745
- const target = selector ? document.querySelector(selector) : document.activeElement;
18746
- if (!target || !(target instanceof HTMLElement)) {
18747
- return selector ? 'Target not found' : 'No focused element';
18748
- }
18749
- target.focus();
18750
- const eventInit = { key, bubbles: true, cancelable: true };
18751
- target.dispatchEvent(new KeyboardEvent('keydown', eventInit));
18752
- target.dispatchEvent(new KeyboardEvent('keypress', eventInit));
18753
- const tag = target.tagName;
18754
- const type = target instanceof HTMLInputElement ? target.type : '';
18755
- if (key === 'Enter') {
18756
- if (tag === 'BUTTON' || (tag === 'INPUT' && (type === 'submit' || type === 'button'))) {
18757
- target.click();
18758
- } else if (tag === 'INPUT' || tag === 'TEXTAREA') {
18759
- const form = target.closest('form');
18760
- if (form) {
18761
- if (typeof form.requestSubmit === 'function') {
18762
- form.requestSubmit();
18763
- } else {
18764
- const submitBtn = form.querySelector('[type="submit"]');
18765
- if (submitBtn) submitBtn.click();
18766
- else form.submit();
18767
- }
18768
- }
18769
- }
18770
- }
18771
- target.dispatchEvent(new KeyboardEvent('keyup', eventInit));
18772
- return 'Pressed key: ' + key;
18773
- })()
18774
- `);
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));
17799
+ }
18775
17800
  }
18776
- async function waitForCondition(wc, text, selector, timeoutMs) {
17801
+ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
18777
17802
  const effectiveTimeout = Math.max(250, timeoutMs || 5e3);
18778
17803
  const expectedText = (text || "").trim();
18779
17804
  const expectedSelector = (selector || "").trim();
18780
- 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") {
18781
17814
  return JSON.stringify({
18782
17815
  matched: false,
18783
17816
  error: "wait_for requires text or selector"
18784
17817
  });
18785
17818
  }
18786
- if (wc.isLoading()) {
18787
- 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
+ });
18788
17824
  }
18789
- const startedAt = Date.now();
18790
- while (Date.now() - startedAt < effectiveTimeout) {
18791
- 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(`
18792
17859
  (function() {
18793
- var selector = ${JSON.stringify(expectedSelector)};
18794
- var text = ${JSON.stringify(expectedText)};
18795
- if (selector) {
18796
- try {
18797
- if (document.querySelector(selector)) return 'selector';
18798
- } catch (e) {
18799
- return 'invalid_selector:' + e.message;
18800
- }
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;
18801
17865
  }
18802
- if (text && document.body && document.body.innerText && document.body.innerText.includes(text)) return 'text';
18803
- return '';
18804
17866
  })()
18805
- `);
18806
- const elapsedMs2 = Date.now() - startedAt;
18807
- if (result === "selector") {
18808
- return JSON.stringify({
18809
- matched: true,
18810
- type: "selector",
18811
- value: expectedSelector,
18812
- elapsed_ms: elapsedMs2
18813
- });
18814
- }
18815
- if (result === "text") {
18816
- return JSON.stringify({
18817
- matched: true,
18818
- type: "text",
18819
- value: expectedText.slice(0, 80),
18820
- elapsed_ms: elapsedMs2
18821
- });
18822
- }
18823
- if (typeof result === "string" && result.startsWith("invalid_selector:")) {
18824
- return JSON.stringify({
18825
- matched: false,
18826
- error: `Invalid selector "${expectedSelector}" — ${result.slice(17)}`
18827
- });
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;
18828
17873
  }
18829
- await new Promise((resolve) => setTimeout(resolve, 150));
18830
17874
  }
18831
- const elapsedMs = Date.now() - startedAt;
18832
- const diagnostic = expectedSelector ? await wc.executeJavaScript(`
18833
- (function() {
18834
- try {
18835
- var count = document.querySelectorAll(${JSON.stringify(expectedSelector)}).length;
18836
- return count > 0 ? 'found ' + count + ' after timeout' : 'not found (page has ' + document.querySelectorAll('*').length + ' elements)';
18837
- } catch (e) { return 'selector error: ' + e.message; }
18838
- })()
18839
- `) : null;
18840
- return JSON.stringify({
18841
- matched: false,
18842
- type: expectedSelector ? "selector" : "text",
18843
- value: expectedSelector ? expectedSelector : expectedText.slice(0, 80),
18844
- elapsed_ms: elapsedMs,
18845
- timeout_ms: effectiveTimeout,
18846
- ...diagnostic ? { diagnostic } : {}
18847
- });
17875
+ return JSON.stringify(timeoutPayload);
18848
17876
  }
18849
17877
  function registerTools(server, tabManager, runtime2) {
18850
17878
  server.registerPrompt(
@@ -18923,7 +17951,8 @@ function registerTools(server, tabManager, runtime2) {
18923
17951
  pageTitle = wc.getTitle();
18924
17952
  const page = await extractContent(wc);
18925
17953
  pageType = detectPageType(page);
18926
- } catch {
17954
+ } catch (err) {
17955
+ logger$7.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
18927
17956
  }
18928
17957
  }
18929
17958
  const scored = TOOL_DEFINITIONS.map((def) => {
@@ -18975,7 +18004,7 @@ function registerTools(server, tabManager, runtime2) {
18975
18004
  },
18976
18005
  async () => {
18977
18006
  const activeTab = getActiveTabSummary(tabManager);
18978
- if (!activeTab) return asTextResponse("Error: No active tab");
18007
+ if (!activeTab) return asNoActiveTabResponse();
18979
18008
  return asTextResponse(JSON.stringify(activeTab, null, 2));
18980
18009
  }
18981
18010
  );
@@ -19084,7 +18113,7 @@ ${buildScopedContext(pageContent, mode)}`;
19084
18113
  },
19085
18114
  async ({ mode }) => {
19086
18115
  const tab = tabManager.getActiveTab();
19087
- if (!tab) return asTextResponse("Error: No active tab");
18116
+ if (!tab) return asNoActiveTabResponse();
19088
18117
  try {
19089
18118
  const pageContent = await extractContent(tab.view.webContents);
19090
18119
  const effectiveMode = mode || "full";
@@ -19116,7 +18145,7 @@ ${buildScopedContext(pageContent, mode)}`;
19116
18145
  },
19117
18146
  async ({ mode }) => {
19118
18147
  const tab = tabManager.getActiveTab();
19119
- if (!tab) return asTextResponse("Error: No active tab");
18148
+ if (!tab) return asNoActiveTabResponse();
19120
18149
  try {
19121
18150
  const pageContent = await extractContent(tab.view.webContents);
19122
18151
  const effectiveMode = mode || "full";
@@ -19165,7 +18194,7 @@ ${buildScopedContext(pageContent, mode)}`;
19165
18194
  },
19166
18195
  async ({ url, postBody }) => {
19167
18196
  const tab = tabManager.getActiveTab();
19168
- if (!tab) return asTextResponse("Error: No active tab");
18197
+ if (!tab) return asNoActiveTabResponse();
19169
18198
  const preCheck = await validateLinkDestination(url);
19170
18199
  if (preCheck.status === "dead") {
19171
18200
  return asTextResponse(
@@ -19207,7 +18236,7 @@ ${buildScopedContext(pageContent, mode)}`;
19207
18236
  async ({ enabled, tabId, match, reload }) => {
19208
18237
  const activeTab = tabManager.getActiveTab();
19209
18238
  if (!activeTab && !tabId && !match) {
19210
- return asTextResponse("Error: No active tab");
18239
+ return asNoActiveTabResponse();
19211
18240
  }
19212
18241
  return withAction(
19213
18242
  runtime2,
@@ -19250,7 +18279,7 @@ ${buildScopedContext(pageContent, mode)}`;
19250
18279
  },
19251
18280
  async ({ type }) => {
19252
18281
  const tab = tabManager.getActiveTab();
19253
- if (!tab) return asTextResponse("Error: No active tab");
18282
+ if (!tab) return asNoActiveTabResponse();
19254
18283
  try {
19255
18284
  const pageContent = await extractContent(tab.view.webContents);
19256
18285
  const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
@@ -19298,7 +18327,7 @@ ${buildScopedContext(pageContent, mode)}`;
19298
18327
  },
19299
18328
  async () => {
19300
18329
  const tab = tabManager.getActiveTab();
19301
- if (!tab) return asTextResponse("Error: No active tab");
18330
+ if (!tab) return asNoActiveTabResponse();
19302
18331
  return withAction(runtime2, tabManager, "go_back", {}, async () => {
19303
18332
  if (!tab.canGoBack()) {
19304
18333
  return "No previous page in history";
@@ -19319,7 +18348,7 @@ ${buildScopedContext(pageContent, mode)}`;
19319
18348
  },
19320
18349
  async () => {
19321
18350
  const tab = tabManager.getActiveTab();
19322
- if (!tab) return asTextResponse("Error: No active tab");
18351
+ if (!tab) return asNoActiveTabResponse();
19323
18352
  return withAction(runtime2, tabManager, "go_forward", {}, async () => {
19324
18353
  if (!tab.canGoForward()) {
19325
18354
  return "No forward page in history";
@@ -19340,7 +18369,7 @@ ${buildScopedContext(pageContent, mode)}`;
19340
18369
  },
19341
18370
  async () => {
19342
18371
  const tab = tabManager.getActiveTab();
19343
- if (!tab) return asTextResponse("Error: No active tab");
18372
+ if (!tab) return asNoActiveTabResponse();
19344
18373
  return withAction(runtime2, tabManager, "reload", {}, async () => {
19345
18374
  tabManager.reloadTab(tabManager.getActiveTabId());
19346
18375
  await waitForLoad(tab.view.webContents);
@@ -19360,7 +18389,7 @@ ${buildScopedContext(pageContent, mode)}`;
19360
18389
  },
19361
18390
  async ({ index, selector }) => {
19362
18391
  const tab = tabManager.getActiveTab();
19363
- if (!tab) return asTextResponse("Error: No active tab");
18392
+ if (!tab) return asNoActiveTabResponse();
19364
18393
  return withAction(
19365
18394
  runtime2,
19366
18395
  tabManager,
@@ -19389,7 +18418,7 @@ ${buildScopedContext(pageContent, mode)}`;
19389
18418
  },
19390
18419
  async ({ index, selector }) => {
19391
18420
  const tab = tabManager.getActiveTab();
19392
- if (!tab) return asTextResponse("Error: No active tab");
18421
+ if (!tab) return asNoActiveTabResponse();
19393
18422
  return withAction(
19394
18423
  runtime2,
19395
18424
  tabManager,
@@ -19418,7 +18447,7 @@ ${buildScopedContext(pageContent, mode)}`;
19418
18447
  },
19419
18448
  async ({ index, selector }) => {
19420
18449
  const tab = tabManager.getActiveTab();
19421
- if (!tab) return asTextResponse("Error: No active tab");
18450
+ if (!tab) return asNoActiveTabResponse();
19422
18451
  return withAction(
19423
18452
  runtime2,
19424
18453
  tabManager,
@@ -19447,11 +18476,11 @@ ${buildScopedContext(pageContent, mode)}`;
19447
18476
  },
19448
18477
  async ({ index, selector }) => {
19449
18478
  const tab = tabManager.getActiveTab();
19450
- if (!tab) return asTextResponse("Error: No active tab");
18479
+ if (!tab) return asNoActiveTabResponse();
19451
18480
  const wc = tab.view.webContents;
19452
18481
  const resolvedSelector = await resolveSelector(wc, index, selector);
19453
18482
  if (!resolvedSelector) {
19454
- return asTextResponse("Error: No index or selector provided");
18483
+ return asErrorTextResponse("No index or selector provided");
19455
18484
  }
19456
18485
  const result = await wc.executeJavaScript(`
19457
18486
  (function() {
@@ -19501,7 +18530,7 @@ ${buildScopedContext(pageContent, mode)}`;
19501
18530
  );
19502
18531
  }
19503
18532
  if ("error" in result && typeof result.error === "string") {
19504
- return asTextResponse(`Error: ${result.error}`);
18533
+ return asErrorTextResponse(result.error);
19505
18534
  }
19506
18535
  const parts = [`<${result.tag}>`];
19507
18536
  if ("role" in result && typeof result.role === "string" && result.role.trim()) {
@@ -19532,7 +18561,7 @@ ${buildScopedContext(pageContent, mode)}`;
19532
18561
  },
19533
18562
  async ({ index, selector, text, mode }) => {
19534
18563
  const tab = tabManager.getActiveTab();
19535
- if (!tab) return asTextResponse("Error: No active tab");
18564
+ if (!tab) return asNoActiveTabResponse();
19536
18565
  return withAction(
19537
18566
  runtime2,
19538
18567
  tabManager,
@@ -19571,7 +18600,7 @@ ${buildScopedContext(pageContent, mode)}`;
19571
18600
  },
19572
18601
  async ({ index, selector, text, mode }) => {
19573
18602
  const tab = tabManager.getActiveTab();
19574
- if (!tab) return asTextResponse("Error: No active tab");
18603
+ if (!tab) return asNoActiveTabResponse();
19575
18604
  return withAction(
19576
18605
  runtime2,
19577
18606
  tabManager,
@@ -19608,13 +18637,13 @@ ${buildScopedContext(pageContent, mode)}`;
19608
18637
  },
19609
18638
  async ({ index, selector, label, value }) => {
19610
18639
  const tab = tabManager.getActiveTab();
19611
- if (!tab) return asTextResponse("Error: No active tab");
18640
+ if (!tab) return asNoActiveTabResponse();
19612
18641
  return withAction(
19613
18642
  runtime2,
19614
18643
  tabManager,
19615
18644
  "select_option",
19616
18645
  { index, selector, label, value },
19617
- async () => selectOption(tab.view.webContents, index, selector, label, value)
18646
+ async () => selectOptionDirect(tab.view.webContents, index, selector, label, value)
19618
18647
  );
19619
18648
  }
19620
18649
  );
@@ -19630,23 +18659,13 @@ ${buildScopedContext(pageContent, mode)}`;
19630
18659
  },
19631
18660
  async ({ index, selector }) => {
19632
18661
  const tab = tabManager.getActiveTab();
19633
- if (!tab) return asTextResponse("Error: No active tab");
18662
+ if (!tab) return asNoActiveTabResponse();
19634
18663
  return withAction(
19635
18664
  runtime2,
19636
18665
  tabManager,
19637
18666
  "submit_form",
19638
18667
  { index, selector },
19639
- async () => {
19640
- const wc = tab.view.webContents;
19641
- const beforeUrl = wc.getURL();
19642
- const result = await submitForm(wc, index, selector);
19643
- if (result.startsWith("Error") || result.startsWith("Target") || result.startsWith("No parent") || result.startsWith("Submit control")) {
19644
- return result;
19645
- }
19646
- await waitForPotentialNavigation$1(wc, beforeUrl);
19647
- const afterUrl = wc.getURL();
19648
- return afterUrl !== beforeUrl ? `${result} -> ${afterUrl}` : result;
19649
- }
18668
+ async () => submitFormDirect(tab.view.webContents, index, selector)
19650
18669
  );
19651
18670
  }
19652
18671
  );
@@ -19663,7 +18682,7 @@ ${buildScopedContext(pageContent, mode)}`;
19663
18682
  },
19664
18683
  async ({ key, index, selector }) => {
19665
18684
  const tab = tabManager.getActiveTab();
19666
- if (!tab) return asTextResponse("Error: No active tab");
18685
+ if (!tab) return asNoActiveTabResponse();
19667
18686
  return withAction(
19668
18687
  runtime2,
19669
18688
  tabManager,
@@ -19672,7 +18691,7 @@ ${buildScopedContext(pageContent, mode)}`;
19672
18691
  async () => {
19673
18692
  const wc = tab.view.webContents;
19674
18693
  const beforeUrl = wc.getURL();
19675
- const result = await pressKey(wc, key, index, selector);
18694
+ const result = await pressKeyDirect(wc, key, index, selector);
19676
18695
  if (key === "Enter") {
19677
18696
  await waitForPotentialNavigation$1(wc, beforeUrl, 3e3);
19678
18697
  const afterUrl = wc.getURL();
@@ -19699,7 +18718,7 @@ ${buildScopedContext(pageContent, mode)}`;
19699
18718
  },
19700
18719
  async ({ direction, amount }) => {
19701
18720
  const tab = tabManager.getActiveTab();
19702
- if (!tab) return asTextResponse("Error: No active tab");
18721
+ if (!tab) return asNoActiveTabResponse();
19703
18722
  return withAction(
19704
18723
  runtime2,
19705
18724
  tabManager,
@@ -19722,7 +18741,7 @@ ${buildScopedContext(pageContent, mode)}`;
19722
18741
  },
19723
18742
  async () => {
19724
18743
  const tab = tabManager.getActiveTab();
19725
- if (!tab) return asTextResponse("Error: No active tab");
18744
+ if (!tab) return asNoActiveTabResponse();
19726
18745
  return withAction(
19727
18746
  runtime2,
19728
18747
  tabManager,
@@ -19745,7 +18764,7 @@ ${buildScopedContext(pageContent, mode)}`;
19745
18764
  },
19746
18765
  async ({ strategy }) => {
19747
18766
  const tab = tabManager.getActiveTab();
19748
- if (!tab) return asTextResponse("Error: No active tab");
18767
+ if (!tab) return asNoActiveTabResponse();
19749
18768
  return withAction(
19750
18769
  runtime2,
19751
18770
  tabManager,
@@ -19771,13 +18790,13 @@ ${buildScopedContext(pageContent, mode)}`;
19771
18790
  },
19772
18791
  async ({ text, selector, timeoutMs }) => {
19773
18792
  const tab = tabManager.getActiveTab();
19774
- if (!tab) return asTextResponse("Error: No active tab");
18793
+ if (!tab) return asNoActiveTabResponse();
19775
18794
  return withAction(
19776
18795
  runtime2,
19777
18796
  tabManager,
19778
18797
  "wait_for",
19779
18798
  { text, selector, timeoutMs },
19780
- async () => waitForCondition(tab.view.webContents, text, selector, timeoutMs)
18799
+ async () => waitForConditionMcp(tab.view.webContents, text, selector, timeoutMs)
19781
18800
  );
19782
18801
  }
19783
18802
  );
@@ -19958,7 +18977,7 @@ ${buildScopedContext(pageContent, mode)}`;
19958
18977
  },
19959
18978
  async () => {
19960
18979
  const tab = tabManager.getActiveTab();
19961
- if (!tab) return asTextResponse("Error: No active tab");
18980
+ if (!tab) return asNoActiveTabResponse();
19962
18981
  try {
19963
18982
  const bounds = tab.view.getBounds();
19964
18983
  if (bounds.width <= 0 || bounds.height <= 0) {
@@ -20027,7 +19046,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20027
19046
  },
20028
19047
  async ({ index, selector, text, label, durationMs, persist, color }) => {
20029
19048
  const tab = tabManager.getActiveTab();
20030
- if (!tab) return asTextResponse("Error: No active tab");
19049
+ if (!tab) return asNoActiveTabResponse();
20031
19050
  const normalizedText = normalizeLooseString(text);
20032
19051
  return withAction(
20033
19052
  runtime2,
@@ -20077,7 +19096,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
20077
19096
  },
20078
19097
  async () => {
20079
19098
  const tab = tabManager.getActiveTab();
20080
- if (!tab) return asTextResponse("Error: No active tab");
19099
+ if (!tab) return asNoActiveTabResponse();
20081
19100
  return withAction(
20082
19101
  runtime2,
20083
19102
  tabManager,
@@ -20216,8 +19235,9 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20216
19235
  h.label,
20217
19236
  void 0,
20218
19237
  h.color
20219
- ).catch(() => {
20220
- });
19238
+ ).catch(
19239
+ (err) => logger$7.warn("Failed to restore highlight after removal:", err)
19240
+ );
20221
19241
  }
20222
19242
  }
20223
19243
  return asTextResponse(`Removed highlight ${id}`);
@@ -20281,7 +19301,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20281
19301
  note: zod.z.string().optional().describe("Optional note about why this was bookmarked"),
20282
19302
  on_duplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe(
20283
19303
  'How to handle an existing bookmark with the same URL in the same folder: "ask" (default), "update", or "duplicate"'
20284
- )
19304
+ ),
19305
+ intent: zod.z.string().optional().describe(
19306
+ "Human-readable description of what this bookmark is for"
19307
+ ),
19308
+ expected_content: zod.z.string().optional().describe(
19309
+ "Brief description of the content the agent should expect to find here"
19310
+ ),
19311
+ key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
19312
+ agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
20285
19313
  }
20286
19314
  },
20287
19315
  async ({
@@ -20294,7 +19322,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20294
19322
  folder_summary,
20295
19323
  create_folder_if_missing,
20296
19324
  note,
20297
- on_duplicate
19325
+ on_duplicate,
19326
+ intent,
19327
+ expected_content,
19328
+ key_fields,
19329
+ agent_hints
20298
19330
  }) => {
20299
19331
  return withAction(
20300
19332
  runtime2,
@@ -20309,7 +19341,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20309
19341
  folder_name,
20310
19342
  folder_summary,
20311
19343
  create_folder_if_missing,
20312
- note
19344
+ note,
19345
+ intent,
19346
+ expected_content,
19347
+ key_fields,
19348
+ agent_hints
20313
19349
  },
20314
19350
  async () => {
20315
19351
  const currentTab = tabManager.getActiveTab();
@@ -20339,7 +19375,15 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20339
19375
  source.title,
20340
19376
  target.folderId,
20341
19377
  note,
20342
- { onDuplicate: on_duplicate ?? "ask" }
19378
+ {
19379
+ onDuplicate: on_duplicate ?? "ask",
19380
+ extra: getBookmarkMetadataFromArgs({
19381
+ intent,
19382
+ expected_content,
19383
+ key_fields,
19384
+ agent_hints
19385
+ })
19386
+ }
20343
19387
  );
20344
19388
  if (result.status === "conflict" && result.existing) {
20345
19389
  return composeFolderAwareResponse(
@@ -20436,7 +19480,11 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20436
19480
  folder_summary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
20437
19481
  create_folder_if_missing: zod.z.boolean().optional().describe("Create folder_name automatically when it does not exist"),
20438
19482
  note: zod.z.string().optional().describe("Optional note to attach or update on the bookmark"),
20439
- archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
19483
+ archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
19484
+ intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
19485
+ expected_content: zod.z.string().optional().describe("Brief description of content the agent should expect"),
19486
+ key_fields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
19487
+ agent_hints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
20440
19488
  }
20441
19489
  },
20442
19490
  async (args) => {
@@ -20472,7 +19520,8 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20472
19520
  const updated = updateBookmark(existing.id, {
20473
19521
  folderId: target.folderId,
20474
19522
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
20475
- note
19523
+ note,
19524
+ ...getBookmarkMetadataFromArgs(args)
20476
19525
  });
20477
19526
  if (!updated) {
20478
19527
  return `Bookmark ${existing.id} not found`;
@@ -20483,12 +19532,18 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20483
19532
  );
20484
19533
  }
20485
19534
  if ("error" in source) return `Error: ${source.error}`;
20486
- const bookmark = saveBookmark(
19535
+ const result = saveBookmarkWithPolicy(
20487
19536
  source.url,
20488
19537
  source.title,
20489
19538
  target.folderId,
20490
- note
19539
+ note,
19540
+ {
19541
+ onDuplicate: "update",
19542
+ extra: getBookmarkMetadataFromArgs(args)
19543
+ }
20491
19544
  );
19545
+ const bookmark = result.bookmark;
19546
+ if (!bookmark) return "Error: Bookmark save failed";
20492
19547
  return composeFolderAwareResponse(
20493
19548
  `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder(bookmark.folderId)}" (id=${bookmark.id})`,
20494
19549
  target.createdFolder
@@ -20867,7 +19922,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
20867
19922
  },
20868
19923
  async ({ title, folder, summary, note, tags }) => {
20869
19924
  const tab = tabManager.getActiveTab();
20870
- if (!tab) return asTextResponse("Error: No active tab");
19925
+ if (!tab) return asNoActiveTabResponse();
20871
19926
  return withAction(
20872
19927
  runtime2,
20873
19928
  tabManager,
@@ -21011,7 +20066,8 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21011
20066
  let page;
21012
20067
  try {
21013
20068
  page = await extractContent(wc);
21014
- } catch {
20069
+ } catch (err) {
20070
+ logger$7.warn("Failed to extract page while generating suggestions:", err);
21015
20071
  return asTextResponse(
21016
20072
  "Could not read page. Try navigate to a working URL."
21017
20073
  );
@@ -21118,7 +20174,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21118
20174
  },
21119
20175
  async ({ fields, submit }) => {
21120
20176
  const tab = tabManager.getActiveTab();
21121
- if (!tab) return asTextResponse("Error: No active tab");
20177
+ if (!tab) return asNoActiveTabResponse();
21122
20178
  return withAction(
21123
20179
  runtime2,
21124
20180
  tabManager,
@@ -21132,7 +20188,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
21132
20188
  const firstSel = fillResults.find((item) => item.selector)?.selector ?? null;
21133
20189
  if (firstSel) {
21134
20190
  const beforeUrl = wc.getURL();
21135
- const submitResult = await submitForm(wc, void 0, firstSel);
20191
+ const submitResult = await submitFormDirect(wc, void 0, firstSel);
21136
20192
  await waitForPotentialNavigation$1(wc, beforeUrl);
21137
20193
  const afterUrl = wc.getURL();
21138
20194
  results.push(
@@ -21175,7 +20231,7 @@ ${results.join("\n")}`;
21175
20231
  submit_selector
21176
20232
  }) => {
21177
20233
  const tab = tabManager.getActiveTab();
21178
- if (!tab) return asTextResponse("Error: No active tab");
20234
+ if (!tab) return asNoActiveTabResponse();
21179
20235
  return withAction(
21180
20236
  runtime2,
21181
20237
  tabManager,
@@ -21249,7 +20305,7 @@ ${steps.join("\n")}`;
21249
20305
  },
21250
20306
  async ({ query, selector }) => {
21251
20307
  const tab = tabManager.getActiveTab();
21252
- if (!tab) return asTextResponse("Error: No active tab");
20308
+ if (!tab) return asNoActiveTabResponse();
21253
20309
  const qLower = query.toLowerCase().trim();
21254
20310
  const buttonLabels = [
21255
20311
  "add to cart",
@@ -21334,7 +20390,7 @@ ${steps.join("\n")}`;
21334
20390
  },
21335
20391
  async ({ direction, selector }) => {
21336
20392
  const tab = tabManager.getActiveTab();
21337
- if (!tab) return asTextResponse("Error: No active tab");
20393
+ if (!tab) return asNoActiveTabResponse();
21338
20394
  return withAction(
21339
20395
  runtime2,
21340
20396
  tabManager,
@@ -21385,7 +20441,7 @@ ${steps.join("\n")}`;
21385
20441
  },
21386
20442
  async () => {
21387
20443
  const tab = tabManager.getActiveTab();
21388
- if (!tab) return asTextResponse("Error: No active tab");
20444
+ if (!tab) return asNoActiveTabResponse();
21389
20445
  return withAction(
21390
20446
  runtime2,
21391
20447
  tabManager,
@@ -21443,7 +20499,7 @@ ${steps.join("\n")}`;
21443
20499
  },
21444
20500
  async ({ index, selector: rawSelector }) => {
21445
20501
  const tab = tabManager.getActiveTab();
21446
- if (!tab) return asTextResponse("Error: No active tab");
20502
+ if (!tab) return asNoActiveTabResponse();
21447
20503
  return withAction(
21448
20504
  runtime2,
21449
20505
  tabManager,
@@ -21498,7 +20554,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21498
20554
  },
21499
20555
  async ({ index, selector: rawSelector, position }) => {
21500
20556
  const tab = tabManager.getActiveTab();
21501
- if (!tab) return asTextResponse("Error: No active tab");
20557
+ if (!tab) return asNoActiveTabResponse();
21502
20558
  return withAction(
21503
20559
  runtime2,
21504
20560
  tabManager,
@@ -21556,7 +20612,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
21556
20612
  },
21557
20613
  async ({ timeoutMs }) => {
21558
20614
  const tab = tabManager.getActiveTab();
21559
- if (!tab) return asTextResponse("Error: No active tab");
20615
+ if (!tab) return asNoActiveTabResponse();
21560
20616
  return withAction(
21561
20617
  runtime2,
21562
20618
  tabManager,
@@ -21616,11 +20672,12 @@ ${JSON.stringify(tableJson, null, 2)}`;
21616
20672
  let targetDomain = domain;
21617
20673
  if (!targetDomain) {
21618
20674
  const tab = tabManager.getActiveTab();
21619
- if (!tab) return asTextResponse("Error: No active tab and no domain specified");
20675
+ if (!tab) return asErrorTextResponse("No active tab and no domain specified");
21620
20676
  try {
21621
20677
  targetDomain = new URL(tab.state.url).hostname;
21622
- } catch {
21623
- 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");
21624
20681
  }
21625
20682
  }
21626
20683
  const matches = findEntriesForDomain(
@@ -21679,13 +20736,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21679
20736
  submit_index
21680
20737
  }) => {
21681
20738
  const tab = tabManager.getActiveTab();
21682
- if (!tab) return asTextResponse("Error: No active tab");
20739
+ if (!tab) return asNoActiveTabResponse();
21683
20740
  const wc = tab.view.webContents;
21684
20741
  let hostname;
21685
20742
  try {
21686
20743
  hostname = new URL(tab.state.url).hostname;
21687
- } catch {
21688
- 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");
21689
20747
  }
21690
20748
  const matches = findEntriesForDomain(`https://${hostname}`);
21691
20749
  if (matches.length === 0) {
@@ -21721,7 +20779,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21721
20779
  }
21722
20780
  const creds = getCredential(match.id);
21723
20781
  if (!creds) {
21724
- return asTextResponse("Error: Credential not found in vault");
20782
+ return asErrorTextResponse("Credential not found in vault");
21725
20783
  }
21726
20784
  const results = [];
21727
20785
  if (username_index != null) {
@@ -21772,13 +20830,14 @@ Use vault_login to fill the login form. Credentials are filled directly — you
21772
20830
  },
21773
20831
  async ({ credential_label, code_index, submit_after, submit_index }) => {
21774
20832
  const tab = tabManager.getActiveTab();
21775
- if (!tab) return asTextResponse("Error: No active tab");
20833
+ if (!tab) return asNoActiveTabResponse();
21776
20834
  const wc = tab.view.webContents;
21777
20835
  let hostname;
21778
20836
  try {
21779
20837
  hostname = new URL(tab.state.url).hostname;
21780
- } catch {
21781
- 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");
21782
20841
  }
21783
20842
  const matches = findEntriesForDomain(`https://${hostname}`);
21784
20843
  const match = credential_label ? matches.find(
@@ -21949,7 +21008,7 @@ function startMcpServer(tabManager, runtime2, port) {
21949
21008
  await mcpServer.connect(transport);
21950
21009
  await transport.handleRequest(req, res);
21951
21010
  } catch (error) {
21952
- console.error("[Vessel MCP] Error handling request:", error);
21011
+ logger$7.error("Error handling request:", error);
21953
21012
  if (!res.headersSent) {
21954
21013
  res.writeHead(500, { "Content-Type": "application/json" });
21955
21014
  res.end(
@@ -21968,7 +21027,7 @@ function startMcpServer(tabManager, runtime2, port) {
21968
21027
  };
21969
21028
  server.once("error", (error) => {
21970
21029
  const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
21971
- console.error("[Vessel MCP] Server error:", error);
21030
+ logger$7.error("Server error:", error);
21972
21031
  clearMcpAuthFile();
21973
21032
  setMcpHealth({
21974
21033
  configuredPort: port,
@@ -21980,14 +21039,12 @@ function startMcpServer(tabManager, runtime2, port) {
21980
21039
  if (httpServer === server) {
21981
21040
  httpServer = null;
21982
21041
  }
21983
- finish({
21984
- ok: false,
21042
+ finish(errorResult(message, {
21985
21043
  configuredPort: port,
21986
21044
  activePort: null,
21987
21045
  endpoint: null,
21988
- authToken: null,
21989
- error: message
21990
- });
21046
+ authToken: null
21047
+ }));
21991
21048
  });
21992
21049
  server.listen(port, "127.0.0.1", () => {
21993
21050
  httpServer = server;
@@ -22002,7 +21059,7 @@ function startMcpServer(tabManager, runtime2, port) {
22002
21059
  message: `MCP server listening on ${endpoint}.`
22003
21060
  });
22004
21061
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22005
- console.log(`[Vessel MCP] Server listening on ${endpoint} (auth enabled)`);
21062
+ logger$7.info(`Server listening on ${endpoint} (auth enabled)`);
22006
21063
  }
22007
21064
  if (mcpAuthToken) {
22008
21065
  writeMcpAuthFile(endpoint, mcpAuthToken);
@@ -22041,7 +21098,7 @@ function stopMcpServer() {
22041
21098
  message: "MCP server is stopped."
22042
21099
  });
22043
21100
  if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
22044
- console.log("[Vessel MCP] Server stopped");
21101
+ logger$7.info("Server stopped");
22045
21102
  }
22046
21103
  resolve();
22047
21104
  });
@@ -22062,6 +21119,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
22062
21119
  function isSafeAutomationKitId(id) {
22063
21120
  return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
22064
21121
  }
21122
+ const logger$6 = createLogger("KitRegistry");
22065
21123
  function getUserKitsDir() {
22066
21124
  return path$1.join(electron.app.getPath("userData"), "kits");
22067
21125
  }
@@ -22099,10 +21157,10 @@ function getInstalledKits() {
22099
21157
  if (isValidKit(parsed)) {
22100
21158
  kits.push(parsed);
22101
21159
  } else {
22102
- console.warn(`[kit-registry] Skipping invalid kit file: ${file}`);
21160
+ logger$6.warn(`Skipping invalid kit file: ${file}`);
22103
21161
  }
22104
- } catch {
22105
- console.warn(`[kit-registry] Failed to read kit file: ${file}`);
21162
+ } catch (err) {
21163
+ logger$6.warn(`Failed to read kit file: ${file}`, err);
22106
21164
  }
22107
21165
  }
22108
21166
  return kits;
@@ -22114,69 +21172,67 @@ async function installKitFromFile() {
22114
21172
  properties: ["openFile"]
22115
21173
  });
22116
21174
  if (canceled || filePaths.length === 0) {
22117
- return { ok: false, error: "canceled" };
21175
+ return errorResult("canceled");
22118
21176
  }
22119
21177
  let raw;
22120
21178
  try {
22121
21179
  raw = fs$1.readFileSync(filePaths[0], "utf-8");
22122
21180
  } catch {
22123
- return { ok: false, error: "Could not read the selected file." };
21181
+ return errorResult("Could not read the selected file.");
22124
21182
  }
22125
21183
  let parsed;
22126
21184
  try {
22127
21185
  parsed = JSON.parse(raw);
22128
21186
  } catch {
22129
- return { ok: false, error: "File is not valid JSON." };
21187
+ return errorResult("File is not valid JSON.");
22130
21188
  }
22131
21189
  if (!isValidKit(parsed)) {
22132
- return {
22133
- ok: false,
22134
- error: "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
22135
- };
21190
+ return errorResult(
21191
+ "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
21192
+ );
22136
21193
  }
22137
21194
  if (BUNDLED_KIT_IDS.has(parsed.id)) {
22138
- return {
22139
- ok: false,
22140
- error: `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
22141
- };
21195
+ return errorResult(
21196
+ `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
21197
+ );
22142
21198
  }
22143
21199
  ensureKitsDir();
22144
21200
  const dest = getKitFilePath(parsed.id);
22145
21201
  if (!dest) {
22146
- return { ok: false, error: "Kit id contains unsupported characters." };
21202
+ return errorResult("Kit id contains unsupported characters.");
22147
21203
  }
22148
21204
  try {
22149
21205
  fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
22150
21206
  } catch {
22151
- return { ok: false, error: "Failed to save the kit file." };
21207
+ return errorResult("Failed to save the kit file.");
22152
21208
  }
22153
- return { ok: true, kit: parsed };
21209
+ return okResult({ kit: parsed });
22154
21210
  }
22155
21211
  function uninstallKit(id, scheduledKitIds) {
22156
21212
  if (BUNDLED_KIT_IDS.has(id)) {
22157
- return { ok: false, error: "Built-in kits cannot be removed." };
21213
+ return errorResult("Built-in kits cannot be removed.");
22158
21214
  }
22159
21215
  if (scheduledKitIds?.has(id)) {
22160
- return {
22161
- ok: false,
22162
- error: "This kit has active scheduled jobs. Delete or reassign them first."
22163
- };
21216
+ return errorResult(
21217
+ "This kit has active scheduled jobs. Delete or reassign them first."
21218
+ );
22164
21219
  }
22165
21220
  ensureKitsDir();
22166
21221
  const target = getKitFilePath(id);
22167
21222
  if (!target) {
22168
- return { ok: false, error: "Kit id contains unsupported characters." };
21223
+ return errorResult("Kit id contains unsupported characters.");
22169
21224
  }
22170
21225
  if (!fs$1.existsSync(target)) {
22171
- return { ok: false, error: "Kit not found." };
21226
+ return errorResult("Kit not found.");
22172
21227
  }
22173
21228
  try {
22174
21229
  fs$1.unlinkSync(target);
22175
- return { ok: true };
21230
+ return okResult();
22176
21231
  } catch {
22177
- return { ok: false, error: "Failed to remove the kit file." };
21232
+ return errorResult("Failed to remove the kit file.");
22178
21233
  }
22179
21234
  }
21235
+ const logger$5 = createLogger("Scheduler");
22180
21236
  let jobs = [];
22181
21237
  let removeIdleListener = null;
22182
21238
  let broadcastFn = null;
@@ -22201,7 +21257,7 @@ function saveJobs() {
22201
21257
  try {
22202
21258
  fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
22203
21259
  } catch (err) {
22204
- console.warn("[scheduler] Failed to save jobs:", err);
21260
+ logger$5.warn("Failed to save jobs:", err);
22205
21261
  }
22206
21262
  }
22207
21263
  function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
@@ -22323,7 +21379,7 @@ async function fireJob(job, windowState, runtime2) {
22323
21379
  };
22324
21380
  startActivity();
22325
21381
  if (!settings2.chatProvider) {
22326
- console.warn(`[scheduler] Job "${job.kitName}" skipped — no chat provider configured`);
21382
+ logger$5.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
22327
21383
  appendActivity(
22328
21384
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
22329
21385
  );
@@ -22331,7 +21387,7 @@ async function fireJob(job, windowState, runtime2) {
22331
21387
  return;
22332
21388
  }
22333
21389
  if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
22334
- console.log(`[scheduler] Firing scheduled job: ${job.kitName} (${job.id})`);
21390
+ logger$5.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
22335
21391
  }
22336
21392
  try {
22337
21393
  const provider = createProvider(settings2.chatProvider);
@@ -22384,7 +21440,7 @@ function tick(windowState, runtime2) {
22384
21440
  saveJobs();
22385
21441
  broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
22386
21442
  void fireJob(job, windowState, runtime2).catch((err) => {
22387
- console.warn("[scheduler] Unexpected error firing job:", err);
21443
+ logger$5.warn("Unexpected error firing job:", err);
22388
21444
  }).finally(fireNext);
22389
21445
  };
22390
21446
  fireNext();
@@ -22942,6 +21998,7 @@ function registerWindowControlHandlers(mainWindow) {
22942
21998
  });
22943
21999
  }
22944
22000
  let activeChatProvider = null;
22001
+ const logger$4 = createLogger("IPC");
22945
22002
  const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
22946
22003
  function registerIpcHandlers(windowState, runtime2) {
22947
22004
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
@@ -23018,7 +22075,8 @@ function registerIpcHandlers(windowState, runtime2) {
23018
22075
  let parsed;
23019
22076
  try {
23020
22077
  parsed = new URL(rawUrl);
23021
- } catch {
22078
+ } catch (err) {
22079
+ logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
23022
22080
  return;
23023
22081
  }
23024
22082
  if (parsed.origin !== premiumApiOrigin) return;
@@ -23075,7 +22133,8 @@ function registerIpcHandlers(windowState, runtime2) {
23075
22133
  if (wc.isDestroyed()) return 0;
23076
22134
  try {
23077
22135
  return await getHighlightCount(wc) ?? 0;
23078
- } catch {
22136
+ } catch (err) {
22137
+ logger$4.warn("Failed to get active highlight count:", err);
23079
22138
  return 0;
23080
22139
  }
23081
22140
  };
@@ -23183,13 +22242,13 @@ function registerIpcHandlers(windowState, runtime2) {
23183
22242
  electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (_, config) => {
23184
22243
  try {
23185
22244
  if (!config || typeof config !== "object" || !("id" in config)) {
23186
- return { ok: false, models: [], error: "Invalid provider configuration" };
22245
+ return errorResult("Invalid provider configuration", { models: [] });
23187
22246
  }
23188
22247
  return await fetchProviderModels(
23189
22248
  config
23190
22249
  );
23191
22250
  } catch (err) {
23192
- return { ok: false, models: [], error: err instanceof Error ? err.message : "Unknown error" };
22251
+ return errorResult(getErrorMessage(err), { models: [] });
23193
22252
  }
23194
22253
  });
23195
22254
  electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
@@ -23344,14 +22403,21 @@ function registerIpcHandlers(windowState, runtime2) {
23344
22403
  Channels.BOOKMARK_SAVE,
23345
22404
  (_, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
23346
22405
  trackBookmarkAction("save");
23347
- return saveBookmarkWithPolicy(url, title, folderId, note, {
22406
+ const result = saveBookmarkWithPolicy(url, title, folderId, note, {
22407
+ onDuplicate: "update",
23348
22408
  extra: {
23349
- intent: intent?.trim() || void 0,
23350
- expectedContent: expectedContent?.trim() || void 0,
23351
- keyFields,
23352
- agentHints
22409
+ ...normalizeBookmarkMetadata({
22410
+ intent,
22411
+ expectedContent,
22412
+ keyFields,
22413
+ agentHints
22414
+ })
23353
22415
  }
23354
22416
  });
22417
+ if (!result.bookmark) {
22418
+ throw new Error("Bookmark save failed");
22419
+ }
22420
+ return result.bookmark;
23355
22421
  }
23356
22422
  );
23357
22423
  electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
@@ -23377,12 +22443,14 @@ function registerIpcHandlers(windowState, runtime2) {
23377
22443
  const wc = activeTab.view.webContents;
23378
22444
  const result = await captureSelectionHighlight(wc);
23379
22445
  if (result.success && result.text) {
23380
- await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
23381
- });
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
+ );
23382
22449
  await emitHighlightCount();
23383
22450
  }
23384
22451
  return result;
23385
- } catch {
22452
+ } catch (err) {
22453
+ logger$4.warn("Failed to capture highlight from active tab:", err);
23386
22454
  return { success: false, message: "Could not capture selection" };
23387
22455
  }
23388
22456
  });
@@ -23406,7 +22474,8 @@ function registerIpcHandlers(windowState, runtime2) {
23406
22474
  chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
23407
22475
  }
23408
22476
  });
23409
- } catch {
22477
+ } catch (err) {
22478
+ logger$4.warn("Failed to persist auto-highlight selection:", err);
23410
22479
  }
23411
22480
  });
23412
22481
  electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
@@ -23419,7 +22488,8 @@ function registerIpcHandlers(windowState, runtime2) {
23419
22488
  if (wc.isDestroyed()) return false;
23420
22489
  try {
23421
22490
  return scrollToHighlight(wc, index);
23422
- } catch {
22491
+ } catch (err) {
22492
+ logger$4.warn("Failed to scroll to highlight:", err);
23423
22493
  return false;
23424
22494
  }
23425
22495
  });
@@ -23434,7 +22504,8 @@ function registerIpcHandlers(windowState, runtime2) {
23434
22504
  await emitHighlightCount();
23435
22505
  }
23436
22506
  return removed;
23437
- } catch {
22507
+ } catch (err) {
22508
+ logger$4.warn("Failed to remove highlight at index:", err);
23438
22509
  return false;
23439
22510
  }
23440
22511
  });
@@ -23449,7 +22520,8 @@ function registerIpcHandlers(windowState, runtime2) {
23449
22520
  await emitHighlightCount();
23450
22521
  }
23451
22522
  return cleared;
23452
- } catch {
22523
+ } catch (err) {
22524
+ logger$4.warn("Failed to clear highlight elements:", err);
23453
22525
  return false;
23454
22526
  }
23455
22527
  });
@@ -23533,7 +22605,7 @@ function registerIpcHandlers(windowState, runtime2) {
23533
22605
  electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (_, email) => {
23534
22606
  assertString(email, "email");
23535
22607
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23536
- return { ok: false, error: "Invalid email format" };
22608
+ return errorResult("Invalid email format");
23537
22609
  }
23538
22610
  trackPremiumFunnel("activation_attempted");
23539
22611
  const result = await requestActivationCode(email);
@@ -23549,11 +22621,9 @@ function registerIpcHandlers(windowState, runtime2) {
23549
22621
  assertString(code, "code");
23550
22622
  assertString(challengeToken, "challengeToken");
23551
22623
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
23552
- return {
23553
- ok: false,
23554
- state: getPremiumState(),
23555
- error: "Invalid email format"
23556
- };
22624
+ return errorResult("Invalid email format", {
22625
+ state: getPremiumState()
22626
+ });
23557
22627
  }
23558
22628
  trackPremiumFunnel("activation_attempted");
23559
22629
  const result = await verifyActivationCode(email, code, challengeToken);
@@ -23988,6 +23058,7 @@ ${lines.join("\n")}
23988
23058
  }
23989
23059
  const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
23990
23060
  const PERSIST_DEBOUNCE_MS = 500;
23061
+ const logger$3 = createLogger("Runtime");
23991
23062
  function clone(value) {
23992
23063
  return JSON.parse(JSON.stringify(value));
23993
23064
  }
@@ -24377,9 +23448,7 @@ ${progress}
24377
23448
  JSON.stringify(persisted, null, 2),
24378
23449
  "utf-8"
24379
23450
  )
24380
- ).catch(
24381
- (err) => console.error("[Vessel] Failed to persist runtime state:", err)
24382
- );
23451
+ ).catch((err) => logger$3.error("Failed to persist runtime state:", err));
24383
23452
  }
24384
23453
  schedulePersist() {
24385
23454
  this.persistDirty = true;
@@ -24644,6 +23713,7 @@ function installDownloadHandler(chromeView) {
24644
23713
  });
24645
23714
  });
24646
23715
  }
23716
+ const logger$2 = createLogger("Shortcuts");
24647
23717
  function registerHighlightShortcut(mainWindow, tabManager) {
24648
23718
  const register = () => {
24649
23719
  electron.globalShortcut.unregister("CommandOrControl+H");
@@ -24653,7 +23723,7 @@ function registerHighlightShortcut(mainWindow, tabManager) {
24653
23723
  tabManager.captureHighlightFromActiveTab();
24654
23724
  });
24655
23725
  if (!success) {
24656
- console.warn("[Vessel] Failed to register Ctrl+H shortcut");
23726
+ logger$2.warn("Failed to register Ctrl+H shortcut");
24657
23727
  }
24658
23728
  };
24659
23729
  register();
@@ -24722,6 +23792,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
24722
23792
  });
24723
23793
  }
24724
23794
  }
23795
+ const logger$1 = createLogger("Splash");
24725
23796
  function findIconBase64() {
24726
23797
  const candidates = [
24727
23798
  path$1.join(process.resourcesPath, "vessel-icon.png"),
@@ -24884,7 +23955,7 @@ function createSplashWindow() {
24884
23955
  fs$1.writeFileSync(tmpPath, html, "utf-8");
24885
23956
  void splash.loadFile(tmpPath);
24886
23957
  } catch (err) {
24887
- console.warn("[splash] Failed to write temp HTML, using fallback:", err);
23958
+ logger$1.warn("Failed to write temp HTML, using fallback:", err);
24888
23959
  void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
24889
23960
  }
24890
23961
  return splash;
@@ -24894,6 +23965,7 @@ function closeSplash(splash, delayMs = 0) {
24894
23965
  if (!splash.isDestroyed()) splash.close();
24895
23966
  }, delayMs);
24896
23967
  }
23968
+ const logger = createLogger("Bootstrap");
24897
23969
  let runtime = null;
24898
23970
  function checkWritableUserData(userDataPath) {
24899
23971
  const issues = [];
@@ -25013,7 +24085,7 @@ async function bootstrap() {
25013
24085
  };
25014
24086
  let didInitializeChromeRenderer = false;
25015
24087
  const splashTimeout = setTimeout(() => {
25016
- console.warn("[bootstrap] Renderer did not finish loading before splash timeout");
24088
+ logger.warn("Renderer did not finish loading before splash timeout");
25017
24089
  revealMainWindow();
25018
24090
  }, 8e3);
25019
24091
  const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
@@ -25064,8 +24136,8 @@ async function bootstrap() {
25064
24136
  "did-fail-load",
25065
24137
  (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
25066
24138
  if (!isMainFrame) return;
25067
- console.error(
25068
- "[bootstrap] Chrome renderer failed to load:",
24139
+ logger.error(
24140
+ "Chrome renderer failed to load:",
25069
24141
  errorCode,
25070
24142
  errorDescription,
25071
24143
  validatedURL
@@ -25076,21 +24148,21 @@ async function bootstrap() {
25076
24148
  );
25077
24149
  loadRenderers(chromeView, sidebarView, devtoolsPanelView);
25078
24150
  startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
25079
- console.error("[bootstrap] MCP server failed to start:", err);
24151
+ logger.error("MCP server failed to start:", err);
25080
24152
  });
25081
24153
  }
25082
24154
  process.on("uncaughtException", (error) => {
25083
- console.error("[Vessel] Uncaught exception:", error.message, error.stack);
24155
+ logger.error("Uncaught exception:", error.message, error.stack);
25084
24156
  electron.app.quit();
25085
24157
  });
25086
24158
  process.on("unhandledRejection", (reason) => {
25087
- console.error(
25088
- "[Vessel] Unhandled rejection:",
24159
+ logger.error(
24160
+ "Unhandled rejection:",
25089
24161
  reason instanceof Error ? reason.message : reason
25090
24162
  );
25091
24163
  });
25092
24164
  electron.app.whenReady().then(bootstrap).catch((error) => {
25093
- console.error("[Vessel] Failed to bootstrap application:", error);
24165
+ logger.error("Failed to bootstrap application:", error);
25094
24166
  electron.app.quit();
25095
24167
  });
25096
24168
  electron.app.on("window-all-closed", () => {