@cyberstrike-io/cyberstrike 1.1.16-beta.2 → 1.1.16-beta.4

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.
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- function __accessProp(key) {
9
- return this[key];
10
- }
11
- var __toESMCache_node;
12
- var __toESMCache_esm;
13
8
  var __toESM = (mod, isNodeMode, target) => {
14
- var canCache = mod != null && typeof mod === "object";
15
- if (canCache) {
16
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
- var cached = cache.get(mod);
18
- if (cached)
19
- return cached;
20
- }
21
9
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
10
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
11
  for (let key of __getOwnPropNames(mod))
24
12
  if (!__hasOwnProp.call(to, key))
25
13
  __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
14
+ get: () => mod[key],
27
15
  enumerable: true
28
16
  });
29
- if (canCache)
30
- cache.set(mod, to);
31
17
  return to;
32
18
  };
33
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
- var __returnValue = (v) => v;
35
- function __exportSetter(name, newValue) {
36
- this[name] = __returnValue.bind(null, newValue);
37
- }
38
20
  var __export = (target, all) => {
39
21
  for (var name in all)
40
22
  __defProp(target, name, {
41
23
  get: all[name],
42
24
  enumerable: true,
43
25
  configurable: true,
44
- set: __exportSetter.bind(all, name)
26
+ set: (newValue) => all[name] = () => newValue
45
27
  });
46
28
  };
47
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -19343,7 +19325,7 @@ var require_tracestate_impl = __commonJS((exports) => {
19343
19325
  const value = listMember.slice(i2 + 1, part.length);
19344
19326
  if ((0, tracestate_validators_1.validateKey)(key) && (0, tracestate_validators_1.validateValue)(value)) {
19345
19327
  agg.set(key, value);
19346
- }
19328
+ } else {}
19347
19329
  }
19348
19330
  return agg;
19349
19331
  }, new Map);
@@ -30852,7 +30834,7 @@ class JSONSchemaGenerator {
30852
30834
  if (val === undefined) {
30853
30835
  if (this.unrepresentable === "throw") {
30854
30836
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
30855
- }
30837
+ } else {}
30856
30838
  } else if (typeof val === "bigint") {
30857
30839
  if (this.unrepresentable === "throw") {
30858
30840
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -87530,7 +87512,7 @@ function finalize(ctx, schema) {
87530
87512
  result.$schema = "http://json-schema.org/draft-07/schema#";
87531
87513
  } else if (ctx.target === "draft-04") {
87532
87514
  result.$schema = "http://json-schema.org/draft-04/schema#";
87533
- } else if (ctx.target === "openapi-3.0") {}
87515
+ } else if (ctx.target === "openapi-3.0") {} else {}
87534
87516
  if (ctx.external?.uri) {
87535
87517
  const id = ctx.external.registry.get(schema)?.id;
87536
87518
  if (!id)
@@ -87777,7 +87759,7 @@ var literalProcessor = (schema, ctx, json2, _params) => {
87777
87759
  if (val === undefined) {
87778
87760
  if (ctx.unrepresentable === "throw") {
87779
87761
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
87780
- }
87762
+ } else {}
87781
87763
  } else if (typeof val === "bigint") {
87782
87764
  if (ctx.unrepresentable === "throw") {
87783
87765
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -115538,6 +115520,9 @@ A JSON snapshot with:
115538
115520
  - url: current page URL
115539
115521
  - viewportCenterBlocked: true if an overlay/modal/sidebar is open
115540
115522
  - elements: interactive elements with id, role, label, type, value, placeholder, href, options
115523
+ - occludedBy: present when this element is physically covered by an overlay and
115524
+ cannot be clicked until that overlay is cleared. The value is the overlay's
115525
+ visible text (which usually includes its own dismiss control's label).
115541
115526
  - outOfScope (optional): array of labels the user has excluded — never plan tasks matching these
115542
115527
  - pendingMutations (optional): mutation keywords awaiting a triggering task.
115543
115528
  If a task you plan would trigger one of these, set \`triggersMutation\` on that
@@ -115639,6 +115624,25 @@ Also include:
115639
115624
  - Product/item cards — include max 2-3 (the API pattern repeats)
115640
115625
  - CRUD buttons (Edit, Delete, View, Export, etc.) — one per action type is enough
115641
115626
 
115627
+ ### Modal/dialog you opened to fill (viewportCenterBlocked + form fields)
115628
+ When viewportCenterBlocked is true because a MODAL/DIALOG containing form fields is
115629
+ open (you likely just opened it via a "+ Add" / "New" / "Create" / "Edit" button),
115630
+ that modal IS the content to explore: FILL its fields and SUBMIT via its primary
115631
+ action button (e.g. Kaydet / Save / Submit / Create). Do NOT plan a click on its
115632
+ close / X / Cancel control — closing it discards the form before it is submitted.
115633
+ Only dismiss a centered overlay that has NO form to complete (a banner, tour, cookie
115634
+ or notification notice covering the content behind it).
115635
+
115636
+ ### Occluded elements (occludedBy present)
115637
+ An element with an \`occludedBy\` field is physically covered by an overlay and
115638
+ cannot be clicked until that overlay is cleared. \`occludedBy\` is the overlay's
115639
+ visible text. BEFORE planning any task on an occluded element, plan a click that
115640
+ dismisses the overlay FIRST (make it the first task). Identify the dismiss control
115641
+ among the listed elements using the \`occludedBy\` text, and choose the LEAST-COMMITTAL
115642
+ option — one that closes or postpones without granting permissions, subscribing,
115643
+ accepting terms, or changing account state. If no such dismiss control is present,
115644
+ simply omit the occluded element (do not invent a control, do not click a destructive one).
115645
+
115642
115646
  ### Skip these (do NOT include as tasks):
115643
115647
  - Language / locale selectors (labels: "EN", "English", "DE", "🌐")
115644
115648
  - Logout / sign-out / delete account (system handles logout separately)
@@ -115830,8 +115834,11 @@ async function planPage(snapshot, model, usageAcc) {
115830
115834
  model,
115831
115835
  system: systemPrompt,
115832
115836
  messages: [{ role: "user", content: userMessage }],
115833
- maxOutputTokens: 2048,
115834
- temperature: 0
115837
+ maxOutputTokens: 16384,
115838
+ temperature: 0,
115839
+ providerOptions: {
115840
+ openai: { reasoningEffort: "low" }
115841
+ }
115835
115842
  });
115836
115843
  if (usageAcc) {
115837
115844
  usageAcc.inputTokens += result.usage.inputTokens ?? 0;
@@ -115884,8 +115891,11 @@ async function planUnexploredElements(snapshot, unexploredLabels, model, usageAc
115884
115891
  model,
115885
115892
  system: systemPrompt,
115886
115893
  messages: [{ role: "user", content: userMessage }],
115887
- maxOutputTokens: 2048,
115888
- temperature: 0
115894
+ maxOutputTokens: 16384,
115895
+ temperature: 0,
115896
+ providerOptions: {
115897
+ openai: { reasoningEffort: "low" }
115898
+ }
115889
115899
  });
115890
115900
  if (usageAcc) {
115891
115901
  usageAcc.inputTokens += result.usage.inputTokens ?? 0;
@@ -116123,12 +116133,20 @@ function elementToPrompt(el) {
116123
116133
  }
116124
116134
  return result;
116125
116135
  }
116126
- function buildPlannerSnapshot(url3, elements, globalState, credId, viewportCenterBlocked) {
116136
+ function buildPlannerSnapshot(url3, elements, globalState, credId, viewportCenterBlocked, occlusions) {
116137
+ const promptElements = elements.map(elementToPrompt);
116138
+ if (occlusions && occlusions.length > 0) {
116139
+ for (const occ of occlusions) {
116140
+ const match = promptElements.find((e2) => e2.label === occ.label && e2.role === occ.role);
116141
+ if (match)
116142
+ match.occludedBy = occ.occluderText;
116143
+ }
116144
+ }
116127
116145
  const snapshot = {
116128
116146
  url: url3,
116129
116147
  viewportCenterBlocked,
116130
116148
  totalPagesVisited: globalState.visitedPages.size,
116131
- elements: elements.map(elementToPrompt)
116149
+ elements: promptElements
116132
116150
  };
116133
116151
  if (globalState.outOfScope.length > 0) {
116134
116152
  snapshot.outOfScope = [...globalState.outOfScope];
@@ -116299,7 +116317,7 @@ async function collectInteractiveElements(page) {
116299
116317
  return true;
116300
116318
  return /^(mui-\d|radix-|headlessui-|react-aria-|rc-_?\d|ember\d)/i.test(id);
116301
116319
  }
116302
- function buildCSSSelector(el) {
116320
+ function buildCSSSelectorCandidate(el) {
116303
116321
  const tag = el.tagName.toLowerCase();
116304
116322
  const id = el.getAttribute("id");
116305
116323
  if (id && !isEphemeralId(id))
@@ -116347,6 +116365,35 @@ async function collectInteractiveElements(page) {
116347
116365
  }
116348
116366
  return tag;
116349
116367
  }
116368
+ function uniquePositionalPath(el) {
116369
+ const segs = [];
116370
+ let cur = el;
116371
+ while (cur && cur !== document.documentElement) {
116372
+ const tag = cur.tagName.toLowerCase();
116373
+ const id = cur.getAttribute("id");
116374
+ if (id && !isEphemeralId(id)) {
116375
+ segs.unshift(`${tag}#${CSS.escape(id)}`);
116376
+ break;
116377
+ }
116378
+ const parent = cur.parentElement;
116379
+ if (!parent) {
116380
+ segs.unshift(tag);
116381
+ break;
116382
+ }
116383
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
116384
+ segs.unshift(sameTag.length > 1 ? `${tag}:nth-of-type(${sameTag.indexOf(cur) + 1})` : tag);
116385
+ cur = parent;
116386
+ }
116387
+ return segs.join(" > ");
116388
+ }
116389
+ function buildCSSSelector(el) {
116390
+ const candidate = buildCSSSelectorCandidate(el);
116391
+ try {
116392
+ if (document.querySelectorAll(candidate).length === 1)
116393
+ return candidate;
116394
+ } catch {}
116395
+ return uniquePositionalPath(el);
116396
+ }
116350
116397
  const INTERACTIVE_SELECTORS = [
116351
116398
  "button",
116352
116399
  "a[href]",
@@ -116422,7 +116469,12 @@ async function collectInteractiveElements(page) {
116422
116469
  const label = getLabel(el);
116423
116470
  const tag = el.tagName.toLowerCase();
116424
116471
  const type = el.type?.toLowerCase() || "";
116425
- const href = el.href || "";
116472
+ const rawHref = (el.getAttribute("href") || "").trim();
116473
+ const navigates = !!rawHref && rawHref !== "#" && !rawHref.startsWith("#") && !rawHref.startsWith("javascript:");
116474
+ const href = navigates ? el.href || "" : "";
116475
+ const nonNavAnchor = tag === "a" && !navigates;
116476
+ if (nonNavAnchor)
116477
+ role = "button";
116426
116478
  const isSlider = role === "slider";
116427
116479
  const value = isSlider ? el.getAttribute("aria-valuenow") ?? el.value ?? "" : el.value || "";
116428
116480
  const placeholder = el.placeholder || "";
@@ -116438,7 +116490,8 @@ async function collectInteractiveElements(page) {
116438
116490
  const disambiguatedLabel = count > 1 ? `${label} (${count})` : label;
116439
116491
  const ariaLabelRaw = (el.getAttribute("aria-label") || "").trim();
116440
116492
  const safeAriaLabel = ariaLabelRaw.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
116441
- const selectorRole = syntheticRole || count > 1 ? "" : safeAriaLabel ? `role=${role}[name="${safeAriaLabel}"]` : `role=${role}`;
116493
+ const isFileInput = el.matches("input[type=file]");
116494
+ const selectorRole = syntheticRole || count > 1 || isFileInput || nonNavAnchor ? "" : safeAriaLabel ? `role=${role}[name="${safeAriaLabel}"]` : `role=${role}`;
116442
116495
  const selectorCSS = buildCSSSelector(el);
116443
116496
  const inChrome = !!el.closest("nav, header, footer, aside, [role=navigation], [role=banner], [role=contentinfo], [role=complementary]");
116444
116497
  const roleCount = (seenRoleSelectors.get(selectorRole) ?? 0) + 1;
@@ -116573,8 +116626,17 @@ function assignIds(browserElements, startId) {
116573
116626
  }
116574
116627
  async function collectElements(page) {
116575
116628
  const browserElements = await collectInteractiveElements(page);
116576
- const sampled = sampleTemplates(browserElements);
116577
- return assignIds(sampled, 1).slice(0, MAX_ELEMENTS);
116629
+ const sampled = capElements(sampleTemplates(browserElements));
116630
+ return assignIds(sampled, 1);
116631
+ }
116632
+ var ACTION_ROLES_CAP = new Set(["button", "link", "menuitem", "tab"]);
116633
+ function capElements(els) {
116634
+ if (els.length <= MAX_ELEMENTS)
116635
+ return els;
116636
+ const actions = els.filter((e2) => ACTION_ROLES_CAP.has(e2.role));
116637
+ const rest = els.filter((e2) => !ACTION_ROLES_CAP.has(e2.role));
116638
+ const kept = new Set([...actions, ...rest].slice(0, MAX_ELEMENTS));
116639
+ return els.filter((e2) => kept.has(e2));
116578
116640
  }
116579
116641
  function sampleTemplates(elements) {
116580
116642
  const clusterCount = new Map;
@@ -116703,6 +116765,48 @@ function evalIsViewportCenterBlocked(page) {
116703
116765
  return false;
116704
116766
  });
116705
116767
  }
116768
+ async function probeClickPoint(page, selector) {
116769
+ try {
116770
+ return await page.evaluate((sel) => {
116771
+ const el = document.querySelector(sel);
116772
+ if (!el)
116773
+ return { status: "clickable" };
116774
+ const r2 = el.getBoundingClientRect();
116775
+ if (r2.width === 0 || r2.height === 0)
116776
+ return { status: "clickable" };
116777
+ const cx = r2.left + r2.width / 2;
116778
+ const cy = r2.top + r2.height / 2;
116779
+ if (cx < 0 || cy < 0 || cx > window.innerWidth || cy > window.innerHeight) {
116780
+ return { status: "offscreen" };
116781
+ }
116782
+ const top = document.elementFromPoint(cx, cy);
116783
+ if (!top)
116784
+ return { status: "offscreen" };
116785
+ if (top === el || el.contains(top) || top.contains(el))
116786
+ return { status: "clickable" };
116787
+ if (top.tagName === "IFRAME")
116788
+ return { status: "clickable" };
116789
+ let node = el;
116790
+ while (node) {
116791
+ const root = node.getRootNode();
116792
+ if (root instanceof ShadowRoot) {
116793
+ const host = root.host;
116794
+ if (host === top || top.contains(host))
116795
+ return { status: "clickable" };
116796
+ node = host;
116797
+ } else
116798
+ break;
116799
+ }
116800
+ const text2 = (top.innerText || top.getAttribute("aria-label") || "").trim().replace(/\s+/g, " ").slice(0, 80);
116801
+ return {
116802
+ status: "occluded",
116803
+ occluder: { tag: top.tagName.toLowerCase(), role: top.getAttribute("role") || "", text: text2 }
116804
+ };
116805
+ }, selector);
116806
+ } catch {
116807
+ return { status: "clickable" };
116808
+ }
116809
+ }
116706
116810
  function filterVisitedLinks(elements, currentUrl, visitedPages) {
116707
116811
  let currentPath;
116708
116812
  try {
@@ -116728,6 +116832,59 @@ function filterVisitedLinks(elements, currentUrl, visitedPages) {
116728
116832
  });
116729
116833
  }
116730
116834
 
116835
+ // ../hackbrowser/src/upload-samples.ts
116836
+ var nodeBuffer = globalThis.Buffer;
116837
+ var b64 = (s2) => nodeBuffer.from(s2, "base64");
116838
+ var txt = (s2) => nodeBuffer.from(s2, "utf8");
116839
+ var SAMPLES = [
116840
+ {
116841
+ name: "sample.png",
116842
+ ext: "png",
116843
+ mimeType: "image/png",
116844
+ buffer: b64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGJgAQAAAAUAAaX2RUAAAAAASUVORK5CYII=")
116845
+ },
116846
+ {
116847
+ name: "sample.jpg",
116848
+ ext: "jpg",
116849
+ mimeType: "image/jpeg",
116850
+ buffer: b64("/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AfwD/2Q==")
116851
+ },
116852
+ {
116853
+ name: "sample.pdf",
116854
+ ext: "pdf",
116855
+ mimeType: "application/pdf",
116856
+ buffer: b64("JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0+PmVuZG9iagp0cmFpbGVyPDwvUm9vdCAxIDAgUj4+CiUlRU9GCg==")
116857
+ },
116858
+ { name: "sample.csv", ext: "csv", mimeType: "text/csv", buffer: txt(`name,email
116859
+ Test User,test@example.com
116860
+ `) },
116861
+ { name: "sample.txt", ext: "txt", mimeType: "text/plain", buffer: txt(`sample text file
116862
+ `) },
116863
+ { name: "sample.json", ext: "json", mimeType: "application/json", buffer: txt(`{"sample":true,"value":123}
116864
+ `) }
116865
+ ];
116866
+ var DEFAULT_SAMPLE = SAMPLES[0];
116867
+ function tokenMatches(sample, token) {
116868
+ const t2 = token.trim().toLowerCase();
116869
+ if (!t2)
116870
+ return false;
116871
+ if (t2.startsWith("."))
116872
+ return t2.slice(1) === sample.ext;
116873
+ if (t2.endsWith("/*"))
116874
+ return sample.mimeType.startsWith(t2.slice(0, -1));
116875
+ return t2 === sample.mimeType;
116876
+ }
116877
+ function pickSample(accept) {
116878
+ if (!accept || !accept.trim())
116879
+ return DEFAULT_SAMPLE;
116880
+ const tokens = accept.split(",");
116881
+ for (const sample of SAMPLES) {
116882
+ if (tokens.some((tok) => tokenMatches(sample, tok)))
116883
+ return sample;
116884
+ }
116885
+ return DEFAULT_SAMPLE;
116886
+ }
116887
+
116731
116888
  // ../hackbrowser/src/executor.ts
116732
116889
  var log6 = Log.create({ service: "hackbrowser:executor" });
116733
116890
  var CLICK_TIMEOUT = 2000;
@@ -116773,6 +116930,9 @@ async function executeFill(page, element, value) {
116773
116930
  if (element.role === "slider" || element.type === "range") {
116774
116931
  return executeSliderFill(page, selector || "role=slider", value);
116775
116932
  }
116933
+ if (element.type === "file") {
116934
+ return executeFileSelect(page, selector);
116935
+ }
116776
116936
  const fillErr = await page.fill(selector, value, { timeout: FILL_TIMEOUT }).then(() => null).catch((e2) => e2.message.split(`
116777
116937
  `)[0]);
116778
116938
  if (!fillErr)
@@ -116784,6 +116944,18 @@ async function executeFill(page, element, value) {
116784
116944
  log6.warn("fill failed", { selector, error: fillErr });
116785
116945
  return fillErr;
116786
116946
  }
116947
+ async function executeFileSelect(page, selector) {
116948
+ const accept = await page.getAttribute(selector, "accept").catch(() => null);
116949
+ const sample = pickSample(accept);
116950
+ const err = await page.setInputFiles(selector, { name: sample.name, mimeType: sample.mimeType, buffer: sample.buffer }, { timeout: FILL_TIMEOUT }).then(() => null).catch((e2) => e2.message.split(`
116951
+ `)[0]);
116952
+ if (err) {
116953
+ log6.warn("file select failed", { selector, accept, error: err });
116954
+ return err;
116955
+ }
116956
+ log6.info("file selected", { selector, file: sample.name });
116957
+ return;
116958
+ }
116787
116959
  async function executeSliderFill(page, selector, value) {
116788
116960
  const numericValue = parseInt(value);
116789
116961
  if (isNaN(numericValue) || numericValue <= 0) {
@@ -127843,7 +128015,8 @@ function normalizeScope(input) {
127843
128015
  s2 = s2.replace(/^https?:\/\//, "");
127844
128016
  s2 = s2.split(/[/?#]/)[0] ?? "";
127845
128017
  s2 = s2.replace(/:\d+$/, "");
127846
- s2 = s2.replace(/\.+$/, "");
128018
+ while (s2.endsWith("."))
128019
+ s2 = s2.slice(0, -1);
127847
128020
  return s2;
127848
128021
  }
127849
128022
  function deriveScope(targetUrl) {
@@ -127872,8 +128045,10 @@ var log7 = Log.create({ service: "hackbrowser:agent" });
127872
128045
  var MAX_STEPS_PER_PAGE = 30;
127873
128046
  var MAX_UNPLANNED_ITERATIONS = 2;
127874
128047
  var OVERLAY_ESCAPE_WAIT = 400;
128048
+ var OVERLAY_CLEAR_WAIT = 800;
127875
128049
  var SPA_RENDER_RETRY_WAIT = 600;
127876
128050
  var POST_GOTO_WAIT = 400;
128051
+ var MAX_INLINE_DEPTH = 2;
127877
128052
  var LOGIN_SUCCESS_PATTERN = /POST\s+.*\/(login|signin|authenticate)\S*\s+\[200\]/i;
127878
128053
  function triggerReDiscovery(globalState) {
127879
128054
  if (globalState.authPhase === "authenticated")
@@ -128036,6 +128211,15 @@ function attachDialogAutoAccept(page) {
128036
128211
  dialog.accept().catch(() => {});
128037
128212
  });
128038
128213
  }
128214
+ function attachFileChooserAutoFill(page) {
128215
+ page.on("filechooser", async (chooser) => {
128216
+ try {
128217
+ const accept = await chooser.element().getAttribute("accept").catch(() => null);
128218
+ const sample = pickSample(accept);
128219
+ await chooser.setFiles({ name: sample.name, mimeType: sample.mimeType, buffer: sample.buffer });
128220
+ } catch {}
128221
+ });
128222
+ }
128039
128223
  function setupRequestInterceptor(page, inScope, onCapture, credentialId = SINGLE_CRED) {
128040
128224
  let pendingUIContext = null;
128041
128225
  let pendingTrigger = null;
@@ -128185,9 +128369,10 @@ function getPathPatternKey(url3) {
128185
128369
  return null;
128186
128370
  }
128187
128371
  }
128188
- async function explorePageWithAI(page, pageUrl, interceptor, model, globalState, inScope, credentialId, maxPages, usageAcc) {
128372
+ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState, inScope, credentialId, maxPages, usageAcc, nav = { explore: async () => {}, depth: MAX_INLINE_DEPTH }) {
128189
128373
  const linksToEnqueue = [];
128190
128374
  const semanticActionsDone = new Set;
128375
+ const occ = { pending: [], signaled: new Set };
128191
128376
  csEmit(page, {
128192
128377
  type: "page-change",
128193
128378
  url: pageUrl,
@@ -128246,9 +128431,9 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
128246
128431
  remaining: taskQueue.length
128247
128432
  });
128248
128433
  if (task.type === "form") {
128249
- await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
128434
+ await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
128250
128435
  } else {
128251
- await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
128436
+ await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
128252
128437
  }
128253
128438
  const postActionElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
128254
128439
  if (task.type === "click" && task.role === "combobox") {
@@ -128259,9 +128444,10 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
128259
128444
  }
128260
128445
  const hasNewElements = discoverNewElements(postActionElements, seenKeys);
128261
128446
  const hasStaleTargets = queueHasStaleTargets(taskQueue, postActionElements);
128262
- if (hasNewElements || hasStaleTargets) {
128447
+ if (hasNewElements || hasStaleTargets || occ.pending.length > 0) {
128263
128448
  const freshElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
128264
- const snapshot2 = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page));
128449
+ const snapshot2 = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page), occ.pending);
128450
+ occ.pending = [];
128265
128451
  csEmit(page, {
128266
128452
  type: "llm-thinking",
128267
128453
  reason: "replan",
@@ -128367,9 +128553,9 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
128367
128553
  remaining: additionalQueue.length
128368
128554
  });
128369
128555
  if (task.type === "form") {
128370
- await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
128556
+ await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
128371
128557
  } else {
128372
- await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
128558
+ await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
128373
128559
  }
128374
128560
  const postActionElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
128375
128561
  if (task.type === "click" && task.role === "combobox") {
@@ -128379,9 +128565,10 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
128379
128565
  }
128380
128566
  const hasNewElements = discoverNewElements(postActionElements, seenKeys);
128381
128567
  const hasStaleTargets = queueHasStaleTargets(additionalQueue, postActionElements);
128382
- if (hasNewElements || hasStaleTargets) {
128568
+ if (hasNewElements || hasStaleTargets || occ.pending.length > 0) {
128383
128569
  const freshElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
128384
- const freshSnap = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page));
128570
+ const freshSnap = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page), occ.pending);
128571
+ occ.pending = [];
128385
128572
  csEmit(page, {
128386
128573
  type: "llm-thinking",
128387
128574
  reason: "replan",
@@ -128431,7 +128618,45 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
128431
128618
  }
128432
128619
  return linksToEnqueue;
128433
128620
  }
128434
- async function executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope) {
128621
+ async function recoverFromOcclusion(page, el, original, retry) {
128622
+ if (original.success)
128623
+ return original;
128624
+ if ((await probeClickPoint(page, el.selector)).status !== "occluded")
128625
+ return original;
128626
+ log7.debug("click occluded — clearing overlay + waiting, then retrying once", { label: el.label });
128627
+ await closeOverlay(page);
128628
+ await page.waitForTimeout(OVERLAY_CLEAR_WAIT);
128629
+ return retry();
128630
+ }
128631
+ async function diagnoseOcclusion(page, el, occ) {
128632
+ const probe = await probeClickPoint(page, el.selector);
128633
+ if (probe.status === "occluded" && !occ.signaled.has(probe.occluder.text)) {
128634
+ occ.signaled.add(probe.occluder.text);
128635
+ occ.pending.push({ label: el.label, role: el.role, occluderText: probe.occluder.text });
128636
+ log7.debug("action target occluded — signalling planner to dismiss overlay first", {
128637
+ label: el.label,
128638
+ occluder: probe.occluder.text
128639
+ });
128640
+ return true;
128641
+ }
128642
+ return false;
128643
+ }
128644
+ async function handleNavigation(page, parentUrl, linksToEnqueue, inScope, globalState, nav) {
128645
+ const cur = page.url();
128646
+ const curNorm = normalizeUrl(cur);
128647
+ if (curNorm !== normalizeUrl(parentUrl) && isInScope(cur, inScope)) {
128648
+ if (!globalState.visitedPages.has(curNorm) && nav.depth < MAX_INLINE_DEPTH) {
128649
+ globalState.visitedPages.add(curNorm);
128650
+ await nav.explore(page, cur, nav.depth + 1);
128651
+ } else {
128652
+ linksToEnqueue.push(cur);
128653
+ }
128654
+ }
128655
+ await page.goto(parentUrl, { waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
128656
+ await page.waitForTimeout(POST_GOTO_WAIT);
128657
+ await page.waitForLoadState("domcontentloaded", { timeout: 5000 }).catch(() => {});
128658
+ }
128659
+ async function executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav) {
128435
128660
  for (const field of task.fields) {
128436
128661
  let el = resolveElement(elements, field.role, field.label);
128437
128662
  if (!el) {
@@ -128483,23 +128708,19 @@ async function executeFormTask(task, page, elements, interceptor, semanticAction
128483
128708
  targetSelector: submitEl.selector,
128484
128709
  credential: credentialId
128485
128710
  });
128486
- const result = await execute(page, submitEl, "click", undefined, interceptor.setPendingUI, elements.length);
128711
+ let result = await execute(page, submitEl, "click", undefined, interceptor.setPendingUI, elements.length);
128712
+ result = await recoverFromOcclusion(page, submitEl, result, () => execute(page, submitEl, "click", undefined, interceptor.setPendingUI, elements.length));
128713
+ const occluded = !result.success && await diagnoseOcclusion(page, submitEl, occ);
128487
128714
  interceptor.clearPendingTrigger();
128488
128715
  trackResult(result, interceptor, globalState, credentialId, task.triggersMutation, page);
128489
128716
  csEmit(page, { type: "action-end", ok: result.success, credential: credentialId });
128490
- semanticActionsDone.add(submitKey);
128717
+ if (!occluded)
128718
+ semanticActionsDone.add(submitKey);
128491
128719
  globalState.totalSteps++;
128492
- if (result.navigated) {
128493
- const cur = page.url();
128494
- if (cur !== pageUrl && isInScope(cur, inScope))
128495
- linksToEnqueue.push(cur);
128496
- log7.debug("reloading after form submit", { pageUrl });
128497
- await page.goto(pageUrl, { waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
128498
- await page.waitForTimeout(POST_GOTO_WAIT);
128499
- await page.waitForLoadState("domcontentloaded", { timeout: 5000 }).catch(() => {});
128500
- }
128720
+ if (result.navigated)
128721
+ await handleNavigation(page, pageUrl, linksToEnqueue, inScope, globalState, nav);
128501
128722
  }
128502
- async function executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope) {
128723
+ async function executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav) {
128503
128724
  const el = resolveElement(elements, task.role, task.label);
128504
128725
  if (!el) {
128505
128726
  log7.debug("click target not found", { role: task.role, label: task.label });
@@ -128518,15 +128739,13 @@ async function executeClickTask(task, page, elements, interceptor, semanticActio
128518
128739
  credential: credentialId
128519
128740
  });
128520
128741
  let result = await execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length);
128521
- if (!result.success && await isViewportCenterBlocked(page)) {
128522
- log7.debug("click blocked by a stray overlay — closing it and retrying once", { label: el.label });
128523
- await closeOverlay(page);
128524
- result = await execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length);
128525
- }
128742
+ result = await recoverFromOcclusion(page, el, result, () => execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length));
128743
+ const occluded = !result.success && await diagnoseOcclusion(page, el, occ);
128526
128744
  interceptor.clearPendingTrigger();
128527
128745
  trackResult(result, interceptor, globalState, credentialId, task.triggersMutation, page);
128528
128746
  csEmit(page, { type: "action-end", ok: result.success, credential: credentialId });
128529
- semanticActionsDone.add(key);
128747
+ if (!occluded)
128748
+ semanticActionsDone.add(key);
128530
128749
  globalState.totalSteps++;
128531
128750
  if (el.role === "option") {
128532
128751
  const parentCombobox = elements.find((e2) => e2.role === "combobox");
@@ -128535,12 +128754,7 @@ async function executeClickTask(task, page, elements, interceptor, semanticActio
128535
128754
  }
128536
128755
  }
128537
128756
  if (result.navigated) {
128538
- const cur = page.url();
128539
- if (cur !== pageUrl && isInScope(cur, inScope))
128540
- linksToEnqueue.push(cur);
128541
- await page.goto(pageUrl, { waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
128542
- await page.waitForTimeout(POST_GOTO_WAIT);
128543
- await page.waitForLoadState("domcontentloaded", { timeout: 5000 }).catch(() => {});
128757
+ await handleNavigation(page, pageUrl, linksToEnqueue, inScope, globalState, nav);
128544
128758
  return;
128545
128759
  }
128546
128760
  if (el.role === "combobox")
@@ -128870,6 +129084,7 @@ async function runMultiCredential(config3, credentials) {
128870
129084
  await browserContext.addInitScript(PANEL_INIT_SCRIPT);
128871
129085
  const page = await browserContext.newPage();
128872
129086
  attachDialogAutoAccept(page);
129087
+ attachFileChooserAutoFill(page);
128873
129088
  await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
128874
129089
  csEmit(page, {
128875
129090
  type: "init",
@@ -128975,11 +129190,14 @@ async function runMultiCredential(config3, credentials) {
128975
129190
  });
128976
129191
  continue;
128977
129192
  }
128978
- log7.info("redirect to different page, will explore actual URL", {
129193
+ const landed = normalizeUrl(r2.ctx.page.url());
129194
+ log7.info("redirect to different page, enqueueing landed URL", {
128979
129195
  credential: r2.ctx.id,
128980
129196
  intended: entry.url,
128981
- actual: r2.ctx.page.url()
129197
+ landed
128982
129198
  });
129199
+ enqueueWithContext(landed, r2.ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
129200
+ continue;
128983
129201
  }
128984
129202
  visitableContexts.push(r2.ctx);
128985
129203
  }
@@ -129022,7 +129240,12 @@ async function runMultiCredential(config3, credentials) {
129022
129240
  }
129023
129241
  for (const ctx of visitableContexts) {
129024
129242
  log7.info("exploring", { credential: ctx.id, url: entry.url });
129025
- const discovered = await explorePageWithAI(ctx.page, entry.url, ctx.interceptor, model, globalState, inScope, ctx.id, maxPages, usageAcc);
129243
+ const exploreInline = async (p, url3, depth) => {
129244
+ const found = await explorePageWithAI(p, url3, ctx.interceptor, model, globalState, inScope, ctx.id, maxPages, usageAcc, { explore: exploreInline, depth });
129245
+ for (const u of found)
129246
+ enqueueWithContext(u, ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
129247
+ };
129248
+ const discovered = await explorePageWithAI(ctx.page, entry.url, ctx.interceptor, model, globalState, inScope, ctx.id, maxPages, usageAcc, { explore: exploreInline, depth: 0 });
129026
129249
  for (const url3 of discovered) {
129027
129250
  enqueueWithContext(url3, ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
129028
129251
  }
@@ -129115,6 +129338,7 @@ async function run(config3) {
129115
129338
  await context.addInitScript(PANEL_INIT_SCRIPT);
129116
129339
  const page = await context.newPage();
129117
129340
  attachDialogAutoAccept(page);
129341
+ attachFileChooserAutoFill(page);
129118
129342
  if (config3.auth.sessionFile) {
129119
129343
  await loadSession(context, config3.auth.sessionFile);
129120
129344
  }
@@ -129207,6 +129431,10 @@ async function run(config3) {
129207
129431
  continue;
129208
129432
  }
129209
129433
  const currentUrl = normalizeUrl(page.url());
129434
+ if (currentUrl !== normalizeUrl(nextUrl) && globalState.visitedPages.has(currentUrl)) {
129435
+ log7.info("redirect to already-explored page, skipping", { requested: normalizeUrl(nextUrl), landed: currentUrl });
129436
+ continue;
129437
+ }
129210
129438
  globalState.visitedPages.add(currentUrl);
129211
129439
  log7.info("exploring page", { page: `${pagesExplored}/${maxPages}`, url: currentUrl });
129212
129440
  log7.debug("state", {
@@ -129258,7 +129486,12 @@ async function run(config3) {
129258
129486
  }
129259
129487
  log7.info("page changed after auth, re-exploring", { url: currentUrl });
129260
129488
  }
129261
- const discovered = await explorePageWithAI(page, currentUrl, interceptor, model, globalState, inScope, SINGLE_CRED, maxPages, usageAcc);
129489
+ const exploreInline = async (p, url3, depth) => {
129490
+ const found = await explorePageWithAI(p, url3, interceptor, model, globalState, inScope, SINGLE_CRED, maxPages, usageAcc, { explore: exploreInline, depth });
129491
+ for (const u of found)
129492
+ enqueueUrl(u, globalState, inScope);
129493
+ };
129494
+ const discovered = await explorePageWithAI(page, currentUrl, interceptor, model, globalState, inScope, SINGLE_CRED, maxPages, usageAcc, { explore: exploreInline, depth: 0 });
129262
129495
  let newEnqueued = 0;
129263
129496
  for (const url3 of discovered) {
129264
129497
  if (enqueueUrl(url3, globalState, inScope))