@cyberstrike-io/cyberstrike 1.1.16-beta.2 → 1.1.16-beta.3
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/hackbrowser-worker.js +301 -73
- package/package.json +12 -12
- package/postinstall.mjs +44 -0
- package/web/assets/{ghostty-web-ClrKZYRt.js → ghostty-web-BljlRKGw.js} +1 -1
- package/web/assets/{home-CBbUenzB.js → home-DselBJMO.js} +1 -1
- package/web/assets/{index-DMx52TJy.js → index-CJ0Cgzs8.js} +40 -41
- package/web/assets/session-D2a1VaLl.js +56 -0
- package/web/index.html +1 -1
- package/web/assets/session-BpWko3PJ.js +0 -55
package/hackbrowser-worker.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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]",
|
|
@@ -116438,7 +116485,8 @@ async function collectInteractiveElements(page) {
|
|
|
116438
116485
|
const disambiguatedLabel = count > 1 ? `${label} (${count})` : label;
|
|
116439
116486
|
const ariaLabelRaw = (el.getAttribute("aria-label") || "").trim();
|
|
116440
116487
|
const safeAriaLabel = ariaLabelRaw.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
116441
|
-
const
|
|
116488
|
+
const isFileInput = el.matches("input[type=file]");
|
|
116489
|
+
const selectorRole = syntheticRole || count > 1 || isFileInput ? "" : safeAriaLabel ? `role=${role}[name="${safeAriaLabel}"]` : `role=${role}`;
|
|
116442
116490
|
const selectorCSS = buildCSSSelector(el);
|
|
116443
116491
|
const inChrome = !!el.closest("nav, header, footer, aside, [role=navigation], [role=banner], [role=contentinfo], [role=complementary]");
|
|
116444
116492
|
const roleCount = (seenRoleSelectors.get(selectorRole) ?? 0) + 1;
|
|
@@ -116573,8 +116621,17 @@ function assignIds(browserElements, startId) {
|
|
|
116573
116621
|
}
|
|
116574
116622
|
async function collectElements(page) {
|
|
116575
116623
|
const browserElements = await collectInteractiveElements(page);
|
|
116576
|
-
const sampled = sampleTemplates(browserElements);
|
|
116577
|
-
return assignIds(sampled, 1)
|
|
116624
|
+
const sampled = capElements(sampleTemplates(browserElements));
|
|
116625
|
+
return assignIds(sampled, 1);
|
|
116626
|
+
}
|
|
116627
|
+
var ACTION_ROLES_CAP = new Set(["button", "link", "menuitem", "tab"]);
|
|
116628
|
+
function capElements(els) {
|
|
116629
|
+
if (els.length <= MAX_ELEMENTS)
|
|
116630
|
+
return els;
|
|
116631
|
+
const actions = els.filter((e2) => ACTION_ROLES_CAP.has(e2.role));
|
|
116632
|
+
const rest = els.filter((e2) => !ACTION_ROLES_CAP.has(e2.role));
|
|
116633
|
+
const kept = new Set([...actions, ...rest].slice(0, MAX_ELEMENTS));
|
|
116634
|
+
return els.filter((e2) => kept.has(e2));
|
|
116578
116635
|
}
|
|
116579
116636
|
function sampleTemplates(elements) {
|
|
116580
116637
|
const clusterCount = new Map;
|
|
@@ -116703,6 +116760,48 @@ function evalIsViewportCenterBlocked(page) {
|
|
|
116703
116760
|
return false;
|
|
116704
116761
|
});
|
|
116705
116762
|
}
|
|
116763
|
+
async function probeClickPoint(page, selector) {
|
|
116764
|
+
try {
|
|
116765
|
+
return await page.evaluate((sel) => {
|
|
116766
|
+
const el = document.querySelector(sel);
|
|
116767
|
+
if (!el)
|
|
116768
|
+
return { status: "clickable" };
|
|
116769
|
+
const r2 = el.getBoundingClientRect();
|
|
116770
|
+
if (r2.width === 0 || r2.height === 0)
|
|
116771
|
+
return { status: "clickable" };
|
|
116772
|
+
const cx = r2.left + r2.width / 2;
|
|
116773
|
+
const cy = r2.top + r2.height / 2;
|
|
116774
|
+
if (cx < 0 || cy < 0 || cx > window.innerWidth || cy > window.innerHeight) {
|
|
116775
|
+
return { status: "offscreen" };
|
|
116776
|
+
}
|
|
116777
|
+
const top = document.elementFromPoint(cx, cy);
|
|
116778
|
+
if (!top)
|
|
116779
|
+
return { status: "offscreen" };
|
|
116780
|
+
if (top === el || el.contains(top) || top.contains(el))
|
|
116781
|
+
return { status: "clickable" };
|
|
116782
|
+
if (top.tagName === "IFRAME")
|
|
116783
|
+
return { status: "clickable" };
|
|
116784
|
+
let node = el;
|
|
116785
|
+
while (node) {
|
|
116786
|
+
const root = node.getRootNode();
|
|
116787
|
+
if (root instanceof ShadowRoot) {
|
|
116788
|
+
const host = root.host;
|
|
116789
|
+
if (host === top || top.contains(host))
|
|
116790
|
+
return { status: "clickable" };
|
|
116791
|
+
node = host;
|
|
116792
|
+
} else
|
|
116793
|
+
break;
|
|
116794
|
+
}
|
|
116795
|
+
const text2 = (top.innerText || top.getAttribute("aria-label") || "").trim().replace(/\s+/g, " ").slice(0, 80);
|
|
116796
|
+
return {
|
|
116797
|
+
status: "occluded",
|
|
116798
|
+
occluder: { tag: top.tagName.toLowerCase(), role: top.getAttribute("role") || "", text: text2 }
|
|
116799
|
+
};
|
|
116800
|
+
}, selector);
|
|
116801
|
+
} catch {
|
|
116802
|
+
return { status: "clickable" };
|
|
116803
|
+
}
|
|
116804
|
+
}
|
|
116706
116805
|
function filterVisitedLinks(elements, currentUrl, visitedPages) {
|
|
116707
116806
|
let currentPath;
|
|
116708
116807
|
try {
|
|
@@ -116728,6 +116827,59 @@ function filterVisitedLinks(elements, currentUrl, visitedPages) {
|
|
|
116728
116827
|
});
|
|
116729
116828
|
}
|
|
116730
116829
|
|
|
116830
|
+
// ../hackbrowser/src/upload-samples.ts
|
|
116831
|
+
var nodeBuffer = globalThis.Buffer;
|
|
116832
|
+
var b64 = (s2) => nodeBuffer.from(s2, "base64");
|
|
116833
|
+
var txt = (s2) => nodeBuffer.from(s2, "utf8");
|
|
116834
|
+
var SAMPLES = [
|
|
116835
|
+
{
|
|
116836
|
+
name: "sample.png",
|
|
116837
|
+
ext: "png",
|
|
116838
|
+
mimeType: "image/png",
|
|
116839
|
+
buffer: b64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGJgAQAAAAUAAaX2RUAAAAAASUVORK5CYII=")
|
|
116840
|
+
},
|
|
116841
|
+
{
|
|
116842
|
+
name: "sample.jpg",
|
|
116843
|
+
ext: "jpg",
|
|
116844
|
+
mimeType: "image/jpeg",
|
|
116845
|
+
buffer: b64("/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AfwD/2Q==")
|
|
116846
|
+
},
|
|
116847
|
+
{
|
|
116848
|
+
name: "sample.pdf",
|
|
116849
|
+
ext: "pdf",
|
|
116850
|
+
mimeType: "application/pdf",
|
|
116851
|
+
buffer: b64("JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0+PmVuZG9iagp0cmFpbGVyPDwvUm9vdCAxIDAgUj4+CiUlRU9GCg==")
|
|
116852
|
+
},
|
|
116853
|
+
{ name: "sample.csv", ext: "csv", mimeType: "text/csv", buffer: txt(`name,email
|
|
116854
|
+
Test User,test@example.com
|
|
116855
|
+
`) },
|
|
116856
|
+
{ name: "sample.txt", ext: "txt", mimeType: "text/plain", buffer: txt(`sample text file
|
|
116857
|
+
`) },
|
|
116858
|
+
{ name: "sample.json", ext: "json", mimeType: "application/json", buffer: txt(`{"sample":true,"value":123}
|
|
116859
|
+
`) }
|
|
116860
|
+
];
|
|
116861
|
+
var DEFAULT_SAMPLE = SAMPLES[0];
|
|
116862
|
+
function tokenMatches(sample, token) {
|
|
116863
|
+
const t2 = token.trim().toLowerCase();
|
|
116864
|
+
if (!t2)
|
|
116865
|
+
return false;
|
|
116866
|
+
if (t2.startsWith("."))
|
|
116867
|
+
return t2.slice(1) === sample.ext;
|
|
116868
|
+
if (t2.endsWith("/*"))
|
|
116869
|
+
return sample.mimeType.startsWith(t2.slice(0, -1));
|
|
116870
|
+
return t2 === sample.mimeType;
|
|
116871
|
+
}
|
|
116872
|
+
function pickSample(accept) {
|
|
116873
|
+
if (!accept || !accept.trim())
|
|
116874
|
+
return DEFAULT_SAMPLE;
|
|
116875
|
+
const tokens = accept.split(",");
|
|
116876
|
+
for (const sample of SAMPLES) {
|
|
116877
|
+
if (tokens.some((tok) => tokenMatches(sample, tok)))
|
|
116878
|
+
return sample;
|
|
116879
|
+
}
|
|
116880
|
+
return DEFAULT_SAMPLE;
|
|
116881
|
+
}
|
|
116882
|
+
|
|
116731
116883
|
// ../hackbrowser/src/executor.ts
|
|
116732
116884
|
var log6 = Log.create({ service: "hackbrowser:executor" });
|
|
116733
116885
|
var CLICK_TIMEOUT = 2000;
|
|
@@ -116773,6 +116925,9 @@ async function executeFill(page, element, value) {
|
|
|
116773
116925
|
if (element.role === "slider" || element.type === "range") {
|
|
116774
116926
|
return executeSliderFill(page, selector || "role=slider", value);
|
|
116775
116927
|
}
|
|
116928
|
+
if (element.type === "file") {
|
|
116929
|
+
return executeFileSelect(page, selector);
|
|
116930
|
+
}
|
|
116776
116931
|
const fillErr = await page.fill(selector, value, { timeout: FILL_TIMEOUT }).then(() => null).catch((e2) => e2.message.split(`
|
|
116777
116932
|
`)[0]);
|
|
116778
116933
|
if (!fillErr)
|
|
@@ -116784,6 +116939,18 @@ async function executeFill(page, element, value) {
|
|
|
116784
116939
|
log6.warn("fill failed", { selector, error: fillErr });
|
|
116785
116940
|
return fillErr;
|
|
116786
116941
|
}
|
|
116942
|
+
async function executeFileSelect(page, selector) {
|
|
116943
|
+
const accept = await page.getAttribute(selector, "accept").catch(() => null);
|
|
116944
|
+
const sample = pickSample(accept);
|
|
116945
|
+
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(`
|
|
116946
|
+
`)[0]);
|
|
116947
|
+
if (err) {
|
|
116948
|
+
log6.warn("file select failed", { selector, accept, error: err });
|
|
116949
|
+
return err;
|
|
116950
|
+
}
|
|
116951
|
+
log6.info("file selected", { selector, file: sample.name });
|
|
116952
|
+
return;
|
|
116953
|
+
}
|
|
116787
116954
|
async function executeSliderFill(page, selector, value) {
|
|
116788
116955
|
const numericValue = parseInt(value);
|
|
116789
116956
|
if (isNaN(numericValue) || numericValue <= 0) {
|
|
@@ -127843,7 +128010,8 @@ function normalizeScope(input) {
|
|
|
127843
128010
|
s2 = s2.replace(/^https?:\/\//, "");
|
|
127844
128011
|
s2 = s2.split(/[/?#]/)[0] ?? "";
|
|
127845
128012
|
s2 = s2.replace(/:\d+$/, "");
|
|
127846
|
-
|
|
128013
|
+
while (s2.endsWith("."))
|
|
128014
|
+
s2 = s2.slice(0, -1);
|
|
127847
128015
|
return s2;
|
|
127848
128016
|
}
|
|
127849
128017
|
function deriveScope(targetUrl) {
|
|
@@ -127872,8 +128040,10 @@ var log7 = Log.create({ service: "hackbrowser:agent" });
|
|
|
127872
128040
|
var MAX_STEPS_PER_PAGE = 30;
|
|
127873
128041
|
var MAX_UNPLANNED_ITERATIONS = 2;
|
|
127874
128042
|
var OVERLAY_ESCAPE_WAIT = 400;
|
|
128043
|
+
var OVERLAY_CLEAR_WAIT = 800;
|
|
127875
128044
|
var SPA_RENDER_RETRY_WAIT = 600;
|
|
127876
128045
|
var POST_GOTO_WAIT = 400;
|
|
128046
|
+
var MAX_INLINE_DEPTH = 2;
|
|
127877
128047
|
var LOGIN_SUCCESS_PATTERN = /POST\s+.*\/(login|signin|authenticate)\S*\s+\[200\]/i;
|
|
127878
128048
|
function triggerReDiscovery(globalState) {
|
|
127879
128049
|
if (globalState.authPhase === "authenticated")
|
|
@@ -128036,6 +128206,15 @@ function attachDialogAutoAccept(page) {
|
|
|
128036
128206
|
dialog.accept().catch(() => {});
|
|
128037
128207
|
});
|
|
128038
128208
|
}
|
|
128209
|
+
function attachFileChooserAutoFill(page) {
|
|
128210
|
+
page.on("filechooser", async (chooser) => {
|
|
128211
|
+
try {
|
|
128212
|
+
const accept = await chooser.element().getAttribute("accept").catch(() => null);
|
|
128213
|
+
const sample = pickSample(accept);
|
|
128214
|
+
await chooser.setFiles({ name: sample.name, mimeType: sample.mimeType, buffer: sample.buffer });
|
|
128215
|
+
} catch {}
|
|
128216
|
+
});
|
|
128217
|
+
}
|
|
128039
128218
|
function setupRequestInterceptor(page, inScope, onCapture, credentialId = SINGLE_CRED) {
|
|
128040
128219
|
let pendingUIContext = null;
|
|
128041
128220
|
let pendingTrigger = null;
|
|
@@ -128185,9 +128364,10 @@ function getPathPatternKey(url3) {
|
|
|
128185
128364
|
return null;
|
|
128186
128365
|
}
|
|
128187
128366
|
}
|
|
128188
|
-
async function explorePageWithAI(page, pageUrl, interceptor, model, globalState, inScope, credentialId, maxPages, usageAcc) {
|
|
128367
|
+
async function explorePageWithAI(page, pageUrl, interceptor, model, globalState, inScope, credentialId, maxPages, usageAcc, nav = { explore: async () => {}, depth: MAX_INLINE_DEPTH }) {
|
|
128189
128368
|
const linksToEnqueue = [];
|
|
128190
128369
|
const semanticActionsDone = new Set;
|
|
128370
|
+
const occ = { pending: [], signaled: new Set };
|
|
128191
128371
|
csEmit(page, {
|
|
128192
128372
|
type: "page-change",
|
|
128193
128373
|
url: pageUrl,
|
|
@@ -128246,9 +128426,9 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
|
|
|
128246
128426
|
remaining: taskQueue.length
|
|
128247
128427
|
});
|
|
128248
128428
|
if (task.type === "form") {
|
|
128249
|
-
await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
|
|
128429
|
+
await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
|
|
128250
128430
|
} else {
|
|
128251
|
-
await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
|
|
128431
|
+
await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
|
|
128252
128432
|
}
|
|
128253
128433
|
const postActionElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
|
|
128254
128434
|
if (task.type === "click" && task.role === "combobox") {
|
|
@@ -128259,9 +128439,10 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
|
|
|
128259
128439
|
}
|
|
128260
128440
|
const hasNewElements = discoverNewElements(postActionElements, seenKeys);
|
|
128261
128441
|
const hasStaleTargets = queueHasStaleTargets(taskQueue, postActionElements);
|
|
128262
|
-
if (hasNewElements || hasStaleTargets) {
|
|
128442
|
+
if (hasNewElements || hasStaleTargets || occ.pending.length > 0) {
|
|
128263
128443
|
const freshElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
|
|
128264
|
-
const snapshot2 = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page));
|
|
128444
|
+
const snapshot2 = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page), occ.pending);
|
|
128445
|
+
occ.pending = [];
|
|
128265
128446
|
csEmit(page, {
|
|
128266
128447
|
type: "llm-thinking",
|
|
128267
128448
|
reason: "replan",
|
|
@@ -128367,9 +128548,9 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
|
|
|
128367
128548
|
remaining: additionalQueue.length
|
|
128368
128549
|
});
|
|
128369
128550
|
if (task.type === "form") {
|
|
128370
|
-
await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
|
|
128551
|
+
await executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
|
|
128371
128552
|
} else {
|
|
128372
|
-
await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope);
|
|
128553
|
+
await executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav);
|
|
128373
128554
|
}
|
|
128374
128555
|
const postActionElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
|
|
128375
128556
|
if (task.type === "click" && task.role === "combobox") {
|
|
@@ -128379,9 +128560,10 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
|
|
|
128379
128560
|
}
|
|
128380
128561
|
const hasNewElements = discoverNewElements(postActionElements, seenKeys);
|
|
128381
128562
|
const hasStaleTargets = queueHasStaleTargets(additionalQueue, postActionElements);
|
|
128382
|
-
if (hasNewElements || hasStaleTargets) {
|
|
128563
|
+
if (hasNewElements || hasStaleTargets || occ.pending.length > 0) {
|
|
128383
128564
|
const freshElements = filterVisitedLinks(await collectElements(page), pageUrl, globalState.visitedPages);
|
|
128384
|
-
const freshSnap = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page));
|
|
128565
|
+
const freshSnap = buildPlannerSnapshot(pageUrl, freshElements, globalState, credentialId, await isViewportCenterBlocked(page), occ.pending);
|
|
128566
|
+
occ.pending = [];
|
|
128385
128567
|
csEmit(page, {
|
|
128386
128568
|
type: "llm-thinking",
|
|
128387
128569
|
reason: "replan",
|
|
@@ -128431,7 +128613,45 @@ async function explorePageWithAI(page, pageUrl, interceptor, model, globalState,
|
|
|
128431
128613
|
}
|
|
128432
128614
|
return linksToEnqueue;
|
|
128433
128615
|
}
|
|
128434
|
-
async function
|
|
128616
|
+
async function recoverFromOcclusion(page, el, original, retry) {
|
|
128617
|
+
if (original.success)
|
|
128618
|
+
return original;
|
|
128619
|
+
if ((await probeClickPoint(page, el.selector)).status !== "occluded")
|
|
128620
|
+
return original;
|
|
128621
|
+
log7.debug("click occluded — clearing overlay + waiting, then retrying once", { label: el.label });
|
|
128622
|
+
await closeOverlay(page);
|
|
128623
|
+
await page.waitForTimeout(OVERLAY_CLEAR_WAIT);
|
|
128624
|
+
return retry();
|
|
128625
|
+
}
|
|
128626
|
+
async function diagnoseOcclusion(page, el, occ) {
|
|
128627
|
+
const probe = await probeClickPoint(page, el.selector);
|
|
128628
|
+
if (probe.status === "occluded" && !occ.signaled.has(probe.occluder.text)) {
|
|
128629
|
+
occ.signaled.add(probe.occluder.text);
|
|
128630
|
+
occ.pending.push({ label: el.label, role: el.role, occluderText: probe.occluder.text });
|
|
128631
|
+
log7.debug("action target occluded — signalling planner to dismiss overlay first", {
|
|
128632
|
+
label: el.label,
|
|
128633
|
+
occluder: probe.occluder.text
|
|
128634
|
+
});
|
|
128635
|
+
return true;
|
|
128636
|
+
}
|
|
128637
|
+
return false;
|
|
128638
|
+
}
|
|
128639
|
+
async function handleNavigation(page, parentUrl, linksToEnqueue, inScope, globalState, nav) {
|
|
128640
|
+
const cur = page.url();
|
|
128641
|
+
const curNorm = normalizeUrl(cur);
|
|
128642
|
+
if (curNorm !== normalizeUrl(parentUrl) && isInScope(cur, inScope)) {
|
|
128643
|
+
if (!globalState.visitedPages.has(curNorm) && nav.depth < MAX_INLINE_DEPTH) {
|
|
128644
|
+
globalState.visitedPages.add(curNorm);
|
|
128645
|
+
await nav.explore(page, cur, nav.depth + 1);
|
|
128646
|
+
} else {
|
|
128647
|
+
linksToEnqueue.push(cur);
|
|
128648
|
+
}
|
|
128649
|
+
}
|
|
128650
|
+
await page.goto(parentUrl, { waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
|
|
128651
|
+
await page.waitForTimeout(POST_GOTO_WAIT);
|
|
128652
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 5000 }).catch(() => {});
|
|
128653
|
+
}
|
|
128654
|
+
async function executeFormTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav) {
|
|
128435
128655
|
for (const field of task.fields) {
|
|
128436
128656
|
let el = resolveElement(elements, field.role, field.label);
|
|
128437
128657
|
if (!el) {
|
|
@@ -128483,23 +128703,19 @@ async function executeFormTask(task, page, elements, interceptor, semanticAction
|
|
|
128483
128703
|
targetSelector: submitEl.selector,
|
|
128484
128704
|
credential: credentialId
|
|
128485
128705
|
});
|
|
128486
|
-
|
|
128706
|
+
let result = await execute(page, submitEl, "click", undefined, interceptor.setPendingUI, elements.length);
|
|
128707
|
+
result = await recoverFromOcclusion(page, submitEl, result, () => execute(page, submitEl, "click", undefined, interceptor.setPendingUI, elements.length));
|
|
128708
|
+
const occluded = !result.success && await diagnoseOcclusion(page, submitEl, occ);
|
|
128487
128709
|
interceptor.clearPendingTrigger();
|
|
128488
128710
|
trackResult(result, interceptor, globalState, credentialId, task.triggersMutation, page);
|
|
128489
128711
|
csEmit(page, { type: "action-end", ok: result.success, credential: credentialId });
|
|
128490
|
-
|
|
128712
|
+
if (!occluded)
|
|
128713
|
+
semanticActionsDone.add(submitKey);
|
|
128491
128714
|
globalState.totalSteps++;
|
|
128492
|
-
if (result.navigated)
|
|
128493
|
-
|
|
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
|
-
}
|
|
128715
|
+
if (result.navigated)
|
|
128716
|
+
await handleNavigation(page, pageUrl, linksToEnqueue, inScope, globalState, nav);
|
|
128501
128717
|
}
|
|
128502
|
-
async function executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope) {
|
|
128718
|
+
async function executeClickTask(task, page, elements, interceptor, semanticActionsDone, globalState, credentialId, linksToEnqueue, pageUrl, inScope, occ, nav) {
|
|
128503
128719
|
const el = resolveElement(elements, task.role, task.label);
|
|
128504
128720
|
if (!el) {
|
|
128505
128721
|
log7.debug("click target not found", { role: task.role, label: task.label });
|
|
@@ -128518,15 +128734,13 @@ async function executeClickTask(task, page, elements, interceptor, semanticActio
|
|
|
128518
128734
|
credential: credentialId
|
|
128519
128735
|
});
|
|
128520
128736
|
let result = await execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length);
|
|
128521
|
-
|
|
128522
|
-
|
|
128523
|
-
await closeOverlay(page);
|
|
128524
|
-
result = await execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length);
|
|
128525
|
-
}
|
|
128737
|
+
result = await recoverFromOcclusion(page, el, result, () => execute(page, el, "click", undefined, interceptor.setPendingUI, elements.length));
|
|
128738
|
+
const occluded = !result.success && await diagnoseOcclusion(page, el, occ);
|
|
128526
128739
|
interceptor.clearPendingTrigger();
|
|
128527
128740
|
trackResult(result, interceptor, globalState, credentialId, task.triggersMutation, page);
|
|
128528
128741
|
csEmit(page, { type: "action-end", ok: result.success, credential: credentialId });
|
|
128529
|
-
|
|
128742
|
+
if (!occluded)
|
|
128743
|
+
semanticActionsDone.add(key);
|
|
128530
128744
|
globalState.totalSteps++;
|
|
128531
128745
|
if (el.role === "option") {
|
|
128532
128746
|
const parentCombobox = elements.find((e2) => e2.role === "combobox");
|
|
@@ -128535,12 +128749,7 @@ async function executeClickTask(task, page, elements, interceptor, semanticActio
|
|
|
128535
128749
|
}
|
|
128536
128750
|
}
|
|
128537
128751
|
if (result.navigated) {
|
|
128538
|
-
|
|
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(() => {});
|
|
128752
|
+
await handleNavigation(page, pageUrl, linksToEnqueue, inScope, globalState, nav);
|
|
128544
128753
|
return;
|
|
128545
128754
|
}
|
|
128546
128755
|
if (el.role === "combobox")
|
|
@@ -128870,6 +129079,7 @@ async function runMultiCredential(config3, credentials) {
|
|
|
128870
129079
|
await browserContext.addInitScript(PANEL_INIT_SCRIPT);
|
|
128871
129080
|
const page = await browserContext.newPage();
|
|
128872
129081
|
attachDialogAutoAccept(page);
|
|
129082
|
+
attachFileChooserAutoFill(page);
|
|
128873
129083
|
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
128874
129084
|
csEmit(page, {
|
|
128875
129085
|
type: "init",
|
|
@@ -128975,11 +129185,14 @@ async function runMultiCredential(config3, credentials) {
|
|
|
128975
129185
|
});
|
|
128976
129186
|
continue;
|
|
128977
129187
|
}
|
|
128978
|
-
|
|
129188
|
+
const landed = normalizeUrl(r2.ctx.page.url());
|
|
129189
|
+
log7.info("redirect to different page, enqueueing landed URL", {
|
|
128979
129190
|
credential: r2.ctx.id,
|
|
128980
129191
|
intended: entry.url,
|
|
128981
|
-
|
|
129192
|
+
landed
|
|
128982
129193
|
});
|
|
129194
|
+
enqueueWithContext(landed, r2.ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
|
|
129195
|
+
continue;
|
|
128983
129196
|
}
|
|
128984
129197
|
visitableContexts.push(r2.ctx);
|
|
128985
129198
|
}
|
|
@@ -129022,7 +129235,12 @@ async function runMultiCredential(config3, credentials) {
|
|
|
129022
129235
|
}
|
|
129023
129236
|
for (const ctx of visitableContexts) {
|
|
129024
129237
|
log7.info("exploring", { credential: ctx.id, url: entry.url });
|
|
129025
|
-
const
|
|
129238
|
+
const exploreInline = async (p, url3, depth) => {
|
|
129239
|
+
const found = await explorePageWithAI(p, url3, ctx.interceptor, model, globalState, inScope, ctx.id, maxPages, usageAcc, { explore: exploreInline, depth });
|
|
129240
|
+
for (const u of found)
|
|
129241
|
+
enqueueWithContext(u, ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
|
|
129242
|
+
};
|
|
129243
|
+
const discovered = await explorePageWithAI(ctx.page, entry.url, ctx.interceptor, model, globalState, inScope, ctx.id, maxPages, usageAcc, { explore: exploreInline, depth: 0 });
|
|
129026
129244
|
for (const url3 of discovered) {
|
|
129027
129245
|
enqueueWithContext(url3, ctx.id, pageQueue, visitedPages, inScope, pathPatternCounts);
|
|
129028
129246
|
}
|
|
@@ -129115,6 +129333,7 @@ async function run(config3) {
|
|
|
129115
129333
|
await context.addInitScript(PANEL_INIT_SCRIPT);
|
|
129116
129334
|
const page = await context.newPage();
|
|
129117
129335
|
attachDialogAutoAccept(page);
|
|
129336
|
+
attachFileChooserAutoFill(page);
|
|
129118
129337
|
if (config3.auth.sessionFile) {
|
|
129119
129338
|
await loadSession(context, config3.auth.sessionFile);
|
|
129120
129339
|
}
|
|
@@ -129207,6 +129426,10 @@ async function run(config3) {
|
|
|
129207
129426
|
continue;
|
|
129208
129427
|
}
|
|
129209
129428
|
const currentUrl = normalizeUrl(page.url());
|
|
129429
|
+
if (currentUrl !== normalizeUrl(nextUrl) && globalState.visitedPages.has(currentUrl)) {
|
|
129430
|
+
log7.info("redirect to already-explored page, skipping", { requested: normalizeUrl(nextUrl), landed: currentUrl });
|
|
129431
|
+
continue;
|
|
129432
|
+
}
|
|
129210
129433
|
globalState.visitedPages.add(currentUrl);
|
|
129211
129434
|
log7.info("exploring page", { page: `${pagesExplored}/${maxPages}`, url: currentUrl });
|
|
129212
129435
|
log7.debug("state", {
|
|
@@ -129258,7 +129481,12 @@ async function run(config3) {
|
|
|
129258
129481
|
}
|
|
129259
129482
|
log7.info("page changed after auth, re-exploring", { url: currentUrl });
|
|
129260
129483
|
}
|
|
129261
|
-
const
|
|
129484
|
+
const exploreInline = async (p, url3, depth) => {
|
|
129485
|
+
const found = await explorePageWithAI(p, url3, interceptor, model, globalState, inScope, SINGLE_CRED, maxPages, usageAcc, { explore: exploreInline, depth });
|
|
129486
|
+
for (const u of found)
|
|
129487
|
+
enqueueUrl(u, globalState, inScope);
|
|
129488
|
+
};
|
|
129489
|
+
const discovered = await explorePageWithAI(page, currentUrl, interceptor, model, globalState, inScope, SINGLE_CRED, maxPages, usageAcc, { explore: exploreInline, depth: 0 });
|
|
129262
129490
|
let newEnqueued = 0;
|
|
129263
129491
|
for (const url3 of discovered) {
|
|
129264
129492
|
if (enqueueUrl(url3, globalState, inScope))
|