sitelooper 0.3.0
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/LICENSE +21 -0
- package/README.md +625 -0
- package/bin/sitelooper.js +6 -0
- package/dist/agent/llm.js +460 -0
- package/dist/agent/llm.js.map +1 -0
- package/dist/agent/loop.js +870 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/prompt.js +40 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/report.js +545 -0
- package/dist/agent/report.js.map +1 -0
- package/dist/agent/tools.js +1147 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/cli.js +1692 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon/browser.js +218 -0
- package/dist/daemon/browser.js.map +1 -0
- package/dist/daemon/codegen.js +241 -0
- package/dist/daemon/codegen.js.map +1 -0
- package/dist/daemon/dialogs.js +57 -0
- package/dist/daemon/dialogs.js.map +1 -0
- package/dist/daemon/diff.js +198 -0
- package/dist/daemon/diff.js.map +1 -0
- package/dist/daemon/fingerprint.js +98 -0
- package/dist/daemon/fingerprint.js.map +1 -0
- package/dist/daemon/inputs.js +134 -0
- package/dist/daemon/inputs.js.map +1 -0
- package/dist/daemon/recorder.js +1232 -0
- package/dist/daemon/recorder.js.map +1 -0
- package/dist/daemon/refs.js +194 -0
- package/dist/daemon/refs.js.map +1 -0
- package/dist/daemon/server.js +1724 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/state.js +239 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/doctor.js +90 -0
- package/dist/doctor.js.map +1 -0
- package/dist/shared/paths.js +80 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/protocol.js +28 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/shared/secrets.js +92 -0
- package/dist/shared/secrets.js.map +1 -0
- package/dist/shared/text.js +39 -0
- package/dist/shared/text.js.map +1 -0
- package/dist/skills/compile.js +1420 -0
- package/dist/skills/compile.js.map +1 -0
- package/dist/skills/components.js +456 -0
- package/dist/skills/components.js.map +1 -0
- package/dist/skills/flow.js +1041 -0
- package/dist/skills/flow.js.map +1 -0
- package/dist/skills/learn.js +406 -0
- package/dist/skills/learn.js.map +1 -0
- package/dist/skills/ledger.js +304 -0
- package/dist/skills/ledger.js.map +1 -0
- package/dist/skills/relabel.js +206 -0
- package/dist/skills/relabel.js.map +1 -0
- package/dist/skills/repair.js +570 -0
- package/dist/skills/repair.js.map +1 -0
- package/dist/skills/replay.js +1281 -0
- package/dist/skills/replay.js.map +1 -0
- package/dist/skills/store.js +147 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/spec/check.js +428 -0
- package/dist/spec/check.js.map +1 -0
- package/dist/spec/diagnostics.js +58 -0
- package/dist/spec/diagnostics.js.map +1 -0
- package/dist/spec/emit.js +2084 -0
- package/dist/spec/emit.js.map +1 -0
- package/dist/spec/index.js +62 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/spec/ir.js +216 -0
- package/dist/spec/ir.js.map +1 -0
- package/dist/spec/lift.js +162 -0
- package/dist/spec/lift.js.map +1 -0
- package/dist/spec/locators.js +270 -0
- package/dist/spec/locators.js.map +1 -0
- package/dist/spec/lower.js +124 -0
- package/dist/spec/lower.js.map +1 -0
- package/dist/spec/repair.js +657 -0
- package/dist/spec/repair.js.map +1 -0
- package/dist/spec/rerecord.js +169 -0
- package/dist/spec/rerecord.js.map +1 -0
- package/dist/spec/rethread.js +120 -0
- package/dist/spec/rethread.js.map +1 -0
- package/package.json +50 -0
- package/skills/sitelooper/SKILL.md +228 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native dialog handling (friction #4). Every page gets a handler installed;
|
|
3
|
+
* by default dialogs are dismissed and captured so the agent sees them.
|
|
4
|
+
* `dialog_expect` arms a one-shot (or N-shot) accept/dismiss policy for the
|
|
5
|
+
* next action. Captured messages are drained into tool results.
|
|
6
|
+
*/
|
|
7
|
+
export class DialogManager {
|
|
8
|
+
policy = null;
|
|
9
|
+
captured = [];
|
|
10
|
+
attached = new WeakSet();
|
|
11
|
+
attach(page) {
|
|
12
|
+
if (this.attached.has(page))
|
|
13
|
+
return;
|
|
14
|
+
this.attached.add(page);
|
|
15
|
+
page.on('dialog', (dialog) => void this.handle(dialog));
|
|
16
|
+
}
|
|
17
|
+
arm(policy) {
|
|
18
|
+
this.policy = { remaining: 1, ...policy };
|
|
19
|
+
}
|
|
20
|
+
async handle(dialog) {
|
|
21
|
+
let action = 'dismiss';
|
|
22
|
+
let promptText;
|
|
23
|
+
if (this.policy && this.policy.remaining > 0) {
|
|
24
|
+
action = this.policy.action;
|
|
25
|
+
promptText = this.policy.promptText;
|
|
26
|
+
this.policy.remaining -= 1;
|
|
27
|
+
if (this.policy.remaining <= 0)
|
|
28
|
+
this.policy = null;
|
|
29
|
+
}
|
|
30
|
+
else if (dialog.type() === 'beforeunload') {
|
|
31
|
+
action = 'accept'; // otherwise navigation deadlocks
|
|
32
|
+
}
|
|
33
|
+
this.captured.push({ type: dialog.type(), message: dialog.message(), action, promptText });
|
|
34
|
+
try {
|
|
35
|
+
if (action === 'accept')
|
|
36
|
+
await dialog.accept(promptText);
|
|
37
|
+
else
|
|
38
|
+
await dialog.dismiss();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// dialog may already be handled/closed
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Return captured dialogs since last drain and clear the buffer. */
|
|
45
|
+
drain() {
|
|
46
|
+
const out = this.captured;
|
|
47
|
+
this.captured = [];
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
peek() {
|
|
51
|
+
return [...this.captured];
|
|
52
|
+
}
|
|
53
|
+
armedPolicy() {
|
|
54
|
+
return this.policy;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=dialogs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogs.js","sourceRoot":"","sources":["../../src/daemon/dialogs.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAChB,MAAM,GAAwB,IAAI,CAAC;IACnC,QAAQ,GAAqB,EAAE,CAAC;IAChC,QAAQ,GAAG,IAAI,OAAO,EAAQ,CAAC;IAEvC,MAAM,CAAC,IAAU;QACf,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO;QACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,GAAG,CAAC,MAAgE;QAClE,IAAI,CAAC,MAAM,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,MAAc;QACjC,IAAI,MAAM,GAAyB,SAAS,CAAC;QAC7C,IAAI,UAA8B,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YAC5B,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC;gBAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrD,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,cAAc,EAAE,CAAC;YAC5C,MAAM,GAAG,QAAQ,CAAC,CAAC,iCAAiC;QACtD,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QAC3F,IAAI,CAAC;YACH,IAAI,MAAM,KAAK,QAAQ;gBAAE,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;;gBACpD,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QACnB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { clip } from '../shared/text.js';
|
|
2
|
+
import { isInteractiveLine, truncate } from './refs.js';
|
|
3
|
+
const CAPTURE_TIMEOUT = 2_000;
|
|
4
|
+
const MAX_ALERTS = 5;
|
|
5
|
+
const MAX_ALERT_CHARS = 200;
|
|
6
|
+
const MAX_LINE_CHARS = 120;
|
|
7
|
+
const LIST_LINE_BUDGET = 12;
|
|
8
|
+
const DIFF_BUDGET = 700;
|
|
9
|
+
/**
|
|
10
|
+
* Fingerprint the live page, or null if it could not be read in time (a
|
|
11
|
+
* navigating/closing page, a busy renderer). Diffing is a convenience layered
|
|
12
|
+
* on top of an action that already succeeded, so a failed capture must degrade
|
|
13
|
+
* to "no diff", never to an error.
|
|
14
|
+
*/
|
|
15
|
+
export async function captureSignature(page) {
|
|
16
|
+
let timer;
|
|
17
|
+
try {
|
|
18
|
+
return await Promise.race([
|
|
19
|
+
capture(page),
|
|
20
|
+
new Promise((resolve) => {
|
|
21
|
+
timer = setTimeout(() => resolve(null), CAPTURE_TIMEOUT);
|
|
22
|
+
}),
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function capture(page) {
|
|
33
|
+
// NOT ariaSnapshot(): every call to it — mode:'ai' *or* plain — re-mints
|
|
34
|
+
// Playwright's [ref=eN] registry, and a plain call leaves it empty, so the
|
|
35
|
+
// @refs the agent holds from its last explicit snapshot stop resolving.
|
|
36
|
+
// This runs after every action, so it walks the DOM itself instead.
|
|
37
|
+
// Everything describeInPage needs is passed in: it is serialised into the
|
|
38
|
+
// page, so module-level constants are not in scope there.
|
|
39
|
+
const { lines, alerts } = await page.evaluate(describeInPage, {
|
|
40
|
+
maxAlerts: MAX_ALERTS,
|
|
41
|
+
maxAlertChars: MAX_ALERT_CHARS,
|
|
42
|
+
maxNodes: 4_000,
|
|
43
|
+
maxLines: 400,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
url: page.url(),
|
|
47
|
+
title: await page.title(),
|
|
48
|
+
lines: lines.filter(isInteractiveLine),
|
|
49
|
+
alerts,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Runs in the page: an aria-snapshot-shaped list of the roles, names and values
|
|
54
|
+
* currently on screen, plus live-region text. Deliberately hand-rolled rather
|
|
55
|
+
* than delegated to ariaSnapshot — see capture().
|
|
56
|
+
*/
|
|
57
|
+
function describeInPage(opts) {
|
|
58
|
+
const clean = (s) => (s ?? '').replace(/\s+/g, ' ').trim();
|
|
59
|
+
const roleOf = (el) => {
|
|
60
|
+
const explicit = clean(el.getAttribute('role'));
|
|
61
|
+
if (explicit)
|
|
62
|
+
return explicit.split(' ')[0];
|
|
63
|
+
const tag = el.tagName.toLowerCase();
|
|
64
|
+
const type = (el.getAttribute('type') || '').toLowerCase();
|
|
65
|
+
if (tag === 'button')
|
|
66
|
+
return 'button';
|
|
67
|
+
if (tag === 'a')
|
|
68
|
+
return el.hasAttribute('href') ? 'link' : null;
|
|
69
|
+
if (tag === 'select')
|
|
70
|
+
return el.hasAttribute('multiple') ? 'listbox' : 'combobox';
|
|
71
|
+
if (tag === 'textarea')
|
|
72
|
+
return 'textbox';
|
|
73
|
+
if (tag === 'option')
|
|
74
|
+
return 'option';
|
|
75
|
+
if (tag === 'td' || tag === 'th')
|
|
76
|
+
return 'cell';
|
|
77
|
+
if (tag === 'tr')
|
|
78
|
+
return 'row';
|
|
79
|
+
if (tag === 'dialog')
|
|
80
|
+
return 'dialog';
|
|
81
|
+
if (/^h[1-6]$/.test(tag))
|
|
82
|
+
return 'heading';
|
|
83
|
+
if (tag === 'input') {
|
|
84
|
+
if (type === 'checkbox')
|
|
85
|
+
return 'checkbox';
|
|
86
|
+
if (type === 'radio')
|
|
87
|
+
return 'radio';
|
|
88
|
+
if (type === 'range')
|
|
89
|
+
return 'slider';
|
|
90
|
+
if (type === 'search')
|
|
91
|
+
return 'searchbox';
|
|
92
|
+
if (type === 'number')
|
|
93
|
+
return 'spinbutton';
|
|
94
|
+
if (type === 'submit' || type === 'button' || type === 'reset')
|
|
95
|
+
return 'button';
|
|
96
|
+
if (type === 'hidden' || type === 'file')
|
|
97
|
+
return null;
|
|
98
|
+
return 'textbox';
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
const nameOf = (el) => {
|
|
103
|
+
const labelledBy = el.getAttribute('aria-labelledby');
|
|
104
|
+
const fromIds = labelledBy
|
|
105
|
+
? labelledBy
|
|
106
|
+
.split(/\s+/)
|
|
107
|
+
.map((id) => el.ownerDocument.getElementById(id)?.textContent ?? '')
|
|
108
|
+
.join(' ')
|
|
109
|
+
: '';
|
|
110
|
+
const own = clean(el.getAttribute('aria-label') ||
|
|
111
|
+
fromIds ||
|
|
112
|
+
el.getAttribute('alt') ||
|
|
113
|
+
el.getAttribute('title') ||
|
|
114
|
+
el.getAttribute('placeholder'));
|
|
115
|
+
if (own)
|
|
116
|
+
return own.slice(0, 80);
|
|
117
|
+
const label = clean(el.closest('label')?.textContent);
|
|
118
|
+
if (label)
|
|
119
|
+
return label.slice(0, 80);
|
|
120
|
+
// Only a short subtree reads as this element's own name; anything longer is
|
|
121
|
+
// a container's text and would make the line churn on unrelated changes.
|
|
122
|
+
const text = clean(el.innerText);
|
|
123
|
+
return text.length <= 80 ? text : '';
|
|
124
|
+
};
|
|
125
|
+
const lines = [];
|
|
126
|
+
const all = Array.from(document.querySelectorAll('*')).slice(0, opts.maxNodes);
|
|
127
|
+
for (const el of all) {
|
|
128
|
+
if (lines.length >= opts.maxLines)
|
|
129
|
+
break;
|
|
130
|
+
const role = roleOf(el);
|
|
131
|
+
if (!role)
|
|
132
|
+
continue;
|
|
133
|
+
if (el.getClientRects().length === 0)
|
|
134
|
+
continue;
|
|
135
|
+
let line = `- ${role} ${JSON.stringify(nameOf(el))}`;
|
|
136
|
+
const input = el;
|
|
137
|
+
if (input.type === 'checkbox' || input.type === 'radio') {
|
|
138
|
+
line += input.checked ? ' [checked]' : '';
|
|
139
|
+
}
|
|
140
|
+
else if (typeof input.value === 'string' && input.value) {
|
|
141
|
+
line += `: ${clean(input.value).slice(0, 80)}`;
|
|
142
|
+
}
|
|
143
|
+
lines.push(line);
|
|
144
|
+
}
|
|
145
|
+
const alerts = Array.from(document.querySelectorAll('[role=alert],[role=status]'))
|
|
146
|
+
.filter((el) => el.getClientRects().length > 0)
|
|
147
|
+
.slice(0, opts.maxAlerts)
|
|
148
|
+
.map((el) => clean(el.innerText).slice(0, opts.maxAlertChars))
|
|
149
|
+
.filter((text) => text.length > 0);
|
|
150
|
+
return { lines, alerts };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Compact description of what changed between two signatures. Lines are
|
|
154
|
+
* compared as a multiset, so reordering alone is not a change — only content
|
|
155
|
+
* appearing or disappearing is worth spending the agent's attention on.
|
|
156
|
+
*/
|
|
157
|
+
export function diffSignatures(before, after,
|
|
158
|
+
/** A batch's combined diff spans several actions, so it raises this. */
|
|
159
|
+
lineBudget = LIST_LINE_BUDGET) {
|
|
160
|
+
const parts = [];
|
|
161
|
+
if (before.url !== after.url) {
|
|
162
|
+
parts.push(`url → ${after.url}` + (before.title !== after.title ? ` — ${JSON.stringify(after.title)}` : ''));
|
|
163
|
+
}
|
|
164
|
+
for (const alert of surplus(after.alerts, before.alerts)) {
|
|
165
|
+
parts.push(`alert: ${JSON.stringify(clip(alert, MAX_ALERT_CHARS))}`);
|
|
166
|
+
}
|
|
167
|
+
const added = surplus(after.lines, before.lines);
|
|
168
|
+
const removed = surplus(before.lines, after.lines);
|
|
169
|
+
const changed = added.length + removed.length;
|
|
170
|
+
if (changed > lineBudget) {
|
|
171
|
+
parts.push(`page changed substantially (~${changed} lines differ) — re-snapshot to see the new state`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
for (const line of added)
|
|
175
|
+
parts.push(`+ ${clip(line, MAX_LINE_CHARS)}`);
|
|
176
|
+
for (const line of removed)
|
|
177
|
+
parts.push(`- ${clip(line, MAX_LINE_CHARS)}`);
|
|
178
|
+
}
|
|
179
|
+
if (!parts.length)
|
|
180
|
+
return 'no visible change';
|
|
181
|
+
return truncate(parts.join('; '), DIFF_BUDGET);
|
|
182
|
+
}
|
|
183
|
+
/** Items of `a` not matched one-for-one by an occurrence in `b`. */
|
|
184
|
+
function surplus(a, b) {
|
|
185
|
+
const counts = new Map();
|
|
186
|
+
for (const item of b)
|
|
187
|
+
counts.set(item, (counts.get(item) ?? 0) + 1);
|
|
188
|
+
const out = [];
|
|
189
|
+
for (const item of a) {
|
|
190
|
+
const left = counts.get(item) ?? 0;
|
|
191
|
+
if (left > 0)
|
|
192
|
+
counts.set(item, left - 1);
|
|
193
|
+
else
|
|
194
|
+
out.push(item);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/daemon/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural page fingerprint: a fixed-size hashed bag of normalised DOM paths
|
|
3
|
+
* (tags, roles, stable classes — no text, ids, or generated hashes). Two pages
|
|
4
|
+
* built from the same template land close under cosine similarity; pages that
|
|
5
|
+
* need a different procedure land far apart. Stage 1 only records it and logs
|
|
6
|
+
* the similarity at replay, so thresholds can later be set from real outcomes
|
|
7
|
+
* instead of guessed.
|
|
8
|
+
*/
|
|
9
|
+
export const FINGERPRINT_DIMS = 512;
|
|
10
|
+
const CAPTURE_TIMEOUT_MS = 2_000;
|
|
11
|
+
/** L2-normalised vector of FINGERPRINT_DIMS, or null if the page could not be read in time. */
|
|
12
|
+
export async function fingerprintPage(page) {
|
|
13
|
+
let timer;
|
|
14
|
+
try {
|
|
15
|
+
const counts = await Promise.race([
|
|
16
|
+
page.evaluate(pathsInPage, { maxNodes: 3_000, dims: FINGERPRINT_DIMS }),
|
|
17
|
+
new Promise((resolve) => {
|
|
18
|
+
timer = setTimeout(() => resolve(null), CAPTURE_TIMEOUT_MS);
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
if (!counts)
|
|
22
|
+
return null;
|
|
23
|
+
return normalise(counts);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function cosine(a, b) {
|
|
33
|
+
if (!a || !b || a.length !== b.length || !a.length)
|
|
34
|
+
return null;
|
|
35
|
+
let dot = 0;
|
|
36
|
+
for (let i = 0; i < a.length; i++)
|
|
37
|
+
dot += a[i] * b[i];
|
|
38
|
+
return Math.round(dot * 1000) / 1000;
|
|
39
|
+
}
|
|
40
|
+
function normalise(counts) {
|
|
41
|
+
let norm = 0;
|
|
42
|
+
for (const c of counts)
|
|
43
|
+
norm += c * c;
|
|
44
|
+
norm = Math.sqrt(norm) || 1;
|
|
45
|
+
return counts.map((c) => Math.round((c / norm) * 1000) / 1000);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Runs in the page. Emits, for every element, the path of
|
|
49
|
+
* `tag[.stableClass][@role]` segments from the nearest landmark down, hashed
|
|
50
|
+
* (FNV-1a) into `dims` buckets. Everything volatile is dropped: text, ids,
|
|
51
|
+
* classes that look generated, inline styles, scripts.
|
|
52
|
+
*/
|
|
53
|
+
function pathsInPage(opts) {
|
|
54
|
+
const counts = new Array(opts.dims).fill(0);
|
|
55
|
+
const fnv = (s) => {
|
|
56
|
+
let h = 0x811c9dc5;
|
|
57
|
+
for (let i = 0; i < s.length; i++) {
|
|
58
|
+
h ^= s.charCodeAt(i);
|
|
59
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
60
|
+
}
|
|
61
|
+
return h;
|
|
62
|
+
};
|
|
63
|
+
const stableClass = (c) => c.length >= 3 && c.length <= 32 && !/\d{3,}|[0-9a-f]{6,}|^(css|sc|jss|emotion)-/i.test(c) && !/^_/.test(c);
|
|
64
|
+
const segment = (el) => {
|
|
65
|
+
let s = el.tagName.toLowerCase();
|
|
66
|
+
const role = el.getAttribute('role');
|
|
67
|
+
if (role)
|
|
68
|
+
s += '@' + role.split(' ')[0];
|
|
69
|
+
const type = el.getAttribute('type');
|
|
70
|
+
if (type && s.startsWith('input'))
|
|
71
|
+
s += ':' + type.toLowerCase();
|
|
72
|
+
const classes = Array.from(el.classList).filter(stableClass).sort().slice(0, 2);
|
|
73
|
+
if (classes.length)
|
|
74
|
+
s += '.' + classes.join('.');
|
|
75
|
+
return s;
|
|
76
|
+
};
|
|
77
|
+
const skip = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE', 'SVG', 'PATH', 'BR']);
|
|
78
|
+
const segs = new Map();
|
|
79
|
+
const all = Array.from(document.querySelectorAll('body *')).slice(0, opts.maxNodes);
|
|
80
|
+
for (const el of all) {
|
|
81
|
+
if (skip.has(el.tagName) || el.closest('svg'))
|
|
82
|
+
continue;
|
|
83
|
+
const own = segment(el);
|
|
84
|
+
const parent = el.parentElement;
|
|
85
|
+
const parentPath = parent && segs.has(parent) ? segs.get(parent) : parent ? segment(parent) : '';
|
|
86
|
+
// Keep at most four ancestors: deeper paths are all noise from the same
|
|
87
|
+
// widget, shallower ones lose the region the element sits in.
|
|
88
|
+
const pathParts = parentPath ? parentPath.split('/').slice(-3) : [];
|
|
89
|
+
pathParts.push(own);
|
|
90
|
+
const p = pathParts.join('/');
|
|
91
|
+
segs.set(el, p);
|
|
92
|
+
counts[fnv(p) % opts.dims] += 1;
|
|
93
|
+
// Parent-child bigram and the bare segment add robustness to reordering.
|
|
94
|
+
counts[fnv('seg:' + own) % opts.dims] += 0.5;
|
|
95
|
+
}
|
|
96
|
+
return counts;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/daemon/fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-safe input helpers (friction #5). Plain `locator.fill()` bypasses the
|
|
3
|
+
* native value setter, so React controlled components never see the change
|
|
4
|
+
* (or number inputs end up appending). We set the value through the native
|
|
5
|
+
* prototype setter and dispatch input/change so React's synthetic event
|
|
6
|
+
* system picks it up. Invisible to the agent: `fill` just works.
|
|
7
|
+
*/
|
|
8
|
+
export async function reactSafeFill(locator, value) {
|
|
9
|
+
await locator.waitFor({ state: 'visible', timeout: 10_000 });
|
|
10
|
+
await locator.scrollIntoViewIfNeeded().catch(() => { });
|
|
11
|
+
await locator.click({ timeout: 5_000 }).catch(() => { }); // focus; some widgets need it
|
|
12
|
+
const handled = await locator.evaluate((el, val) => {
|
|
13
|
+
const input = el;
|
|
14
|
+
if (!('value' in input))
|
|
15
|
+
return false;
|
|
16
|
+
const proto = input instanceof HTMLTextAreaElement
|
|
17
|
+
? HTMLTextAreaElement.prototype
|
|
18
|
+
: input instanceof HTMLInputElement
|
|
19
|
+
? HTMLInputElement.prototype
|
|
20
|
+
: null;
|
|
21
|
+
if (!proto)
|
|
22
|
+
return false;
|
|
23
|
+
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
24
|
+
if (!setter)
|
|
25
|
+
return false;
|
|
26
|
+
setter.call(input, ''); // clear-then-set: number inputs otherwise append
|
|
27
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
28
|
+
setter.call(input, val);
|
|
29
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
30
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
31
|
+
return true;
|
|
32
|
+
}, value);
|
|
33
|
+
if (!handled) {
|
|
34
|
+
// contenteditable or non-standard widget — fall back to Playwright fill
|
|
35
|
+
await locator.fill(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Select an option and fire change through the native setter, for React
|
|
40
|
+
* controlled <select> elements.
|
|
41
|
+
*/
|
|
42
|
+
export async function reactSafeSelect(locator, value, fallbackValue) {
|
|
43
|
+
// Look before waiting: an option present NOW is chosen at once, and the
|
|
44
|
+
// recorded fallback value is tried without first sitting out the label's
|
|
45
|
+
// full timeout. Only when nothing matches yet does the label wait for
|
|
46
|
+
// options that may still be loading.
|
|
47
|
+
const present = await locator
|
|
48
|
+
.evaluate((el, [v, f]) => {
|
|
49
|
+
if (!(el instanceof HTMLSelectElement))
|
|
50
|
+
return null;
|
|
51
|
+
const opts = Array.from(el.options);
|
|
52
|
+
if (opts.some((o) => o.label.trim() === v))
|
|
53
|
+
return 'label';
|
|
54
|
+
if (opts.some((o) => o.value === v))
|
|
55
|
+
return 'value';
|
|
56
|
+
if (f && opts.some((o) => o.value === f))
|
|
57
|
+
return 'fallback';
|
|
58
|
+
return null;
|
|
59
|
+
}, [value, fallbackValue ?? ''])
|
|
60
|
+
.catch(() => null);
|
|
61
|
+
if (present === 'fallback')
|
|
62
|
+
return locator.selectOption(fallbackValue);
|
|
63
|
+
if (present === 'value')
|
|
64
|
+
return locator.selectOption(value);
|
|
65
|
+
const result = await locator.selectOption({ label: value }).catch(() => null);
|
|
66
|
+
if (result)
|
|
67
|
+
return result;
|
|
68
|
+
return locator.selectOption(value); // fall back to value/index matching
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The option a <select> currently shows: its visible label and its value.
|
|
72
|
+
* Null for anything that is not a single-choice select with a selection.
|
|
73
|
+
*/
|
|
74
|
+
export async function selectedOption(locator) {
|
|
75
|
+
return locator
|
|
76
|
+
.evaluate((el) => {
|
|
77
|
+
if (!(el instanceof HTMLSelectElement) || el.multiple)
|
|
78
|
+
return null;
|
|
79
|
+
const opt = el.selectedOptions[0];
|
|
80
|
+
return opt ? { label: opt.label.trim(), value: opt.value } : null;
|
|
81
|
+
})
|
|
82
|
+
.catch(() => null);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Hover with synthetic mouse events for autocomplete/listbox widgets that key
|
|
86
|
+
* off mouseenter rather than CSS :hover.
|
|
87
|
+
*/
|
|
88
|
+
export async function syntheticHover(locator) {
|
|
89
|
+
await locator.hover().catch(() => { });
|
|
90
|
+
await locator.evaluate((el) => {
|
|
91
|
+
for (const type of ['pointerover', 'mouseover', 'mouseenter', 'mousemove']) {
|
|
92
|
+
el.dispatchEvent(new MouseEvent(type, { bubbles: type !== 'mouseenter' }));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* HTML5 drag-and-drop fallback: dispatch a full dragstart/dragover/drop
|
|
98
|
+
* sequence with a shared DataTransfer, for libraries that implement DnD on
|
|
99
|
+
* the HTML5 events with custom payloads (where Playwright's mouse-based
|
|
100
|
+
* dragTo doesn't trigger the drop handler).
|
|
101
|
+
*/
|
|
102
|
+
export async function html5DragDrop(source, target) {
|
|
103
|
+
const page = source.page();
|
|
104
|
+
const sourceHandle = await source.elementHandle();
|
|
105
|
+
const targetHandle = await target.elementHandle();
|
|
106
|
+
try {
|
|
107
|
+
if (!sourceHandle || !targetHandle)
|
|
108
|
+
throw new Error('drag source or target not found');
|
|
109
|
+
await page.evaluate(([src, dst]) => {
|
|
110
|
+
const dataTransfer = new DataTransfer();
|
|
111
|
+
const fire = (el, type) => {
|
|
112
|
+
const rect = el.getBoundingClientRect();
|
|
113
|
+
el.dispatchEvent(new DragEvent(type, {
|
|
114
|
+
bubbles: true,
|
|
115
|
+
cancelable: true,
|
|
116
|
+
composed: true,
|
|
117
|
+
clientX: rect.left + rect.width / 2,
|
|
118
|
+
clientY: rect.top + rect.height / 2,
|
|
119
|
+
dataTransfer,
|
|
120
|
+
}));
|
|
121
|
+
};
|
|
122
|
+
fire(src, 'dragstart');
|
|
123
|
+
fire(dst, 'dragenter');
|
|
124
|
+
fire(dst, 'dragover');
|
|
125
|
+
fire(dst, 'drop');
|
|
126
|
+
fire(src, 'dragend');
|
|
127
|
+
}, [sourceHandle, targetHandle]);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await sourceHandle?.dispose().catch(() => { });
|
|
131
|
+
await targetHandle?.dispose().catch(() => { });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=inputs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputs.js","sourceRoot":"","sources":["../../src/daemon/inputs.ts"],"names":[],"mappings":"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"}
|