opensteer 0.6.0 → 0.6.2
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/CHANGELOG.md +16 -1
- package/README.md +63 -0
- package/bin/opensteer.mjs +214 -3
- package/dist/browser-profile-client-CaL-mwqs.d.ts +168 -0
- package/dist/browser-profile-client-DK9qa_Dj.d.cts +168 -0
- package/dist/chunk-7RMY26CM.js +1130 -0
- package/dist/{chunk-SGZYTGY3.js → chunk-F2VDVOJO.js} +1890 -1520
- package/dist/chunk-WDRMHPWL.js +1320 -0
- package/dist/chunk-WJI7TGBQ.js +77 -0
- package/dist/cli/auth.cjs +1834 -0
- package/dist/cli/auth.d.cts +114 -0
- package/dist/cli/auth.d.ts +114 -0
- package/dist/cli/auth.js +15 -0
- package/dist/cli/profile.cjs +16222 -0
- package/dist/cli/profile.d.cts +79 -0
- package/dist/cli/profile.d.ts +79 -0
- package/dist/cli/profile.js +866 -0
- package/dist/cli/server.cjs +1816 -422
- package/dist/cli/server.js +310 -10
- package/dist/index.cjs +1772 -414
- package/dist/index.d.cts +141 -395
- package/dist/index.d.ts +141 -395
- package/dist/index.js +203 -8
- package/dist/types-BxiRblC7.d.cts +327 -0
- package/dist/types-BxiRblC7.d.ts +327 -0
- package/package.json +3 -2
- package/skills/opensteer/SKILL.md +34 -5
- package/skills/opensteer/references/cli-reference.md +10 -8
- package/skills/opensteer/references/sdk-reference.md +14 -3
|
@@ -1,263 +1,515 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpensteerCloudError,
|
|
3
|
+
cloudNotLaunchedError,
|
|
4
|
+
cloudUnsupportedMethodError,
|
|
5
|
+
createCloudRuntimeState,
|
|
6
|
+
createKeychainStore,
|
|
7
|
+
extractErrorMessage,
|
|
8
|
+
normalizeError,
|
|
9
|
+
normalizeNamespace,
|
|
10
|
+
readCloudActionDescription,
|
|
11
|
+
resolveCloudSelection,
|
|
12
|
+
resolveConfigWithEnv,
|
|
13
|
+
resolveNamespace,
|
|
14
|
+
resolveNamespaceDir
|
|
15
|
+
} from "./chunk-WDRMHPWL.js";
|
|
1
16
|
import {
|
|
2
17
|
flattenExtractionDataToFieldPlan
|
|
3
18
|
} from "./chunk-3H5RRIMZ.js";
|
|
4
19
|
|
|
5
|
-
// src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (name) return name;
|
|
20
|
+
// src/browser/chrome.ts
|
|
21
|
+
import { homedir, platform } from "os";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
function expandHome(p) {
|
|
24
|
+
if (p.startsWith("~/") || p === "~") {
|
|
25
|
+
return join(homedir(), p.slice(1));
|
|
12
26
|
}
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/browser/chromium-profile.ts
|
|
31
|
+
import { promisify } from "util";
|
|
32
|
+
import { execFile } from "child_process";
|
|
33
|
+
import { createDecipheriv, createHash, pbkdf2Sync } from "crypto";
|
|
34
|
+
import {
|
|
35
|
+
cp,
|
|
36
|
+
copyFile,
|
|
37
|
+
mkdtemp,
|
|
38
|
+
readdir,
|
|
39
|
+
readFile,
|
|
40
|
+
rm
|
|
41
|
+
} from "fs/promises";
|
|
42
|
+
import { existsSync, statSync } from "fs";
|
|
43
|
+
import { basename, dirname, join as join2 } from "path";
|
|
44
|
+
import { tmpdir } from "os";
|
|
45
|
+
import { chromium } from "playwright";
|
|
46
|
+
var execFileAsync = promisify(execFile);
|
|
47
|
+
var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
|
|
48
|
+
var AES_BLOCK_BYTES = 16;
|
|
49
|
+
var MAC_KEY_ITERATIONS = 1003;
|
|
50
|
+
var LINUX_KEY_ITERATIONS = 1;
|
|
51
|
+
var KEY_LENGTH = 16;
|
|
52
|
+
var KEY_SALT = "saltysalt";
|
|
53
|
+
var DEFAULT_CHROMIUM_BRAND = {
|
|
54
|
+
macService: "Chrome Safe Storage",
|
|
55
|
+
macAccount: "Chrome",
|
|
56
|
+
linuxApplications: ["chrome", "google-chrome"]
|
|
57
|
+
};
|
|
58
|
+
var CHROMIUM_BRANDS = [
|
|
59
|
+
{
|
|
60
|
+
match: ["bravesoftware", "brave-browser"],
|
|
61
|
+
brand: {
|
|
62
|
+
macService: "Brave Safe Storage",
|
|
63
|
+
macAccount: "Brave",
|
|
64
|
+
linuxApplications: ["brave-browser", "brave"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
match: ["microsoft", "edge"],
|
|
69
|
+
brand: {
|
|
70
|
+
macService: "Microsoft Edge Safe Storage",
|
|
71
|
+
macAccount: "Microsoft Edge",
|
|
72
|
+
linuxApplications: ["microsoft-edge"],
|
|
73
|
+
playwrightChannel: "msedge"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
match: ["google", "chrome beta"],
|
|
78
|
+
brand: {
|
|
79
|
+
macService: "Chrome Beta Safe Storage",
|
|
80
|
+
macAccount: "Chrome Beta",
|
|
81
|
+
linuxApplications: ["chrome-beta"],
|
|
82
|
+
playwrightChannel: "chrome-beta"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
match: ["google", "chrome"],
|
|
87
|
+
brand: {
|
|
88
|
+
macService: "Chrome Safe Storage",
|
|
89
|
+
macAccount: "Chrome",
|
|
90
|
+
linuxApplications: ["chrome", "google-chrome"],
|
|
91
|
+
playwrightChannel: "chrome"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
match: ["chromium"],
|
|
96
|
+
brand: {
|
|
97
|
+
macService: "Chromium Safe Storage",
|
|
98
|
+
macAccount: "Chromium",
|
|
99
|
+
linuxApplications: ["chromium"]
|
|
100
|
+
}
|
|
15
101
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
102
|
+
];
|
|
103
|
+
function directoryExists(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
return statSync(filePath).isDirectory();
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
20
108
|
}
|
|
21
|
-
return fallback;
|
|
22
109
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
110
|
+
function fileExists(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
return statSync(filePath).isFile();
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
26
116
|
}
|
|
27
|
-
function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
message: extractErrorMessage(error, fallback)
|
|
33
|
-
};
|
|
117
|
+
function resolveCookieDbPath(profileDir) {
|
|
118
|
+
const candidates = [join2(profileDir, "Network", "Cookies"), join2(profileDir, "Cookies")];
|
|
119
|
+
for (const candidate of candidates) {
|
|
120
|
+
if (fileExists(candidate)) {
|
|
121
|
+
return candidate;
|
|
34
122
|
}
|
|
35
|
-
seen.add(record);
|
|
36
|
-
}
|
|
37
|
-
const message = extractErrorMessage(error, fallback);
|
|
38
|
-
const code = extractCode(error);
|
|
39
|
-
const name = extractName(error);
|
|
40
|
-
const details = extractDetails(error);
|
|
41
|
-
if (depthRemaining <= 0) {
|
|
42
|
-
return compactErrorInfo({
|
|
43
|
-
message,
|
|
44
|
-
...code ? { code } : {},
|
|
45
|
-
...name ? { name } : {},
|
|
46
|
-
...details ? { details } : {}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
const cause = extractCause(error);
|
|
50
|
-
if (!cause) {
|
|
51
|
-
return compactErrorInfo({
|
|
52
|
-
message,
|
|
53
|
-
...code ? { code } : {},
|
|
54
|
-
...name ? { name } : {},
|
|
55
|
-
...details ? { details } : {}
|
|
56
|
-
});
|
|
57
123
|
}
|
|
58
|
-
|
|
59
|
-
cause,
|
|
60
|
-
"Caused by an unknown error.",
|
|
61
|
-
depthRemaining - 1,
|
|
62
|
-
seen
|
|
63
|
-
);
|
|
64
|
-
return compactErrorInfo({
|
|
65
|
-
message,
|
|
66
|
-
...code ? { code } : {},
|
|
67
|
-
...name ? { name } : {},
|
|
68
|
-
...details ? { details } : {},
|
|
69
|
-
cause: normalizedCause
|
|
70
|
-
});
|
|
124
|
+
return null;
|
|
71
125
|
}
|
|
72
|
-
function
|
|
73
|
-
const
|
|
126
|
+
async function selectProfileDirFromUserDataDir(userDataDir) {
|
|
127
|
+
const entries = await readdir(userDataDir, {
|
|
128
|
+
withFileTypes: true
|
|
129
|
+
}).catch(() => []);
|
|
130
|
+
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => join2(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
|
|
131
|
+
return candidates;
|
|
132
|
+
}
|
|
133
|
+
async function resolveChromiumProfileLocation(inputPath) {
|
|
134
|
+
const expandedPath = expandHome(inputPath.trim());
|
|
135
|
+
if (!expandedPath) {
|
|
136
|
+
throw new Error("Profile path cannot be empty.");
|
|
137
|
+
}
|
|
138
|
+
if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
|
|
139
|
+
const directParent = dirname(expandedPath);
|
|
140
|
+
const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
|
|
141
|
+
const userDataDir = dirname(profileDir);
|
|
142
|
+
return {
|
|
143
|
+
userDataDir,
|
|
144
|
+
profileDir,
|
|
145
|
+
profileDirectory: basename(profileDir),
|
|
146
|
+
cookieDbPath: expandedPath,
|
|
147
|
+
localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (fileExists(expandedPath)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (!directoryExists(expandedPath)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Could not find a Chromium profile at "${inputPath}".`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const directCookieDb = resolveCookieDbPath(expandedPath);
|
|
161
|
+
if (directCookieDb) {
|
|
162
|
+
const userDataDir = dirname(expandedPath);
|
|
163
|
+
return {
|
|
164
|
+
userDataDir,
|
|
165
|
+
profileDir: expandedPath,
|
|
166
|
+
profileDirectory: basename(expandedPath),
|
|
167
|
+
cookieDbPath: directCookieDb,
|
|
168
|
+
localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const localStatePath = join2(expandedPath, "Local State");
|
|
172
|
+
if (!fileExists(localStatePath)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const profileDirs = await selectProfileDirFromUserDataDir(expandedPath);
|
|
178
|
+
if (profileDirs.length === 0) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`No Chromium profile with a Cookies database was found under "${inputPath}".`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (profileDirs.length > 1) {
|
|
184
|
+
const candidates = profileDirs.map((entry) => basename(entry)).join(", ");
|
|
185
|
+
throw new Error(
|
|
186
|
+
`"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const selectedProfileDir = profileDirs[0];
|
|
190
|
+
const cookieDbPath = resolveCookieDbPath(selectedProfileDir);
|
|
191
|
+
if (!cookieDbPath) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`No Chromium Cookies database was found for "${inputPath}".`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
74
196
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
197
|
+
userDataDir: expandedPath,
|
|
198
|
+
profileDir: selectedProfileDir,
|
|
199
|
+
profileDirectory: basename(selectedProfileDir),
|
|
200
|
+
cookieDbPath,
|
|
201
|
+
localStatePath
|
|
80
202
|
};
|
|
81
203
|
}
|
|
82
|
-
function
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
204
|
+
function resolvePersistentChromiumLaunchProfile(inputPath) {
|
|
205
|
+
const expandedPath = expandHome(inputPath.trim());
|
|
206
|
+
if (!expandedPath) {
|
|
207
|
+
return {
|
|
208
|
+
userDataDir: inputPath
|
|
209
|
+
};
|
|
87
210
|
}
|
|
88
|
-
if (
|
|
89
|
-
|
|
211
|
+
if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
|
|
212
|
+
const directParent = dirname(expandedPath);
|
|
213
|
+
const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
|
|
214
|
+
return {
|
|
215
|
+
userDataDir: dirname(profileDir),
|
|
216
|
+
profileDirectory: basename(profileDir)
|
|
217
|
+
};
|
|
90
218
|
}
|
|
91
|
-
|
|
219
|
+
if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists(join2(dirname(expandedPath), "Local State"))) {
|
|
220
|
+
return {
|
|
221
|
+
userDataDir: dirname(expandedPath),
|
|
222
|
+
profileDirectory: basename(expandedPath)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
userDataDir: expandedPath
|
|
227
|
+
};
|
|
92
228
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return toNonEmptyString(record?.name);
|
|
99
|
-
}
|
|
100
|
-
function extractDetails(error) {
|
|
101
|
-
const record = asRecord(error);
|
|
102
|
-
if (!record) return void 0;
|
|
103
|
-
const details = {};
|
|
104
|
-
const rawDetails = asRecord(record.details);
|
|
105
|
-
if (rawDetails) {
|
|
106
|
-
Object.assign(details, rawDetails);
|
|
107
|
-
}
|
|
108
|
-
const action = toNonEmptyString(record.action);
|
|
109
|
-
if (action) {
|
|
110
|
-
details.action = action;
|
|
111
|
-
}
|
|
112
|
-
const selectorUsed = toNonEmptyString(record.selectorUsed);
|
|
113
|
-
if (selectorUsed) {
|
|
114
|
-
details.selectorUsed = selectorUsed;
|
|
115
|
-
}
|
|
116
|
-
if (typeof record.status === "number" && Number.isFinite(record.status)) {
|
|
117
|
-
details.status = record.status;
|
|
118
|
-
}
|
|
119
|
-
const failure = asRecord(record.failure);
|
|
120
|
-
if (failure) {
|
|
121
|
-
const failureCode = toNonEmptyString(failure.code);
|
|
122
|
-
const classificationSource = toNonEmptyString(
|
|
123
|
-
failure.classificationSource
|
|
124
|
-
);
|
|
125
|
-
const failureDetails = asRecord(failure.details);
|
|
126
|
-
if (failureCode || classificationSource || failureDetails) {
|
|
127
|
-
details.actionFailure = {
|
|
128
|
-
...failureCode ? { code: failureCode } : {},
|
|
129
|
-
...classificationSource ? { classificationSource } : {},
|
|
130
|
-
...failureDetails ? { details: failureDetails } : {}
|
|
131
|
-
};
|
|
229
|
+
function detectChromiumBrand(location) {
|
|
230
|
+
const normalizedPath = location.userDataDir.toLowerCase();
|
|
231
|
+
for (const candidate of CHROMIUM_BRANDS) {
|
|
232
|
+
if (candidate.match.every((fragment) => normalizedPath.includes(fragment))) {
|
|
233
|
+
return candidate.brand;
|
|
132
234
|
}
|
|
133
235
|
}
|
|
134
|
-
return
|
|
236
|
+
return DEFAULT_CHROMIUM_BRAND;
|
|
135
237
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
238
|
+
async function createSqliteSnapshot(dbPath) {
|
|
239
|
+
const snapshotDir = await mkdtemp(join2(tmpdir(), "opensteer-cookie-db-"));
|
|
240
|
+
const snapshotPath = join2(snapshotDir, "Cookies");
|
|
241
|
+
await copyFile(dbPath, snapshotPath);
|
|
242
|
+
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
243
|
+
const source = `${dbPath}${suffix}`;
|
|
244
|
+
if (!existsSync(source)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
await copyFile(source, `${snapshotPath}${suffix}`);
|
|
139
248
|
}
|
|
140
|
-
|
|
141
|
-
|
|
249
|
+
return {
|
|
250
|
+
snapshotPath,
|
|
251
|
+
cleanup: async () => {
|
|
252
|
+
await rm(snapshotDir, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
};
|
|
142
255
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
256
|
+
async function querySqliteJson(dbPath, query) {
|
|
257
|
+
const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
|
|
258
|
+
encoding: "utf8",
|
|
259
|
+
maxBuffer: 64 * 1024 * 1024
|
|
260
|
+
});
|
|
261
|
+
const stdout = result.stdout;
|
|
262
|
+
const trimmed = stdout.trim();
|
|
263
|
+
if (!trimmed) {
|
|
264
|
+
return [];
|
|
146
265
|
}
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
function toNonEmptyString(value) {
|
|
150
|
-
if (typeof value !== "string") return void 0;
|
|
151
|
-
const normalized = value.trim();
|
|
152
|
-
return normalized.length ? normalized : void 0;
|
|
266
|
+
return JSON.parse(trimmed);
|
|
153
267
|
}
|
|
154
|
-
function
|
|
155
|
-
if (!value
|
|
156
|
-
|
|
157
|
-
if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
|
|
158
|
-
return void 0;
|
|
268
|
+
function convertChromiumTimestampToUnixSeconds(value) {
|
|
269
|
+
if (!value || value === "0") {
|
|
270
|
+
return -1;
|
|
159
271
|
}
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
function toJsonSafeValue(value, seen) {
|
|
164
|
-
if (value === null) return null;
|
|
165
|
-
if (typeof value === "string" || typeof value === "boolean") {
|
|
166
|
-
return value;
|
|
272
|
+
const micros = BigInt(value);
|
|
273
|
+
if (micros <= CHROMIUM_EPOCH_MICROS) {
|
|
274
|
+
return -1;
|
|
167
275
|
}
|
|
168
|
-
|
|
169
|
-
|
|
276
|
+
return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
|
|
277
|
+
}
|
|
278
|
+
function mapChromiumSameSite(value) {
|
|
279
|
+
if (value === 2) {
|
|
280
|
+
return "Strict";
|
|
170
281
|
}
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
282
|
+
if (value === 0) {
|
|
283
|
+
return "None";
|
|
173
284
|
}
|
|
174
|
-
|
|
175
|
-
|
|
285
|
+
return "Lax";
|
|
286
|
+
}
|
|
287
|
+
function stripChromiumPadding(buffer) {
|
|
288
|
+
const paddingLength = buffer[buffer.length - 1];
|
|
289
|
+
if (paddingLength <= 0 || paddingLength > AES_BLOCK_BYTES) {
|
|
290
|
+
return buffer;
|
|
176
291
|
}
|
|
177
|
-
|
|
178
|
-
|
|
292
|
+
return buffer.subarray(0, buffer.length - paddingLength);
|
|
293
|
+
}
|
|
294
|
+
function stripDomainHashPrefix(buffer, hostKey) {
|
|
295
|
+
if (buffer.length < 32) {
|
|
296
|
+
return buffer;
|
|
179
297
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const output = value.map((item) => {
|
|
184
|
-
const next = toJsonSafeValue(item, seen);
|
|
185
|
-
return next === void 0 ? null : next;
|
|
186
|
-
});
|
|
187
|
-
seen.delete(value);
|
|
188
|
-
return output;
|
|
189
|
-
}
|
|
190
|
-
if (value instanceof Set) {
|
|
191
|
-
if (seen.has(value)) return "[Circular]";
|
|
192
|
-
seen.add(value);
|
|
193
|
-
const output = Array.from(value, (item) => {
|
|
194
|
-
const next = toJsonSafeValue(item, seen);
|
|
195
|
-
return next === void 0 ? null : next;
|
|
196
|
-
});
|
|
197
|
-
seen.delete(value);
|
|
198
|
-
return output;
|
|
298
|
+
const domainHash = createHash("sha256").update(hostKey, "utf8").digest();
|
|
299
|
+
if (buffer.subarray(0, 32).equals(domainHash)) {
|
|
300
|
+
return buffer.subarray(32);
|
|
199
301
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
302
|
+
return buffer;
|
|
303
|
+
}
|
|
304
|
+
function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
|
|
305
|
+
const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
|
|
306
|
+
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
307
|
+
const decipher = createDecipheriv("aes-128-cbc", key, iv);
|
|
308
|
+
const plaintext = Buffer.concat([
|
|
309
|
+
decipher.update(ciphertext),
|
|
310
|
+
decipher.final()
|
|
311
|
+
]);
|
|
312
|
+
return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
|
|
313
|
+
"utf8"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function decryptChromiumAes256GcmValue(encryptedValue, key) {
|
|
317
|
+
const nonce = encryptedValue.subarray(3, 15);
|
|
318
|
+
const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
|
|
319
|
+
const authTag = encryptedValue.subarray(encryptedValue.length - 16);
|
|
320
|
+
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
|
321
|
+
decipher.setAuthTag(authTag);
|
|
322
|
+
return Buffer.concat([
|
|
323
|
+
decipher.update(ciphertext),
|
|
324
|
+
decipher.final()
|
|
325
|
+
]).toString("utf8");
|
|
326
|
+
}
|
|
327
|
+
async function dpapiUnprotect(buffer) {
|
|
328
|
+
const script = [
|
|
329
|
+
`$inputBytes = [Convert]::FromBase64String('${buffer.toString("base64")}')`,
|
|
330
|
+
"$plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(",
|
|
331
|
+
" $inputBytes,",
|
|
332
|
+
" $null,",
|
|
333
|
+
" [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
|
|
334
|
+
")",
|
|
335
|
+
"[Convert]::ToBase64String($plainBytes)"
|
|
336
|
+
].join("\n");
|
|
337
|
+
const { stdout } = await execFileAsync(
|
|
338
|
+
"powershell.exe",
|
|
339
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
340
|
+
{
|
|
341
|
+
encoding: "utf8",
|
|
342
|
+
maxBuffer: 8 * 1024 * 1024
|
|
210
343
|
}
|
|
211
|
-
|
|
212
|
-
|
|
344
|
+
);
|
|
345
|
+
return Buffer.from(stdout.trim(), "base64");
|
|
346
|
+
}
|
|
347
|
+
async function buildChromiumDecryptor(location) {
|
|
348
|
+
if (process.platform === "darwin") {
|
|
349
|
+
const brand = detectChromiumBrand(location);
|
|
350
|
+
const keychainStore = createKeychainStore();
|
|
351
|
+
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? null;
|
|
352
|
+
if (!password) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const key = pbkdf2Sync(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
|
|
358
|
+
return async (row) => decryptChromiumAes128CbcValue(
|
|
359
|
+
Buffer.from(row.encrypted_value || "", "hex"),
|
|
360
|
+
key,
|
|
361
|
+
row.host_key
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (process.platform === "linux") {
|
|
365
|
+
const brand = detectChromiumBrand(location);
|
|
366
|
+
const keychainStore = createKeychainStore();
|
|
367
|
+
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
|
|
368
|
+
const key = pbkdf2Sync(
|
|
369
|
+
password || "peanuts",
|
|
370
|
+
KEY_SALT,
|
|
371
|
+
LINUX_KEY_ITERATIONS,
|
|
372
|
+
KEY_LENGTH,
|
|
373
|
+
"sha1"
|
|
374
|
+
);
|
|
375
|
+
return async (row) => decryptChromiumAes128CbcValue(
|
|
376
|
+
Buffer.from(row.encrypted_value || "", "hex"),
|
|
377
|
+
key,
|
|
378
|
+
row.host_key
|
|
379
|
+
);
|
|
213
380
|
}
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
381
|
+
if (process.platform === "win32") {
|
|
382
|
+
if (!location.localStatePath) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Unable to locate Chromium Local State for profile: ${location.profileDir}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
const localState = JSON.parse(
|
|
388
|
+
await readFile(location.localStatePath, "utf8")
|
|
389
|
+
);
|
|
390
|
+
const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
|
|
391
|
+
if (!encryptedKeyBase64) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Local State did not include os_crypt.encrypted_key for ${location.userDataDir}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
|
|
397
|
+
const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
|
|
398
|
+
return async (row) => {
|
|
399
|
+
const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
|
|
400
|
+
if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
|
|
401
|
+
const decrypted = await dpapiUnprotect(encryptedValue);
|
|
402
|
+
return decrypted.toString("utf8");
|
|
223
403
|
}
|
|
404
|
+
return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
throw new Error(
|
|
408
|
+
`Local Chromium cookie sync is not supported on ${process.platform}.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
function buildPlaywrightCookie(row, value) {
|
|
412
|
+
if (!row.name.trim()) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
if (!row.host_key.trim()) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
|
|
419
|
+
if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
name: row.name,
|
|
424
|
+
value,
|
|
425
|
+
domain: row.host_key,
|
|
426
|
+
path: row.path || "/",
|
|
427
|
+
expires,
|
|
428
|
+
httpOnly: row.is_httponly === 1,
|
|
429
|
+
secure: row.is_secure === 1,
|
|
430
|
+
sameSite: mapChromiumSameSite(row.samesite)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
|
|
434
|
+
const location = await resolveChromiumProfileLocation(inputPath);
|
|
435
|
+
try {
|
|
436
|
+
return await loadCookiesFromSqlite(location);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (!isMissingSqliteBinary(error)) {
|
|
439
|
+
throw error;
|
|
224
440
|
}
|
|
225
|
-
seen.delete(objectValue);
|
|
226
|
-
return output;
|
|
227
441
|
}
|
|
228
|
-
return
|
|
442
|
+
return await loadCookiesFromBrowserSnapshot(location, options);
|
|
229
443
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
);
|
|
444
|
+
async function loadCookiesFromSqlite(location) {
|
|
445
|
+
const snapshot = await createSqliteSnapshot(location.cookieDbPath);
|
|
446
|
+
try {
|
|
447
|
+
const rows = await querySqliteJson(
|
|
448
|
+
snapshot.snapshotPath,
|
|
449
|
+
[
|
|
450
|
+
"SELECT",
|
|
451
|
+
" host_key,",
|
|
452
|
+
" name,",
|
|
453
|
+
" value,",
|
|
454
|
+
" hex(encrypted_value) AS encrypted_value,",
|
|
455
|
+
" path,",
|
|
456
|
+
" CAST(expires_utc AS TEXT) AS expires_utc,",
|
|
457
|
+
" is_secure,",
|
|
458
|
+
" is_httponly,",
|
|
459
|
+
" has_expires,",
|
|
460
|
+
" samesite",
|
|
461
|
+
"FROM cookies"
|
|
462
|
+
].join(" ")
|
|
463
|
+
);
|
|
464
|
+
const decryptValue = await buildChromiumDecryptor(location);
|
|
465
|
+
const cookies = [];
|
|
466
|
+
for (const row of rows) {
|
|
467
|
+
let value = row.value || "";
|
|
468
|
+
if (!value && row.encrypted_value) {
|
|
469
|
+
value = await decryptValue(row);
|
|
470
|
+
}
|
|
471
|
+
const cookie = buildPlaywrightCookie(row, value);
|
|
472
|
+
if (cookie) {
|
|
473
|
+
cookies.push(cookie);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return cookies;
|
|
477
|
+
} finally {
|
|
478
|
+
await snapshot.cleanup();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
482
|
+
const snapshotRootDir = await mkdtemp(join2(tmpdir(), "opensteer-profile-"));
|
|
483
|
+
const snapshotProfileDir = join2(
|
|
484
|
+
snapshotRootDir,
|
|
485
|
+
basename(location.profileDir)
|
|
486
|
+
);
|
|
487
|
+
let context = null;
|
|
488
|
+
try {
|
|
489
|
+
await cp(location.profileDir, snapshotProfileDir, {
|
|
490
|
+
recursive: true
|
|
491
|
+
});
|
|
492
|
+
if (location.localStatePath) {
|
|
493
|
+
await copyFile(location.localStatePath, join2(snapshotRootDir, "Local State"));
|
|
494
|
+
}
|
|
495
|
+
const brand = detectChromiumBrand(location);
|
|
496
|
+
const args = [`--profile-directory=${basename(snapshotProfileDir)}`];
|
|
497
|
+
context = await chromium.launchPersistentContext(snapshotRootDir, {
|
|
498
|
+
channel: brand.playwrightChannel,
|
|
499
|
+
headless: options.headless ?? true,
|
|
500
|
+
timeout: options.timeout ?? 12e4,
|
|
501
|
+
args
|
|
502
|
+
});
|
|
503
|
+
return await context.cookies();
|
|
504
|
+
} finally {
|
|
505
|
+
await context?.close().catch(() => void 0);
|
|
506
|
+
await rm(snapshotRootDir, { recursive: true, force: true });
|
|
251
507
|
}
|
|
252
|
-
return namespaceDir;
|
|
253
508
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const collapsed = replaced.replace(/_+/g, "_");
|
|
259
|
-
const bounded = collapsed.replace(/^_+|_+$/g, "");
|
|
260
|
-
return bounded || "";
|
|
509
|
+
function isMissingSqliteBinary(error) {
|
|
510
|
+
return Boolean(
|
|
511
|
+
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
512
|
+
);
|
|
261
513
|
}
|
|
262
514
|
|
|
263
515
|
// src/navigation.ts
|
|
@@ -829,7 +1081,7 @@ function createEmptyRegistry(name) {
|
|
|
829
1081
|
|
|
830
1082
|
// src/storage/local.ts
|
|
831
1083
|
import fs from "fs";
|
|
832
|
-
import
|
|
1084
|
+
import path from "path";
|
|
833
1085
|
var LocalSelectorStorage = class {
|
|
834
1086
|
rootDir;
|
|
835
1087
|
namespace;
|
|
@@ -850,16 +1102,16 @@ var LocalSelectorStorage = class {
|
|
|
850
1102
|
return `${safe}.json`;
|
|
851
1103
|
}
|
|
852
1104
|
getOpensteerDir() {
|
|
853
|
-
return
|
|
1105
|
+
return path.join(this.rootDir, ".opensteer");
|
|
854
1106
|
}
|
|
855
1107
|
getNamespaceDir() {
|
|
856
1108
|
return resolveNamespaceDir(this.rootDir, this.namespace);
|
|
857
1109
|
}
|
|
858
1110
|
getRegistryPath() {
|
|
859
|
-
return
|
|
1111
|
+
return path.join(this.getNamespaceDir(), "index.json");
|
|
860
1112
|
}
|
|
861
1113
|
getSelectorPath(id) {
|
|
862
|
-
return
|
|
1114
|
+
return path.join(this.getNamespaceDir(), this.getSelectorFileName(id));
|
|
863
1115
|
}
|
|
864
1116
|
ensureDirs() {
|
|
865
1117
|
fs.mkdirSync(this.getNamespaceDir(), { recursive: true });
|
|
@@ -1195,11 +1447,11 @@ var OS_INSTANCE_TOKEN_KEY = "__opensteerInstanceToken";
|
|
|
1195
1447
|
|
|
1196
1448
|
// src/element-path/build.ts
|
|
1197
1449
|
var MAX_ATTRIBUTE_VALUE_LENGTH = 300;
|
|
1198
|
-
function cloneElementPath(
|
|
1199
|
-
return JSON.parse(JSON.stringify(
|
|
1450
|
+
function cloneElementPath(path3) {
|
|
1451
|
+
return JSON.parse(JSON.stringify(path3));
|
|
1200
1452
|
}
|
|
1201
|
-
function buildPathSelectorHint(
|
|
1202
|
-
const nodes =
|
|
1453
|
+
function buildPathSelectorHint(path3) {
|
|
1454
|
+
const nodes = path3?.nodes || [];
|
|
1203
1455
|
const last = nodes[nodes.length - 1];
|
|
1204
1456
|
if (!last) return "*";
|
|
1205
1457
|
return buildSegmentSelector(last);
|
|
@@ -1208,9 +1460,9 @@ async function buildElementPathFromSelector(page, selector) {
|
|
|
1208
1460
|
try {
|
|
1209
1461
|
const handle = await page.mainFrame().locator(selector).first().elementHandle({ timeout: 1500 });
|
|
1210
1462
|
if (!handle) return null;
|
|
1211
|
-
const
|
|
1463
|
+
const path3 = await buildElementPathFromHandle(handle);
|
|
1212
1464
|
await handle.dispose();
|
|
1213
|
-
return
|
|
1465
|
+
return path3;
|
|
1214
1466
|
} catch {
|
|
1215
1467
|
return null;
|
|
1216
1468
|
}
|
|
@@ -1623,11 +1875,11 @@ async function buildElementPathFromHandle(handle) {
|
|
|
1623
1875
|
if (!out) return null;
|
|
1624
1876
|
return sanitizeElementPath(out);
|
|
1625
1877
|
}
|
|
1626
|
-
function sanitizeElementPath(
|
|
1878
|
+
function sanitizeElementPath(path3) {
|
|
1627
1879
|
const cleanNodes = (nodes) => (Array.isArray(nodes) ? nodes : []).map(
|
|
1628
1880
|
(raw) => normalizePathNode(raw)
|
|
1629
1881
|
);
|
|
1630
|
-
const context = (Array.isArray(
|
|
1882
|
+
const context = (Array.isArray(path3?.context) ? path3.context : []).filter(
|
|
1631
1883
|
(hop) => hop && (hop.kind === "iframe" || hop.kind === "shadow")
|
|
1632
1884
|
).map((hop) => ({
|
|
1633
1885
|
kind: hop.kind,
|
|
@@ -1635,7 +1887,7 @@ function sanitizeElementPath(path5) {
|
|
|
1635
1887
|
}));
|
|
1636
1888
|
return {
|
|
1637
1889
|
context,
|
|
1638
|
-
nodes: cleanNodes(
|
|
1890
|
+
nodes: cleanNodes(path3?.nodes || [])
|
|
1639
1891
|
};
|
|
1640
1892
|
}
|
|
1641
1893
|
function normalizePathNode(raw) {
|
|
@@ -2074,16 +2326,16 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
2074
2326
|
nodeTableIndexByKey.set(key, index);
|
|
2075
2327
|
return index;
|
|
2076
2328
|
},
|
|
2077
|
-
packDomPath(
|
|
2078
|
-
return
|
|
2329
|
+
packDomPath(path3) {
|
|
2330
|
+
return path3.map((node) => helpers.internPathNode(node));
|
|
2079
2331
|
},
|
|
2080
|
-
packElementPath(
|
|
2332
|
+
packElementPath(path3) {
|
|
2081
2333
|
return {
|
|
2082
|
-
context: (
|
|
2334
|
+
context: (path3.context || []).map((hop) => ({
|
|
2083
2335
|
kind: "shadow",
|
|
2084
2336
|
host: helpers.packDomPath(hop.host)
|
|
2085
2337
|
})),
|
|
2086
|
-
nodes: helpers.packDomPath(
|
|
2338
|
+
nodes: helpers.packDomPath(path3.nodes)
|
|
2087
2339
|
};
|
|
2088
2340
|
},
|
|
2089
2341
|
ensureNodeId(el) {
|
|
@@ -2172,7 +2424,7 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
2172
2424
|
const nodePaths = /* @__PURE__ */ new Map();
|
|
2173
2425
|
const nodeMeta = /* @__PURE__ */ new Map();
|
|
2174
2426
|
for (const [index, entry] of frameSnapshot.entries.entries()) {
|
|
2175
|
-
const
|
|
2427
|
+
const path3 = decodeSerializedElementPath(
|
|
2176
2428
|
frameSnapshot.nodeTable,
|
|
2177
2429
|
entry.path,
|
|
2178
2430
|
`entries[${index}].path`
|
|
@@ -2180,9 +2432,9 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
2180
2432
|
nodePaths.set(entry.nodeId, {
|
|
2181
2433
|
context: [
|
|
2182
2434
|
...baseContext,
|
|
2183
|
-
...
|
|
2435
|
+
...path3.context || []
|
|
2184
2436
|
],
|
|
2185
|
-
nodes:
|
|
2437
|
+
nodes: path3.nodes
|
|
2186
2438
|
});
|
|
2187
2439
|
nodeMeta.set(entry.nodeId, {
|
|
2188
2440
|
frameToken: frameSnapshot.frameToken,
|
|
@@ -2222,8 +2474,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
2222
2474
|
hostEl.after(
|
|
2223
2475
|
`<${OS_IFRAME_BOUNDARY_TAG} ${OS_BOUNDARY_ATTR}="iframe">${childResult.html}</${OS_IFRAME_BOUNDARY_TAG}>`
|
|
2224
2476
|
);
|
|
2225
|
-
for (const [nodeId,
|
|
2226
|
-
nodePaths.set(nodeId,
|
|
2477
|
+
for (const [nodeId, path3] of childResult.nodePaths.entries()) {
|
|
2478
|
+
nodePaths.set(nodeId, path3);
|
|
2227
2479
|
}
|
|
2228
2480
|
for (const [nodeId, meta] of childResult.nodeMeta.entries()) {
|
|
2229
2481
|
nodeMeta.set(nodeId, meta);
|
|
@@ -2235,8 +2487,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
2235
2487
|
}
|
|
2236
2488
|
}
|
|
2237
2489
|
const sanitizedPaths = /* @__PURE__ */ new Map();
|
|
2238
|
-
for (const [nodeId,
|
|
2239
|
-
sanitizedPaths.set(nodeId, sanitizeElementPath(
|
|
2490
|
+
for (const [nodeId, path3] of nodePaths.entries()) {
|
|
2491
|
+
sanitizedPaths.set(nodeId, sanitizeElementPath(path3));
|
|
2240
2492
|
}
|
|
2241
2493
|
return {
|
|
2242
2494
|
html: $.html(),
|
|
@@ -3058,11 +3310,11 @@ async function assignCounters(page, html, nodePaths, nodeMeta) {
|
|
|
3058
3310
|
if (!nodeId) return;
|
|
3059
3311
|
const counter = nextCounter++;
|
|
3060
3312
|
assignedByNodeId.set(nodeId, counter);
|
|
3061
|
-
const
|
|
3313
|
+
const path3 = nodePaths.get(nodeId);
|
|
3062
3314
|
el.attr("c", String(counter));
|
|
3063
3315
|
el.removeAttr(OS_NODE_ID_ATTR);
|
|
3064
|
-
if (
|
|
3065
|
-
counterIndex.set(counter, cloneElementPath(
|
|
3316
|
+
if (path3) {
|
|
3317
|
+
counterIndex.set(counter, cloneElementPath(path3));
|
|
3066
3318
|
}
|
|
3067
3319
|
});
|
|
3068
3320
|
try {
|
|
@@ -3301,10 +3553,10 @@ var ElementPathError = class extends Error {
|
|
|
3301
3553
|
|
|
3302
3554
|
// src/element-path/resolver.ts
|
|
3303
3555
|
async function resolveElementPath(page, rawPath) {
|
|
3304
|
-
const
|
|
3556
|
+
const path3 = sanitizeElementPath(rawPath);
|
|
3305
3557
|
let frame = page.mainFrame();
|
|
3306
3558
|
let rootHandle = null;
|
|
3307
|
-
for (const hop of
|
|
3559
|
+
for (const hop of path3.context) {
|
|
3308
3560
|
const host = await resolveDomPath(frame, hop.host, rootHandle);
|
|
3309
3561
|
if (!host) {
|
|
3310
3562
|
await disposeHandle(rootHandle);
|
|
@@ -3343,17 +3595,17 @@ async function resolveElementPath(page, rawPath) {
|
|
|
3343
3595
|
await disposeHandle(rootHandle);
|
|
3344
3596
|
rootHandle = shadowRoot;
|
|
3345
3597
|
}
|
|
3346
|
-
const target = await resolveDomPath(frame,
|
|
3598
|
+
const target = await resolveDomPath(frame, path3.nodes, rootHandle);
|
|
3347
3599
|
if (!target) {
|
|
3348
3600
|
const diagnostics = await collectCandidateDiagnostics(
|
|
3349
3601
|
frame,
|
|
3350
|
-
|
|
3602
|
+
path3.nodes,
|
|
3351
3603
|
rootHandle
|
|
3352
3604
|
);
|
|
3353
3605
|
await disposeHandle(rootHandle);
|
|
3354
3606
|
throw new ElementPathError(
|
|
3355
3607
|
"ERR_PATH_TARGET_NOT_FOUND",
|
|
3356
|
-
buildTargetNotFoundMessage(
|
|
3608
|
+
buildTargetNotFoundMessage(path3.nodes, diagnostics)
|
|
3357
3609
|
);
|
|
3358
3610
|
}
|
|
3359
3611
|
await disposeHandle(rootHandle);
|
|
@@ -3362,12 +3614,12 @@ async function resolveElementPath(page, rawPath) {
|
|
|
3362
3614
|
selector: target.selector,
|
|
3363
3615
|
mode: target.mode,
|
|
3364
3616
|
count: target.count,
|
|
3365
|
-
targetDepth:
|
|
3617
|
+
targetDepth: path3.nodes.length
|
|
3366
3618
|
});
|
|
3367
3619
|
}
|
|
3368
3620
|
return {
|
|
3369
3621
|
element: target.element,
|
|
3370
|
-
usedSelector: target.selector || buildPathSelectorHint(
|
|
3622
|
+
usedSelector: target.selector || buildPathSelectorHint(path3)
|
|
3371
3623
|
};
|
|
3372
3624
|
}
|
|
3373
3625
|
async function resolveDomPath(frame, domPath, rootHandle) {
|
|
@@ -3522,6 +3774,16 @@ async function disposeHandle(handle) {
|
|
|
3522
3774
|
|
|
3523
3775
|
// src/extract-value-normalization.ts
|
|
3524
3776
|
var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
|
|
3777
|
+
var IFRAME_URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
3778
|
+
"href",
|
|
3779
|
+
"src",
|
|
3780
|
+
"srcset",
|
|
3781
|
+
"imagesrcset",
|
|
3782
|
+
"action",
|
|
3783
|
+
"formaction",
|
|
3784
|
+
"poster",
|
|
3785
|
+
"ping"
|
|
3786
|
+
]);
|
|
3525
3787
|
function normalizeExtractedValue(raw, attribute) {
|
|
3526
3788
|
if (raw == null) return null;
|
|
3527
3789
|
const rawText = String(raw);
|
|
@@ -3537,6 +3799,19 @@ function normalizeExtractedValue(raw, attribute) {
|
|
|
3537
3799
|
const text = rawText.replace(/\s+/g, " ").trim();
|
|
3538
3800
|
return text || null;
|
|
3539
3801
|
}
|
|
3802
|
+
function resolveExtractedValueInContext(normalizedValue, options) {
|
|
3803
|
+
if (normalizedValue == null) return null;
|
|
3804
|
+
const normalizedAttribute = String(options.attribute || "").trim().toLowerCase();
|
|
3805
|
+
if (!options.insideIframe) return normalizedValue;
|
|
3806
|
+
if (!IFRAME_URL_ATTRIBUTES.has(normalizedAttribute)) return normalizedValue;
|
|
3807
|
+
const baseURI = String(options.baseURI || "").trim();
|
|
3808
|
+
if (!baseURI) return normalizedValue;
|
|
3809
|
+
try {
|
|
3810
|
+
return new URL(normalizedValue, baseURI).href;
|
|
3811
|
+
} catch {
|
|
3812
|
+
return normalizedValue;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3540
3815
|
function pickSingleListAttributeValue(attribute, raw) {
|
|
3541
3816
|
if (attribute === "ping") {
|
|
3542
3817
|
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
@@ -3692,6 +3967,36 @@ function readDescriptorToken(value, index) {
|
|
|
3692
3967
|
};
|
|
3693
3968
|
}
|
|
3694
3969
|
|
|
3970
|
+
// src/extract-value-reader.ts
|
|
3971
|
+
async function readExtractedValueFromHandle(element, options) {
|
|
3972
|
+
const insideIframe = await isElementInsideIframe(element);
|
|
3973
|
+
const payload = await element.evaluate(
|
|
3974
|
+
(target, browserOptions) => {
|
|
3975
|
+
const ownerDocument = target.ownerDocument;
|
|
3976
|
+
return {
|
|
3977
|
+
raw: browserOptions.attribute ? target.getAttribute(browserOptions.attribute) : target.textContent,
|
|
3978
|
+
baseURI: ownerDocument?.baseURI || null
|
|
3979
|
+
};
|
|
3980
|
+
},
|
|
3981
|
+
{
|
|
3982
|
+
attribute: options.attribute
|
|
3983
|
+
}
|
|
3984
|
+
);
|
|
3985
|
+
const normalizedValue = normalizeExtractedValue(
|
|
3986
|
+
payload.raw,
|
|
3987
|
+
options.attribute
|
|
3988
|
+
);
|
|
3989
|
+
return resolveExtractedValueInContext(normalizedValue, {
|
|
3990
|
+
attribute: options.attribute,
|
|
3991
|
+
baseURI: payload.baseURI,
|
|
3992
|
+
insideIframe
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
async function isElementInsideIframe(element) {
|
|
3996
|
+
const ownerFrame = await element.ownerFrame();
|
|
3997
|
+
return !!ownerFrame?.parentFrame();
|
|
3998
|
+
}
|
|
3999
|
+
|
|
3695
4000
|
// src/html/counter-runtime.ts
|
|
3696
4001
|
var CounterResolutionError = class extends Error {
|
|
3697
4002
|
code;
|
|
@@ -3714,13 +4019,14 @@ async function resolveCounterElement(page, counter) {
|
|
|
3714
4019
|
if (entry.count > 1) {
|
|
3715
4020
|
throw buildCounterAmbiguousError(counter);
|
|
3716
4021
|
}
|
|
3717
|
-
const
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
4022
|
+
const resolution = await resolveCounterElementInFrame(entry.frame, normalized);
|
|
4023
|
+
if (resolution.status === "ambiguous") {
|
|
4024
|
+
throw buildCounterAmbiguousError(counter);
|
|
4025
|
+
}
|
|
4026
|
+
if (resolution.status === "missing") {
|
|
3721
4027
|
throw buildCounterNotFoundError(counter);
|
|
3722
4028
|
}
|
|
3723
|
-
return element;
|
|
4029
|
+
return resolution.element;
|
|
3724
4030
|
}
|
|
3725
4031
|
async function resolveCountersBatch(page, requests) {
|
|
3726
4032
|
const out = {};
|
|
@@ -3734,41 +4040,57 @@ async function resolveCountersBatch(page, requests) {
|
|
|
3734
4040
|
}
|
|
3735
4041
|
}
|
|
3736
4042
|
const valueCache = /* @__PURE__ */ new Map();
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
normalized
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4043
|
+
const elementCache = /* @__PURE__ */ new Map();
|
|
4044
|
+
try {
|
|
4045
|
+
for (const request of requests) {
|
|
4046
|
+
const normalized = normalizeCounter(request.counter);
|
|
4047
|
+
if (normalized == null) {
|
|
4048
|
+
out[request.key] = null;
|
|
4049
|
+
continue;
|
|
4050
|
+
}
|
|
4051
|
+
const entry = scan.get(normalized);
|
|
4052
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
4053
|
+
out[request.key] = null;
|
|
4054
|
+
continue;
|
|
4055
|
+
}
|
|
4056
|
+
const cacheKey = `${normalized}:${request.attribute || ""}`;
|
|
4057
|
+
if (valueCache.has(cacheKey)) {
|
|
4058
|
+
out[request.key] = valueCache.get(cacheKey);
|
|
4059
|
+
continue;
|
|
4060
|
+
}
|
|
4061
|
+
if (!elementCache.has(normalized)) {
|
|
4062
|
+
elementCache.set(
|
|
4063
|
+
normalized,
|
|
4064
|
+
await resolveCounterElementInFrame(entry.frame, normalized)
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
const resolution = elementCache.get(normalized);
|
|
4068
|
+
if (resolution.status === "ambiguous") {
|
|
4069
|
+
throw buildCounterAmbiguousError(normalized);
|
|
4070
|
+
}
|
|
4071
|
+
if (resolution.status === "missing") {
|
|
4072
|
+
valueCache.set(cacheKey, null);
|
|
4073
|
+
out[request.key] = null;
|
|
4074
|
+
continue;
|
|
4075
|
+
}
|
|
4076
|
+
const value = await readCounterValueFromElement(
|
|
4077
|
+
resolution.element,
|
|
4078
|
+
request.attribute
|
|
4079
|
+
);
|
|
4080
|
+
if (value.status === "missing") {
|
|
4081
|
+
await resolution.element.dispose();
|
|
4082
|
+
elementCache.set(normalized, {
|
|
4083
|
+
status: "missing"
|
|
4084
|
+
});
|
|
4085
|
+
valueCache.set(cacheKey, null);
|
|
4086
|
+
out[request.key] = null;
|
|
4087
|
+
continue;
|
|
4088
|
+
}
|
|
4089
|
+
valueCache.set(cacheKey, value.value);
|
|
4090
|
+
out[request.key] = value.value;
|
|
3765
4091
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
request.attribute
|
|
3769
|
-
);
|
|
3770
|
-
valueCache.set(cacheKey, normalizedValue);
|
|
3771
|
-
out[request.key] = normalizedValue;
|
|
4092
|
+
} finally {
|
|
4093
|
+
await disposeResolvedCounterElements(elementCache.values());
|
|
3772
4094
|
}
|
|
3773
4095
|
return out;
|
|
3774
4096
|
}
|
|
@@ -3840,73 +4162,79 @@ async function scanCounterOccurrences(page, counters) {
|
|
|
3840
4162
|
}
|
|
3841
4163
|
return out;
|
|
3842
4164
|
}
|
|
3843
|
-
async function
|
|
3844
|
-
|
|
3845
|
-
const
|
|
3846
|
-
|
|
3847
|
-
const
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4165
|
+
async function resolveCounterElementInFrame(frame, counter) {
|
|
4166
|
+
try {
|
|
4167
|
+
const handle = await frame.evaluateHandle((targetCounter) => {
|
|
4168
|
+
const matches = [];
|
|
4169
|
+
const walk = (root) => {
|
|
4170
|
+
const children = Array.from(root.children);
|
|
4171
|
+
for (const child of children) {
|
|
4172
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
4173
|
+
matches.push(child);
|
|
4174
|
+
}
|
|
4175
|
+
walk(child);
|
|
4176
|
+
if (child.shadowRoot) {
|
|
4177
|
+
walk(child.shadowRoot);
|
|
4178
|
+
}
|
|
3855
4179
|
}
|
|
4180
|
+
};
|
|
4181
|
+
walk(document);
|
|
4182
|
+
if (!matches.length) {
|
|
4183
|
+
return "missing";
|
|
3856
4184
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
return
|
|
4185
|
+
if (matches.length > 1) {
|
|
4186
|
+
return "ambiguous";
|
|
4187
|
+
}
|
|
4188
|
+
return matches[0];
|
|
4189
|
+
}, String(counter));
|
|
4190
|
+
const element = handle.asElement();
|
|
4191
|
+
if (element) {
|
|
4192
|
+
return {
|
|
4193
|
+
status: "resolved",
|
|
4194
|
+
element
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
const status = await handle.jsonValue();
|
|
4198
|
+
await handle.dispose();
|
|
4199
|
+
return status === "ambiguous" ? { status: "ambiguous" } : { status: "missing" };
|
|
4200
|
+
} catch (error) {
|
|
4201
|
+
if (isRecoverableCounterReadRace(error)) {
|
|
4202
|
+
return {
|
|
4203
|
+
status: "missing"
|
|
4204
|
+
};
|
|
3861
4205
|
}
|
|
3862
|
-
|
|
3863
|
-
}
|
|
4206
|
+
throw error;
|
|
4207
|
+
}
|
|
3864
4208
|
}
|
|
3865
|
-
async function
|
|
4209
|
+
async function readCounterValueFromElement(element, attribute) {
|
|
3866
4210
|
try {
|
|
3867
|
-
return await frame.evaluate(
|
|
3868
|
-
({ targetCounter, attribute: attribute2 }) => {
|
|
3869
|
-
const matches = [];
|
|
3870
|
-
const walk = (root) => {
|
|
3871
|
-
const children = Array.from(root.children);
|
|
3872
|
-
for (const child of children) {
|
|
3873
|
-
if (child.getAttribute("c") === targetCounter) {
|
|
3874
|
-
matches.push(child);
|
|
3875
|
-
}
|
|
3876
|
-
walk(child);
|
|
3877
|
-
if (child.shadowRoot) {
|
|
3878
|
-
walk(child.shadowRoot);
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
};
|
|
3882
|
-
walk(document);
|
|
3883
|
-
if (!matches.length) {
|
|
3884
|
-
return {
|
|
3885
|
-
status: "missing"
|
|
3886
|
-
};
|
|
3887
|
-
}
|
|
3888
|
-
if (matches.length > 1) {
|
|
3889
|
-
return {
|
|
3890
|
-
status: "ambiguous"
|
|
3891
|
-
};
|
|
3892
|
-
}
|
|
3893
|
-
const target = matches[0];
|
|
3894
|
-
const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
|
|
3895
|
-
return {
|
|
3896
|
-
status: "ok",
|
|
3897
|
-
value
|
|
3898
|
-
};
|
|
3899
|
-
},
|
|
3900
|
-
{
|
|
3901
|
-
targetCounter: String(counter),
|
|
3902
|
-
attribute
|
|
3903
|
-
}
|
|
3904
|
-
);
|
|
3905
|
-
} catch {
|
|
3906
4211
|
return {
|
|
3907
|
-
status: "
|
|
4212
|
+
status: "ok",
|
|
4213
|
+
value: await readExtractedValueFromHandle(element, {
|
|
4214
|
+
attribute
|
|
4215
|
+
})
|
|
3908
4216
|
};
|
|
4217
|
+
} catch (error) {
|
|
4218
|
+
if (isRecoverableCounterReadRace(error)) {
|
|
4219
|
+
return {
|
|
4220
|
+
status: "missing"
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
throw error;
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
async function disposeResolvedCounterElements(resolutions) {
|
|
4227
|
+
const disposals = [];
|
|
4228
|
+
for (const resolution of resolutions) {
|
|
4229
|
+
if (resolution.status !== "resolved") continue;
|
|
4230
|
+
disposals.push(resolution.element.dispose());
|
|
3909
4231
|
}
|
|
4232
|
+
await Promise.all(disposals);
|
|
4233
|
+
}
|
|
4234
|
+
function isRecoverableCounterReadRace(error) {
|
|
4235
|
+
if (!(error instanceof Error)) return false;
|
|
4236
|
+
const message = error.message;
|
|
4237
|
+
return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("Frame was detached") || message.includes("Element is not attached to the DOM") || message.includes("Element is detached");
|
|
3910
4238
|
}
|
|
3911
4239
|
function buildCounterNotFoundError(counter) {
|
|
3912
4240
|
return new CounterResolutionError(
|
|
@@ -4391,10 +4719,10 @@ function classifyPathResolutionFailure(action, err) {
|
|
|
4391
4719
|
}
|
|
4392
4720
|
|
|
4393
4721
|
// src/actions/click.ts
|
|
4394
|
-
async function performClick(page,
|
|
4722
|
+
async function performClick(page, path3, options) {
|
|
4395
4723
|
let resolved;
|
|
4396
4724
|
try {
|
|
4397
|
-
resolved = await resolveElementPath(page,
|
|
4725
|
+
resolved = await resolveElementPath(page, path3);
|
|
4398
4726
|
} catch (err) {
|
|
4399
4727
|
const failure = classifyPathResolutionFailure("click", err);
|
|
4400
4728
|
return { ok: false, error: failure.message, failure };
|
|
@@ -4407,7 +4735,7 @@ async function performClick(page, path5, options) {
|
|
|
4407
4735
|
});
|
|
4408
4736
|
return {
|
|
4409
4737
|
ok: true,
|
|
4410
|
-
path:
|
|
4738
|
+
path: path3,
|
|
4411
4739
|
usedSelector: resolved.usedSelector
|
|
4412
4740
|
};
|
|
4413
4741
|
} catch (err) {
|
|
@@ -4424,10 +4752,10 @@ async function performClick(page, path5, options) {
|
|
|
4424
4752
|
}
|
|
4425
4753
|
|
|
4426
4754
|
// src/actions/hover.ts
|
|
4427
|
-
async function performHover(page,
|
|
4755
|
+
async function performHover(page, path3, options) {
|
|
4428
4756
|
let resolved;
|
|
4429
4757
|
try {
|
|
4430
|
-
resolved = await resolveElementPath(page,
|
|
4758
|
+
resolved = await resolveElementPath(page, path3);
|
|
4431
4759
|
} catch (err) {
|
|
4432
4760
|
const failure = classifyPathResolutionFailure("hover", err);
|
|
4433
4761
|
return { ok: false, error: failure.message, failure };
|
|
@@ -4439,7 +4767,7 @@ async function performHover(page, path5, options) {
|
|
|
4439
4767
|
});
|
|
4440
4768
|
return {
|
|
4441
4769
|
ok: true,
|
|
4442
|
-
path:
|
|
4770
|
+
path: path3,
|
|
4443
4771
|
usedSelector: resolved.usedSelector
|
|
4444
4772
|
};
|
|
4445
4773
|
} catch (err) {
|
|
@@ -4456,10 +4784,10 @@ async function performHover(page, path5, options) {
|
|
|
4456
4784
|
}
|
|
4457
4785
|
|
|
4458
4786
|
// src/actions/input.ts
|
|
4459
|
-
async function performInput(page,
|
|
4787
|
+
async function performInput(page, path3, options) {
|
|
4460
4788
|
let resolved;
|
|
4461
4789
|
try {
|
|
4462
|
-
resolved = await resolveElementPath(page,
|
|
4790
|
+
resolved = await resolveElementPath(page, path3);
|
|
4463
4791
|
} catch (err) {
|
|
4464
4792
|
const failure = classifyPathResolutionFailure("input", err);
|
|
4465
4793
|
return { ok: false, error: failure.message, failure };
|
|
@@ -4475,7 +4803,7 @@ async function performInput(page, path5, options) {
|
|
|
4475
4803
|
}
|
|
4476
4804
|
return {
|
|
4477
4805
|
ok: true,
|
|
4478
|
-
path:
|
|
4806
|
+
path: path3,
|
|
4479
4807
|
usedSelector: resolved.usedSelector
|
|
4480
4808
|
};
|
|
4481
4809
|
} catch (err) {
|
|
@@ -4507,9 +4835,9 @@ function getScrollDelta(options) {
|
|
|
4507
4835
|
return { x: 0, y: absoluteAmount };
|
|
4508
4836
|
}
|
|
4509
4837
|
}
|
|
4510
|
-
async function performScroll(page,
|
|
4838
|
+
async function performScroll(page, path3, options) {
|
|
4511
4839
|
const { x, y } = getScrollDelta(options);
|
|
4512
|
-
if (!
|
|
4840
|
+
if (!path3) {
|
|
4513
4841
|
await page.evaluate(
|
|
4514
4842
|
({ deltaX, deltaY }) => {
|
|
4515
4843
|
window.scrollBy(deltaX, deltaY);
|
|
@@ -4520,7 +4848,7 @@ async function performScroll(page, path5, options) {
|
|
|
4520
4848
|
}
|
|
4521
4849
|
let resolved;
|
|
4522
4850
|
try {
|
|
4523
|
-
resolved = await resolveElementPath(page,
|
|
4851
|
+
resolved = await resolveElementPath(page, path3);
|
|
4524
4852
|
} catch (err) {
|
|
4525
4853
|
const failure = classifyPathResolutionFailure("scroll", err);
|
|
4526
4854
|
return { ok: false, error: failure.message, failure };
|
|
@@ -4536,7 +4864,7 @@ async function performScroll(page, path5, options) {
|
|
|
4536
4864
|
);
|
|
4537
4865
|
return {
|
|
4538
4866
|
ok: true,
|
|
4539
|
-
path:
|
|
4867
|
+
path: path3,
|
|
4540
4868
|
usedSelector: resolved.usedSelector
|
|
4541
4869
|
};
|
|
4542
4870
|
} catch (err) {
|
|
@@ -4553,10 +4881,10 @@ async function performScroll(page, path5, options) {
|
|
|
4553
4881
|
}
|
|
4554
4882
|
|
|
4555
4883
|
// src/actions/select.ts
|
|
4556
|
-
async function performSelect(page,
|
|
4884
|
+
async function performSelect(page, path3, options) {
|
|
4557
4885
|
let resolved;
|
|
4558
4886
|
try {
|
|
4559
|
-
resolved = await resolveElementPath(page,
|
|
4887
|
+
resolved = await resolveElementPath(page, path3);
|
|
4560
4888
|
} catch (err) {
|
|
4561
4889
|
const failure = classifyPathResolutionFailure("select", err);
|
|
4562
4890
|
return { ok: false, error: failure.message, failure };
|
|
@@ -4582,7 +4910,7 @@ async function performSelect(page, path5, options) {
|
|
|
4582
4910
|
}
|
|
4583
4911
|
return {
|
|
4584
4912
|
ok: true,
|
|
4585
|
-
path:
|
|
4913
|
+
path: path3,
|
|
4586
4914
|
usedSelector: resolved.usedSelector
|
|
4587
4915
|
};
|
|
4588
4916
|
} catch (err) {
|
|
@@ -4599,17 +4927,6 @@ async function performSelect(page, path5, options) {
|
|
|
4599
4927
|
}
|
|
4600
4928
|
|
|
4601
4929
|
// src/actions/extract.ts
|
|
4602
|
-
async function readFieldValueFromHandle(element, options) {
|
|
4603
|
-
const raw = await element.evaluate(
|
|
4604
|
-
(target, payload) => {
|
|
4605
|
-
return payload.attribute ? target.getAttribute(payload.attribute) : target.textContent;
|
|
4606
|
-
},
|
|
4607
|
-
{
|
|
4608
|
-
attribute: options.attribute
|
|
4609
|
-
}
|
|
4610
|
-
);
|
|
4611
|
-
return normalizeExtractedValue(raw, options.attribute);
|
|
4612
|
-
}
|
|
4613
4930
|
async function extractWithPaths(page, fields) {
|
|
4614
4931
|
const result = {};
|
|
4615
4932
|
for (const field of fields) {
|
|
@@ -4621,7 +4938,7 @@ async function extractWithPaths(page, fields) {
|
|
|
4621
4938
|
continue;
|
|
4622
4939
|
}
|
|
4623
4940
|
try {
|
|
4624
|
-
result[field.key] = await
|
|
4941
|
+
result[field.key] = await readExtractedValueFromHandle(
|
|
4625
4942
|
resolved.element,
|
|
4626
4943
|
{
|
|
4627
4944
|
attribute: field.attribute
|
|
@@ -4661,7 +4978,7 @@ async function extractArrayRowsWithPaths(page, array) {
|
|
|
4661
4978
|
field.candidates
|
|
4662
4979
|
) : item;
|
|
4663
4980
|
try {
|
|
4664
|
-
const value = target ? await
|
|
4981
|
+
const value = target ? await readExtractedValueFromHandle(target, {
|
|
4665
4982
|
attribute: field.attribute
|
|
4666
4983
|
}) : null;
|
|
4667
4984
|
if (key) {
|
|
@@ -4688,8 +5005,8 @@ async function countArrayItemsWithPath(page, itemParentPath) {
|
|
|
4688
5005
|
await Promise.all(items.map((item) => item.dispose()));
|
|
4689
5006
|
return count;
|
|
4690
5007
|
}
|
|
4691
|
-
async function queryAllByElementPath(page,
|
|
4692
|
-
const normalized = sanitizeElementPath(
|
|
5008
|
+
async function queryAllByElementPath(page, path3) {
|
|
5009
|
+
const normalized = sanitizeElementPath(path3);
|
|
4693
5010
|
const scope = await resolvePathScope(page, normalized.context);
|
|
4694
5011
|
if (!scope) return [];
|
|
4695
5012
|
try {
|
|
@@ -4861,9 +5178,9 @@ async function queryFirstByCandidates(selectors, query) {
|
|
|
4861
5178
|
}
|
|
4862
5179
|
return fallback;
|
|
4863
5180
|
}
|
|
4864
|
-
function buildArrayFieldPathCandidates(
|
|
4865
|
-
const strict =
|
|
4866
|
-
const relaxedNodes = stripPositionClauses(
|
|
5181
|
+
function buildArrayFieldPathCandidates(path3) {
|
|
5182
|
+
const strict = path3.nodes.length ? buildPathCandidates(path3.nodes) : [];
|
|
5183
|
+
const relaxedNodes = stripPositionClauses(path3.nodes);
|
|
4867
5184
|
const relaxed = relaxedNodes.length ? buildPathCandidates(relaxedNodes) : [];
|
|
4868
5185
|
return dedupeSelectors([...strict, ...relaxed]);
|
|
4869
5186
|
}
|
|
@@ -4976,7 +5293,7 @@ async function closeTab(context, activePage, index) {
|
|
|
4976
5293
|
}
|
|
4977
5294
|
|
|
4978
5295
|
// src/actions/cookies.ts
|
|
4979
|
-
import { readFile, writeFile } from "fs/promises";
|
|
5296
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
4980
5297
|
async function getCookies(context, url) {
|
|
4981
5298
|
return context.cookies(url ? [url] : void 0);
|
|
4982
5299
|
}
|
|
@@ -4991,7 +5308,7 @@ async function exportCookies(context, filePath, url) {
|
|
|
4991
5308
|
await writeFile(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
4992
5309
|
}
|
|
4993
5310
|
async function importCookies(context, filePath) {
|
|
4994
|
-
const raw = await
|
|
5311
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4995
5312
|
const cookies = JSON.parse(raw);
|
|
4996
5313
|
await context.addCookies(cookies);
|
|
4997
5314
|
}
|
|
@@ -5005,8 +5322,8 @@ async function typeText(page, text) {
|
|
|
5005
5322
|
}
|
|
5006
5323
|
|
|
5007
5324
|
// src/actions/element-info.ts
|
|
5008
|
-
async function getElementText(page,
|
|
5009
|
-
const resolved = await resolveElementPath(page,
|
|
5325
|
+
async function getElementText(page, path3) {
|
|
5326
|
+
const resolved = await resolveElementPath(page, path3);
|
|
5010
5327
|
try {
|
|
5011
5328
|
const text = await resolved.element.textContent();
|
|
5012
5329
|
return text ?? "";
|
|
@@ -5014,16 +5331,16 @@ async function getElementText(page, path5) {
|
|
|
5014
5331
|
await resolved.element.dispose();
|
|
5015
5332
|
}
|
|
5016
5333
|
}
|
|
5017
|
-
async function getElementValue(page,
|
|
5018
|
-
const resolved = await resolveElementPath(page,
|
|
5334
|
+
async function getElementValue(page, path3) {
|
|
5335
|
+
const resolved = await resolveElementPath(page, path3);
|
|
5019
5336
|
try {
|
|
5020
5337
|
return await resolved.element.inputValue();
|
|
5021
5338
|
} finally {
|
|
5022
5339
|
await resolved.element.dispose();
|
|
5023
5340
|
}
|
|
5024
5341
|
}
|
|
5025
|
-
async function getElementAttributes(page,
|
|
5026
|
-
const resolved = await resolveElementPath(page,
|
|
5342
|
+
async function getElementAttributes(page, path3) {
|
|
5343
|
+
const resolved = await resolveElementPath(page, path3);
|
|
5027
5344
|
try {
|
|
5028
5345
|
return await resolved.element.evaluate((el) => {
|
|
5029
5346
|
const attrs = {};
|
|
@@ -5036,8 +5353,8 @@ async function getElementAttributes(page, path5) {
|
|
|
5036
5353
|
await resolved.element.dispose();
|
|
5037
5354
|
}
|
|
5038
5355
|
}
|
|
5039
|
-
async function getElementBoundingBox(page,
|
|
5040
|
-
const resolved = await resolveElementPath(page,
|
|
5356
|
+
async function getElementBoundingBox(page, path3) {
|
|
5357
|
+
const resolved = await resolveElementPath(page, path3);
|
|
5041
5358
|
try {
|
|
5042
5359
|
return await resolved.element.boundingBox();
|
|
5043
5360
|
} finally {
|
|
@@ -5063,10 +5380,10 @@ async function getPageTitle(page) {
|
|
|
5063
5380
|
}
|
|
5064
5381
|
|
|
5065
5382
|
// src/actions/file-upload.ts
|
|
5066
|
-
async function performFileUpload(page,
|
|
5383
|
+
async function performFileUpload(page, path3, filePaths) {
|
|
5067
5384
|
let resolved;
|
|
5068
5385
|
try {
|
|
5069
|
-
resolved = await resolveElementPath(page,
|
|
5386
|
+
resolved = await resolveElementPath(page, path3);
|
|
5070
5387
|
} catch (err) {
|
|
5071
5388
|
const failure = classifyPathResolutionFailure("uploadFile", err);
|
|
5072
5389
|
return { ok: false, error: failure.message, failure };
|
|
@@ -5075,7 +5392,7 @@ async function performFileUpload(page, path5, filePaths) {
|
|
|
5075
5392
|
await resolved.element.setInputFiles(filePaths);
|
|
5076
5393
|
return {
|
|
5077
5394
|
ok: true,
|
|
5078
|
-
path:
|
|
5395
|
+
path: path3,
|
|
5079
5396
|
usedSelector: resolved.usedSelector
|
|
5080
5397
|
};
|
|
5081
5398
|
} catch (err) {
|
|
@@ -5112,32 +5429,6 @@ var OpensteerActionError = class extends Error {
|
|
|
5112
5429
|
// src/cloud/contracts.ts
|
|
5113
5430
|
var cloudSessionContractVersion = "v3";
|
|
5114
5431
|
|
|
5115
|
-
// src/cloud/errors.ts
|
|
5116
|
-
var OpensteerCloudError = class extends Error {
|
|
5117
|
-
code;
|
|
5118
|
-
status;
|
|
5119
|
-
details;
|
|
5120
|
-
constructor(code, message, status, details) {
|
|
5121
|
-
super(message);
|
|
5122
|
-
this.name = "OpensteerCloudError";
|
|
5123
|
-
this.code = code;
|
|
5124
|
-
this.status = status;
|
|
5125
|
-
this.details = details;
|
|
5126
|
-
}
|
|
5127
|
-
};
|
|
5128
|
-
function cloudUnsupportedMethodError(method, message) {
|
|
5129
|
-
return new OpensteerCloudError(
|
|
5130
|
-
"CLOUD_UNSUPPORTED_METHOD",
|
|
5131
|
-
message || `${method} is not supported in cloud mode.`
|
|
5132
|
-
);
|
|
5133
|
-
}
|
|
5134
|
-
function cloudNotLaunchedError() {
|
|
5135
|
-
return new OpensteerCloudError(
|
|
5136
|
-
"CLOUD_SESSION_NOT_FOUND",
|
|
5137
|
-
"Cloud session is not connected. Call launch() first."
|
|
5138
|
-
);
|
|
5139
|
-
}
|
|
5140
|
-
|
|
5141
5432
|
// src/cloud/action-ws-client.ts
|
|
5142
5433
|
import WebSocket from "ws";
|
|
5143
5434
|
var ActionWsClient = class _ActionWsClient {
|
|
@@ -5275,7 +5566,7 @@ function withTokenQuery(wsUrl, token) {
|
|
|
5275
5566
|
|
|
5276
5567
|
// src/cloud/local-cache-sync.ts
|
|
5277
5568
|
import fs2 from "fs";
|
|
5278
|
-
import
|
|
5569
|
+
import path2 from "path";
|
|
5279
5570
|
function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
5280
5571
|
const debug = options.debug === true;
|
|
5281
5572
|
const namespace = storage.getNamespace();
|
|
@@ -5285,7 +5576,7 @@ function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
|
5285
5576
|
const fileNames = fs2.readdirSync(namespaceDir);
|
|
5286
5577
|
for (const fileName of fileNames) {
|
|
5287
5578
|
if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
|
|
5288
|
-
const filePath =
|
|
5579
|
+
const filePath = path2.join(namespaceDir, fileName);
|
|
5289
5580
|
const selector = readSelectorFile(filePath, debug);
|
|
5290
5581
|
if (!selector) continue;
|
|
5291
5582
|
const descriptionHash = normalizeDescriptionHash(selector.id);
|
|
@@ -5407,324 +5698,6 @@ function dedupeNewest(entries) {
|
|
|
5407
5698
|
return [...byKey.values()];
|
|
5408
5699
|
}
|
|
5409
5700
|
|
|
5410
|
-
// src/cloud/cdp-client.ts
|
|
5411
|
-
import {
|
|
5412
|
-
chromium
|
|
5413
|
-
} from "playwright";
|
|
5414
|
-
var CloudCdpClient = class {
|
|
5415
|
-
async connect(args) {
|
|
5416
|
-
const endpoint = withTokenQuery2(args.wsUrl, args.token);
|
|
5417
|
-
let browser;
|
|
5418
|
-
try {
|
|
5419
|
-
browser = await chromium.connectOverCDP(endpoint);
|
|
5420
|
-
} catch (error) {
|
|
5421
|
-
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
5422
|
-
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
5423
|
-
}
|
|
5424
|
-
const contexts = browser.contexts();
|
|
5425
|
-
const context = contexts[0];
|
|
5426
|
-
if (!context) {
|
|
5427
|
-
await browser.close();
|
|
5428
|
-
throw new OpensteerCloudError(
|
|
5429
|
-
"CLOUD_INTERNAL",
|
|
5430
|
-
"Cloud browser returned no context."
|
|
5431
|
-
);
|
|
5432
|
-
}
|
|
5433
|
-
const preferred = selectPreferredContextPage(browser, contexts);
|
|
5434
|
-
if (preferred) {
|
|
5435
|
-
return preferred;
|
|
5436
|
-
}
|
|
5437
|
-
const page = context.pages()[0] || await context.newPage();
|
|
5438
|
-
return { browser, context, page };
|
|
5439
|
-
}
|
|
5440
|
-
};
|
|
5441
|
-
function selectPreferredContextPage(browser, contexts) {
|
|
5442
|
-
let aboutBlankCandidate = null;
|
|
5443
|
-
for (const context of contexts) {
|
|
5444
|
-
for (const page of context.pages()) {
|
|
5445
|
-
const url = safePageUrl(page);
|
|
5446
|
-
if (!isInternalOrEmptyUrl(url)) {
|
|
5447
|
-
return { browser, context, page };
|
|
5448
|
-
}
|
|
5449
|
-
if (!aboutBlankCandidate && url === "about:blank") {
|
|
5450
|
-
aboutBlankCandidate = { browser, context, page };
|
|
5451
|
-
}
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
return aboutBlankCandidate;
|
|
5455
|
-
}
|
|
5456
|
-
function safePageUrl(page) {
|
|
5457
|
-
try {
|
|
5458
|
-
return page.url();
|
|
5459
|
-
} catch {
|
|
5460
|
-
return "";
|
|
5461
|
-
}
|
|
5462
|
-
}
|
|
5463
|
-
function isInternalOrEmptyUrl(url) {
|
|
5464
|
-
if (!url) return true;
|
|
5465
|
-
if (url === "about:blank") return true;
|
|
5466
|
-
return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
|
|
5467
|
-
}
|
|
5468
|
-
function withTokenQuery2(wsUrl, token) {
|
|
5469
|
-
const url = new URL(wsUrl);
|
|
5470
|
-
url.searchParams.set("token", token);
|
|
5471
|
-
return url.toString();
|
|
5472
|
-
}
|
|
5473
|
-
|
|
5474
|
-
// src/cloud/session-client.ts
|
|
5475
|
-
var CACHE_IMPORT_BATCH_SIZE = 200;
|
|
5476
|
-
var CloudSessionClient = class {
|
|
5477
|
-
baseUrl;
|
|
5478
|
-
key;
|
|
5479
|
-
authScheme;
|
|
5480
|
-
constructor(baseUrl, key, authScheme = "api-key") {
|
|
5481
|
-
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
5482
|
-
this.key = key;
|
|
5483
|
-
this.authScheme = authScheme;
|
|
5484
|
-
}
|
|
5485
|
-
async create(request) {
|
|
5486
|
-
const response = await fetch(`${this.baseUrl}/sessions`, {
|
|
5487
|
-
method: "POST",
|
|
5488
|
-
headers: {
|
|
5489
|
-
"content-type": "application/json",
|
|
5490
|
-
...this.authHeaders()
|
|
5491
|
-
},
|
|
5492
|
-
body: JSON.stringify(request)
|
|
5493
|
-
});
|
|
5494
|
-
if (!response.ok) {
|
|
5495
|
-
throw await parseHttpError(response);
|
|
5496
|
-
}
|
|
5497
|
-
let body;
|
|
5498
|
-
try {
|
|
5499
|
-
body = await response.json();
|
|
5500
|
-
} catch {
|
|
5501
|
-
throw new OpensteerCloudError(
|
|
5502
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5503
|
-
"Invalid cloud session create response: expected a JSON object.",
|
|
5504
|
-
response.status
|
|
5505
|
-
);
|
|
5506
|
-
}
|
|
5507
|
-
return parseCreateResponse(body, response.status);
|
|
5508
|
-
}
|
|
5509
|
-
async close(sessionId) {
|
|
5510
|
-
const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
|
|
5511
|
-
method: "DELETE",
|
|
5512
|
-
headers: {
|
|
5513
|
-
...this.authHeaders()
|
|
5514
|
-
}
|
|
5515
|
-
});
|
|
5516
|
-
if (response.status === 204) {
|
|
5517
|
-
return;
|
|
5518
|
-
}
|
|
5519
|
-
if (!response.ok) {
|
|
5520
|
-
throw await parseHttpError(response);
|
|
5521
|
-
}
|
|
5522
|
-
}
|
|
5523
|
-
async importSelectorCache(request) {
|
|
5524
|
-
if (!request.entries.length) {
|
|
5525
|
-
return zeroImportResponse();
|
|
5526
|
-
}
|
|
5527
|
-
let totals = zeroImportResponse();
|
|
5528
|
-
for (let offset = 0; offset < request.entries.length; offset += CACHE_IMPORT_BATCH_SIZE) {
|
|
5529
|
-
const batch = request.entries.slice(
|
|
5530
|
-
offset,
|
|
5531
|
-
offset + CACHE_IMPORT_BATCH_SIZE
|
|
5532
|
-
);
|
|
5533
|
-
const response = await this.importSelectorCacheBatch(batch);
|
|
5534
|
-
totals = mergeImportResponse(totals, response);
|
|
5535
|
-
}
|
|
5536
|
-
return totals;
|
|
5537
|
-
}
|
|
5538
|
-
async importSelectorCacheBatch(entries) {
|
|
5539
|
-
const response = await fetch(`${this.baseUrl}/selector-cache/import`, {
|
|
5540
|
-
method: "POST",
|
|
5541
|
-
headers: {
|
|
5542
|
-
"content-type": "application/json",
|
|
5543
|
-
...this.authHeaders()
|
|
5544
|
-
},
|
|
5545
|
-
body: JSON.stringify({ entries })
|
|
5546
|
-
});
|
|
5547
|
-
if (!response.ok) {
|
|
5548
|
-
throw await parseHttpError(response);
|
|
5549
|
-
}
|
|
5550
|
-
return await response.json();
|
|
5551
|
-
}
|
|
5552
|
-
authHeaders() {
|
|
5553
|
-
if (this.authScheme === "bearer") {
|
|
5554
|
-
return {
|
|
5555
|
-
authorization: `Bearer ${this.key}`
|
|
5556
|
-
};
|
|
5557
|
-
}
|
|
5558
|
-
return {
|
|
5559
|
-
"x-api-key": this.key
|
|
5560
|
-
};
|
|
5561
|
-
}
|
|
5562
|
-
};
|
|
5563
|
-
function normalizeBaseUrl(baseUrl) {
|
|
5564
|
-
return baseUrl.replace(/\/+$/, "");
|
|
5565
|
-
}
|
|
5566
|
-
function parseCreateResponse(body, status) {
|
|
5567
|
-
const root = requireObject(
|
|
5568
|
-
body,
|
|
5569
|
-
"Invalid cloud session create response: expected a JSON object.",
|
|
5570
|
-
status
|
|
5571
|
-
);
|
|
5572
|
-
const sessionId = requireString(root, "sessionId", status);
|
|
5573
|
-
const actionWsUrl = requireString(root, "actionWsUrl", status);
|
|
5574
|
-
const cdpWsUrl = requireString(root, "cdpWsUrl", status);
|
|
5575
|
-
const actionToken = requireString(root, "actionToken", status);
|
|
5576
|
-
const cdpToken = requireString(root, "cdpToken", status);
|
|
5577
|
-
const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
|
|
5578
|
-
const cloudSessionRoot = requireObject(
|
|
5579
|
-
root.cloudSession,
|
|
5580
|
-
"Invalid cloud session create response: cloudSession must be an object.",
|
|
5581
|
-
status
|
|
5582
|
-
);
|
|
5583
|
-
const cloudSession = {
|
|
5584
|
-
sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
|
|
5585
|
-
workspaceId: requireString(
|
|
5586
|
-
cloudSessionRoot,
|
|
5587
|
-
"workspaceId",
|
|
5588
|
-
status,
|
|
5589
|
-
"cloudSession"
|
|
5590
|
-
),
|
|
5591
|
-
state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
|
|
5592
|
-
createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
|
|
5593
|
-
sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
|
|
5594
|
-
sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
|
|
5595
|
-
label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
|
|
5596
|
-
};
|
|
5597
|
-
const expiresAt = optionalNumber(root, "expiresAt", status);
|
|
5598
|
-
return {
|
|
5599
|
-
sessionId,
|
|
5600
|
-
actionWsUrl,
|
|
5601
|
-
cdpWsUrl,
|
|
5602
|
-
actionToken,
|
|
5603
|
-
cdpToken,
|
|
5604
|
-
expiresAt,
|
|
5605
|
-
cloudSessionUrl,
|
|
5606
|
-
cloudSession
|
|
5607
|
-
};
|
|
5608
|
-
}
|
|
5609
|
-
function requireObject(value, message, status) {
|
|
5610
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5611
|
-
throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
|
|
5612
|
-
}
|
|
5613
|
-
return value;
|
|
5614
|
-
}
|
|
5615
|
-
function requireString(source, field, status, parent) {
|
|
5616
|
-
const value = source[field];
|
|
5617
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
5618
|
-
throw new OpensteerCloudError(
|
|
5619
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5620
|
-
`Invalid cloud session create response: ${formatFieldPath(
|
|
5621
|
-
field,
|
|
5622
|
-
parent
|
|
5623
|
-
)} must be a non-empty string.`,
|
|
5624
|
-
status
|
|
5625
|
-
);
|
|
5626
|
-
}
|
|
5627
|
-
return value;
|
|
5628
|
-
}
|
|
5629
|
-
function requireNumber(source, field, status, parent) {
|
|
5630
|
-
const value = source[field];
|
|
5631
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5632
|
-
throw new OpensteerCloudError(
|
|
5633
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5634
|
-
`Invalid cloud session create response: ${formatFieldPath(
|
|
5635
|
-
field,
|
|
5636
|
-
parent
|
|
5637
|
-
)} must be a finite number.`,
|
|
5638
|
-
status
|
|
5639
|
-
);
|
|
5640
|
-
}
|
|
5641
|
-
return value;
|
|
5642
|
-
}
|
|
5643
|
-
function optionalString(source, field, status, parent) {
|
|
5644
|
-
const value = source[field];
|
|
5645
|
-
if (value == null) {
|
|
5646
|
-
return void 0;
|
|
5647
|
-
}
|
|
5648
|
-
if (typeof value !== "string") {
|
|
5649
|
-
throw new OpensteerCloudError(
|
|
5650
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5651
|
-
`Invalid cloud session create response: ${formatFieldPath(
|
|
5652
|
-
field,
|
|
5653
|
-
parent
|
|
5654
|
-
)} must be a string when present.`,
|
|
5655
|
-
status
|
|
5656
|
-
);
|
|
5657
|
-
}
|
|
5658
|
-
return value;
|
|
5659
|
-
}
|
|
5660
|
-
function optionalNumber(source, field, status, parent) {
|
|
5661
|
-
const value = source[field];
|
|
5662
|
-
if (value == null) {
|
|
5663
|
-
return void 0;
|
|
5664
|
-
}
|
|
5665
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5666
|
-
throw new OpensteerCloudError(
|
|
5667
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5668
|
-
`Invalid cloud session create response: ${formatFieldPath(
|
|
5669
|
-
field,
|
|
5670
|
-
parent
|
|
5671
|
-
)} must be a finite number when present.`,
|
|
5672
|
-
status
|
|
5673
|
-
);
|
|
5674
|
-
}
|
|
5675
|
-
return value;
|
|
5676
|
-
}
|
|
5677
|
-
function requireSourceType(source, field, status, parent) {
|
|
5678
|
-
const value = source[field];
|
|
5679
|
-
if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
|
|
5680
|
-
return value;
|
|
5681
|
-
}
|
|
5682
|
-
throw new OpensteerCloudError(
|
|
5683
|
-
"CLOUD_CONTRACT_MISMATCH",
|
|
5684
|
-
`Invalid cloud session create response: ${formatFieldPath(
|
|
5685
|
-
field,
|
|
5686
|
-
parent
|
|
5687
|
-
)} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
|
|
5688
|
-
status
|
|
5689
|
-
);
|
|
5690
|
-
}
|
|
5691
|
-
function formatFieldPath(field, parent) {
|
|
5692
|
-
return parent ? `"${parent}.${field}"` : `"${field}"`;
|
|
5693
|
-
}
|
|
5694
|
-
function zeroImportResponse() {
|
|
5695
|
-
return {
|
|
5696
|
-
imported: 0,
|
|
5697
|
-
inserted: 0,
|
|
5698
|
-
updated: 0,
|
|
5699
|
-
skipped: 0
|
|
5700
|
-
};
|
|
5701
|
-
}
|
|
5702
|
-
function mergeImportResponse(first, second) {
|
|
5703
|
-
return {
|
|
5704
|
-
imported: first.imported + second.imported,
|
|
5705
|
-
inserted: first.inserted + second.inserted,
|
|
5706
|
-
updated: first.updated + second.updated,
|
|
5707
|
-
skipped: first.skipped + second.skipped
|
|
5708
|
-
};
|
|
5709
|
-
}
|
|
5710
|
-
async function parseHttpError(response) {
|
|
5711
|
-
let body = null;
|
|
5712
|
-
try {
|
|
5713
|
-
body = await response.json();
|
|
5714
|
-
} catch {
|
|
5715
|
-
body = null;
|
|
5716
|
-
}
|
|
5717
|
-
const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
|
|
5718
|
-
const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
|
|
5719
|
-
return new OpensteerCloudError(code, message, response.status, body?.details);
|
|
5720
|
-
}
|
|
5721
|
-
function toCloudErrorCode(code) {
|
|
5722
|
-
if (code === "CLOUD_AUTH_FAILED" || code === "CLOUD_SESSION_NOT_FOUND" || code === "CLOUD_SESSION_CLOSED" || code === "CLOUD_UNSUPPORTED_METHOD" || code === "CLOUD_INVALID_REQUEST" || code === "CLOUD_MODEL_NOT_ALLOWED" || code === "CLOUD_ACTION_FAILED" || code === "CLOUD_INTERNAL" || code === "CLOUD_CAPACITY_EXHAUSTED" || code === "CLOUD_RUNTIME_UNAVAILABLE" || code === "CLOUD_RUNTIME_MISMATCH" || code === "CLOUD_SESSION_STALE" || code === "CLOUD_CONTRACT_MISMATCH" || code === "CLOUD_CONTROL_PLANE_ERROR") {
|
|
5723
|
-
return code;
|
|
5724
|
-
}
|
|
5725
|
-
return "CLOUD_TRANSPORT_ERROR";
|
|
5726
|
-
}
|
|
5727
|
-
|
|
5728
5701
|
// src/agent/errors.ts
|
|
5729
5702
|
var OpensteerAgentError = class extends Error {
|
|
5730
5703
|
constructor(message, cause) {
|
|
@@ -7149,15 +7122,15 @@ async function executeAgentAction(page, action) {
|
|
|
7149
7122
|
return;
|
|
7150
7123
|
}
|
|
7151
7124
|
case "drag": {
|
|
7152
|
-
const
|
|
7153
|
-
if (!
|
|
7125
|
+
const path3 = normalizePath(action.path);
|
|
7126
|
+
if (!path3.length) {
|
|
7154
7127
|
throw new OpensteerAgentActionError(
|
|
7155
7128
|
"Drag action requires a non-empty path."
|
|
7156
7129
|
);
|
|
7157
7130
|
}
|
|
7158
|
-
await page.mouse.move(
|
|
7131
|
+
await page.mouse.move(path3[0].x, path3[0].y);
|
|
7159
7132
|
await page.mouse.down();
|
|
7160
|
-
for (const point of
|
|
7133
|
+
for (const point of path3.slice(1)) {
|
|
7161
7134
|
await page.mouse.move(point.x, point.y);
|
|
7162
7135
|
}
|
|
7163
7136
|
await page.mouse.up();
|
|
@@ -7253,10 +7226,10 @@ async function maybeFocusPoint(page, action) {
|
|
|
7253
7226
|
clickCount: 1
|
|
7254
7227
|
});
|
|
7255
7228
|
}
|
|
7256
|
-
function normalizePath(
|
|
7257
|
-
if (!Array.isArray(
|
|
7229
|
+
function normalizePath(path3) {
|
|
7230
|
+
if (!Array.isArray(path3)) return [];
|
|
7258
7231
|
const points = [];
|
|
7259
|
-
for (const entry of
|
|
7232
|
+
for (const entry of path3) {
|
|
7260
7233
|
if (!entry || typeof entry !== "object") continue;
|
|
7261
7234
|
const candidate = entry;
|
|
7262
7235
|
const x = Number(candidate.x);
|
|
@@ -7345,24 +7318,22 @@ var OpensteerCuaAgentHandler = class {
|
|
|
7345
7318
|
page;
|
|
7346
7319
|
config;
|
|
7347
7320
|
client;
|
|
7348
|
-
|
|
7321
|
+
cursorController;
|
|
7349
7322
|
onMutatingAction;
|
|
7350
|
-
cursorOverlayInjected = false;
|
|
7351
7323
|
constructor(options) {
|
|
7352
7324
|
this.page = options.page;
|
|
7353
7325
|
this.config = options.config;
|
|
7354
7326
|
this.client = options.client;
|
|
7355
|
-
this.
|
|
7327
|
+
this.cursorController = options.cursorController;
|
|
7356
7328
|
this.onMutatingAction = options.onMutatingAction;
|
|
7357
7329
|
}
|
|
7358
7330
|
async execute(options) {
|
|
7359
7331
|
const instruction = options.instruction;
|
|
7360
7332
|
const maxSteps = options.maxSteps ?? 20;
|
|
7361
7333
|
await this.initializeClient();
|
|
7362
|
-
const highlightCursor = options.highlightCursor === true;
|
|
7363
7334
|
this.client.setActionHandler(async (action) => {
|
|
7364
|
-
if (
|
|
7365
|
-
await this.
|
|
7335
|
+
if (this.cursorController.isEnabled()) {
|
|
7336
|
+
await this.maybePreviewCursor(action);
|
|
7366
7337
|
}
|
|
7367
7338
|
await executeAgentAction(this.page, action);
|
|
7368
7339
|
this.client.setCurrentUrl(this.page.url());
|
|
@@ -7393,6 +7364,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
7393
7364
|
const viewport = await this.resolveViewport();
|
|
7394
7365
|
this.client.setViewport(viewport.width, viewport.height);
|
|
7395
7366
|
this.client.setCurrentUrl(this.page.url());
|
|
7367
|
+
await this.cursorController.attachPage(this.page);
|
|
7396
7368
|
this.client.setScreenshotProvider(async () => {
|
|
7397
7369
|
const buffer = await this.page.screenshot({
|
|
7398
7370
|
fullPage: false,
|
|
@@ -7421,55 +7393,615 @@ var OpensteerCuaAgentHandler = class {
|
|
|
7421
7393
|
}
|
|
7422
7394
|
return DEFAULT_CUA_VIEWPORT;
|
|
7423
7395
|
}
|
|
7424
|
-
async
|
|
7396
|
+
async maybePreviewCursor(action) {
|
|
7425
7397
|
const x = typeof action.x === "number" ? action.x : null;
|
|
7426
7398
|
const y = typeof action.y === "number" ? action.y : null;
|
|
7427
7399
|
if (x == null || y == null) {
|
|
7428
7400
|
return;
|
|
7429
7401
|
}
|
|
7402
|
+
await this.cursorController.preview({ x, y }, "agent");
|
|
7403
|
+
}
|
|
7404
|
+
};
|
|
7405
|
+
function sleep3(ms) {
|
|
7406
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
// src/cursor/motion.ts
|
|
7410
|
+
var DEFAULT_SNAPPY_OPTIONS = {
|
|
7411
|
+
minDurationMs: 46,
|
|
7412
|
+
maxDurationMs: 170,
|
|
7413
|
+
maxPoints: 14
|
|
7414
|
+
};
|
|
7415
|
+
function planSnappyCursorMotion(from, to, options = {}) {
|
|
7416
|
+
const resolved = {
|
|
7417
|
+
...DEFAULT_SNAPPY_OPTIONS,
|
|
7418
|
+
...options
|
|
7419
|
+
};
|
|
7420
|
+
const dx = to.x - from.x;
|
|
7421
|
+
const dy = to.y - from.y;
|
|
7422
|
+
const distance = Math.hypot(dx, dy);
|
|
7423
|
+
if (distance < 5) {
|
|
7424
|
+
return {
|
|
7425
|
+
points: [roundPoint(to)],
|
|
7426
|
+
stepDelayMs: 0
|
|
7427
|
+
};
|
|
7428
|
+
}
|
|
7429
|
+
const durationMs = clamp(
|
|
7430
|
+
44 + distance * 0.26,
|
|
7431
|
+
resolved.minDurationMs,
|
|
7432
|
+
resolved.maxDurationMs
|
|
7433
|
+
);
|
|
7434
|
+
const rawPoints = clamp(
|
|
7435
|
+
Math.round(durationMs / 13),
|
|
7436
|
+
4,
|
|
7437
|
+
resolved.maxPoints
|
|
7438
|
+
);
|
|
7439
|
+
const sign = deterministicBendSign(from, to);
|
|
7440
|
+
const nx = -dy / distance;
|
|
7441
|
+
const ny = dx / distance;
|
|
7442
|
+
const bend = clamp(distance * 0.12, 8, 28) * sign;
|
|
7443
|
+
const c1 = {
|
|
7444
|
+
x: from.x + dx * 0.28 + nx * bend,
|
|
7445
|
+
y: from.y + dy * 0.28 + ny * bend
|
|
7446
|
+
};
|
|
7447
|
+
const c2 = {
|
|
7448
|
+
x: from.x + dx * 0.74 + nx * bend * 0.58,
|
|
7449
|
+
y: from.y + dy * 0.74 + ny * bend * 0.58
|
|
7450
|
+
};
|
|
7451
|
+
const points = [];
|
|
7452
|
+
for (let i = 1; i <= rawPoints; i += 1) {
|
|
7453
|
+
const t = i / rawPoints;
|
|
7454
|
+
const sampled = cubicBezier(from, c1, c2, to, t);
|
|
7455
|
+
if (i !== rawPoints) {
|
|
7456
|
+
const previous = points[points.length - 1];
|
|
7457
|
+
if (previous && distanceBetween(previous, sampled) < 1.4) {
|
|
7458
|
+
continue;
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
points.push(roundPoint(sampled));
|
|
7462
|
+
}
|
|
7463
|
+
if (distance > 220) {
|
|
7464
|
+
const settle = {
|
|
7465
|
+
x: to.x - dx / distance,
|
|
7466
|
+
y: to.y - dy / distance
|
|
7467
|
+
};
|
|
7468
|
+
const last = points[points.length - 1];
|
|
7469
|
+
if (!last || distanceBetween(last, settle) > 1.2) {
|
|
7470
|
+
points.splice(Math.max(0, points.length - 1), 0, roundPoint(settle));
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
const deduped = dedupeAdjacent(points);
|
|
7474
|
+
const stepDelayMs = deduped.length > 1 ? Math.round(durationMs / deduped.length) : 0;
|
|
7475
|
+
return {
|
|
7476
|
+
points: deduped.length ? deduped : [roundPoint(to)],
|
|
7477
|
+
stepDelayMs
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7480
|
+
function cubicBezier(p0, p1, p2, p3, t) {
|
|
7481
|
+
const inv = 1 - t;
|
|
7482
|
+
const inv2 = inv * inv;
|
|
7483
|
+
const inv3 = inv2 * inv;
|
|
7484
|
+
const t2 = t * t;
|
|
7485
|
+
const t3 = t2 * t;
|
|
7486
|
+
return {
|
|
7487
|
+
x: inv3 * p0.x + 3 * inv2 * t * p1.x + 3 * inv * t2 * p2.x + t3 * p3.x,
|
|
7488
|
+
y: inv3 * p0.y + 3 * inv2 * t * p1.y + 3 * inv * t2 * p2.y + t3 * p3.y
|
|
7489
|
+
};
|
|
7490
|
+
}
|
|
7491
|
+
function deterministicBendSign(from, to) {
|
|
7492
|
+
const seed = Math.sin(
|
|
7493
|
+
from.x * 12.9898 + from.y * 78.233 + to.x * 37.719 + to.y * 19.113
|
|
7494
|
+
) * 43758.5453;
|
|
7495
|
+
const fractional = seed - Math.floor(seed);
|
|
7496
|
+
return fractional >= 0.5 ? 1 : -1;
|
|
7497
|
+
}
|
|
7498
|
+
function roundPoint(point) {
|
|
7499
|
+
return {
|
|
7500
|
+
x: Math.round(point.x * 100) / 100,
|
|
7501
|
+
y: Math.round(point.y * 100) / 100
|
|
7502
|
+
};
|
|
7503
|
+
}
|
|
7504
|
+
function distanceBetween(a, b) {
|
|
7505
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
7506
|
+
}
|
|
7507
|
+
function dedupeAdjacent(points) {
|
|
7508
|
+
const out = [];
|
|
7509
|
+
for (const point of points) {
|
|
7510
|
+
const last = out[out.length - 1];
|
|
7511
|
+
if (last && last.x === point.x && last.y === point.y) {
|
|
7512
|
+
continue;
|
|
7513
|
+
}
|
|
7514
|
+
out.push(point);
|
|
7515
|
+
}
|
|
7516
|
+
return out;
|
|
7517
|
+
}
|
|
7518
|
+
function clamp(value, min, max) {
|
|
7519
|
+
return Math.min(max, Math.max(min, value));
|
|
7520
|
+
}
|
|
7521
|
+
|
|
7522
|
+
// src/cursor/renderers/svg-overlay.ts
|
|
7523
|
+
var PULSE_DURATION_MS = 220;
|
|
7524
|
+
var HOST_ELEMENT_ID = "__os_cr";
|
|
7525
|
+
var SvgCursorRenderer = class {
|
|
7526
|
+
page = null;
|
|
7527
|
+
active = false;
|
|
7528
|
+
reason = "disabled";
|
|
7529
|
+
lastMessage;
|
|
7530
|
+
async initialize(page) {
|
|
7531
|
+
this.page = page;
|
|
7532
|
+
if (page.isClosed()) {
|
|
7533
|
+
this.markInactive("page_closed");
|
|
7534
|
+
return;
|
|
7535
|
+
}
|
|
7536
|
+
try {
|
|
7537
|
+
await page.evaluate(injectCursor, HOST_ELEMENT_ID);
|
|
7538
|
+
this.active = true;
|
|
7539
|
+
this.reason = void 0;
|
|
7540
|
+
this.lastMessage = void 0;
|
|
7541
|
+
} catch (error) {
|
|
7542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7543
|
+
this.markInactive("renderer_error", message);
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7546
|
+
isActive() {
|
|
7547
|
+
return this.active;
|
|
7548
|
+
}
|
|
7549
|
+
status() {
|
|
7550
|
+
return {
|
|
7551
|
+
enabled: true,
|
|
7552
|
+
active: this.active,
|
|
7553
|
+
reason: this.reason ? this.lastMessage ? `${this.reason}: ${this.lastMessage}` : this.reason : void 0
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
async move(point, style) {
|
|
7557
|
+
if (!this.active || !this.page || this.page.isClosed()) return;
|
|
7430
7558
|
try {
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7559
|
+
const ok = await this.page.evaluate(moveCursor, {
|
|
7560
|
+
id: HOST_ELEMENT_ID,
|
|
7561
|
+
x: point.x,
|
|
7562
|
+
y: point.y,
|
|
7563
|
+
size: style.size,
|
|
7564
|
+
fill: colorToRgba(style.fillColor),
|
|
7565
|
+
outline: colorToRgba(style.outlineColor)
|
|
7566
|
+
});
|
|
7567
|
+
if (!ok) {
|
|
7568
|
+
await this.reinject();
|
|
7569
|
+
await this.page.evaluate(moveCursor, {
|
|
7570
|
+
id: HOST_ELEMENT_ID,
|
|
7571
|
+
x: point.x,
|
|
7572
|
+
y: point.y,
|
|
7573
|
+
size: style.size,
|
|
7574
|
+
fill: colorToRgba(style.fillColor),
|
|
7575
|
+
outline: colorToRgba(style.outlineColor)
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
} catch (error) {
|
|
7579
|
+
this.handleError(error);
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
async pulse(point, style) {
|
|
7583
|
+
if (!this.active || !this.page || this.page.isClosed()) return;
|
|
7584
|
+
try {
|
|
7585
|
+
const ok = await this.page.evaluate(pulseCursor, {
|
|
7586
|
+
id: HOST_ELEMENT_ID,
|
|
7587
|
+
x: point.x,
|
|
7588
|
+
y: point.y,
|
|
7589
|
+
size: style.size,
|
|
7590
|
+
fill: colorToRgba(style.fillColor),
|
|
7591
|
+
outline: colorToRgba(style.outlineColor),
|
|
7592
|
+
halo: colorToRgba(style.haloColor),
|
|
7593
|
+
pulseMs: PULSE_DURATION_MS
|
|
7594
|
+
});
|
|
7595
|
+
if (!ok) {
|
|
7596
|
+
await this.reinject();
|
|
7597
|
+
await this.page.evaluate(pulseCursor, {
|
|
7598
|
+
id: HOST_ELEMENT_ID,
|
|
7599
|
+
x: point.x,
|
|
7600
|
+
y: point.y,
|
|
7601
|
+
size: style.size,
|
|
7602
|
+
fill: colorToRgba(style.fillColor),
|
|
7603
|
+
outline: colorToRgba(style.outlineColor),
|
|
7604
|
+
halo: colorToRgba(style.haloColor),
|
|
7605
|
+
pulseMs: PULSE_DURATION_MS
|
|
7448
7606
|
});
|
|
7449
|
-
this.cursorOverlayInjected = true;
|
|
7450
7607
|
}
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
);
|
|
7608
|
+
} catch (error) {
|
|
7609
|
+
this.handleError(error);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
async clear() {
|
|
7613
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7614
|
+
try {
|
|
7615
|
+
await this.page.evaluate(removeCursor, HOST_ELEMENT_ID);
|
|
7616
|
+
} catch {
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7619
|
+
async dispose() {
|
|
7620
|
+
if (this.page && !this.page.isClosed()) {
|
|
7621
|
+
try {
|
|
7622
|
+
await this.page.evaluate(removeCursor, HOST_ELEMENT_ID);
|
|
7623
|
+
} catch {
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
this.active = false;
|
|
7627
|
+
this.reason = "disabled";
|
|
7628
|
+
this.lastMessage = void 0;
|
|
7629
|
+
this.page = null;
|
|
7630
|
+
}
|
|
7631
|
+
async reinject() {
|
|
7632
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7633
|
+
try {
|
|
7634
|
+
await this.page.evaluate(injectCursor, HOST_ELEMENT_ID);
|
|
7635
|
+
} catch {
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
markInactive(reason, message) {
|
|
7639
|
+
this.active = false;
|
|
7640
|
+
this.reason = reason;
|
|
7641
|
+
this.lastMessage = message;
|
|
7642
|
+
}
|
|
7643
|
+
handleError(error) {
|
|
7644
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7645
|
+
if (isPageGone(message)) {
|
|
7646
|
+
this.markInactive("page_closed", message);
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7649
|
+
};
|
|
7650
|
+
function injectCursor(hostId) {
|
|
7651
|
+
const win = window;
|
|
7652
|
+
if (win[hostId]) return;
|
|
7653
|
+
const host = document.createElement("div");
|
|
7654
|
+
host.style.cssText = "position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;";
|
|
7655
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
7656
|
+
const wrapper = document.createElement("div");
|
|
7657
|
+
wrapper.style.cssText = "position:fixed;top:0;left:0;pointer-events:none;will-change:transform;display:none;";
|
|
7658
|
+
wrapper.innerHTML = `
|
|
7659
|
+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 1px 2px rgba(0,0,0,0.3));display:block;">
|
|
7660
|
+
<path d="M3 2L3 23L8.5 17.5L13 26L17 24L12.5 15.5L20 15.5L3 2Z"
|
|
7661
|
+
fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
|
|
7662
|
+
</svg>
|
|
7663
|
+
<div data-role="pulse" style="position:absolute;top:0;left:0;width:24px;height:24px;border-radius:50%;pointer-events:none;opacity:0;transform:translate(-8px,-8px);"></div>
|
|
7664
|
+
`;
|
|
7665
|
+
shadow.appendChild(wrapper);
|
|
7666
|
+
document.documentElement.appendChild(host);
|
|
7667
|
+
Object.defineProperty(window, hostId, {
|
|
7668
|
+
value: {
|
|
7669
|
+
host,
|
|
7670
|
+
wrapper,
|
|
7671
|
+
path: wrapper.querySelector("path"),
|
|
7672
|
+
pulse: wrapper.querySelector('[data-role="pulse"]')
|
|
7673
|
+
},
|
|
7674
|
+
configurable: true,
|
|
7675
|
+
enumerable: false
|
|
7676
|
+
});
|
|
7677
|
+
}
|
|
7678
|
+
function moveCursor(args) {
|
|
7679
|
+
const refs = window[args.id];
|
|
7680
|
+
if (!refs) return false;
|
|
7681
|
+
const scale = args.size / 20;
|
|
7682
|
+
refs.wrapper.style.transform = `translate(${args.x}px, ${args.y}px) scale(${scale})`;
|
|
7683
|
+
refs.wrapper.style.display = "block";
|
|
7684
|
+
if (refs.path) {
|
|
7685
|
+
refs.path.setAttribute("fill", args.fill);
|
|
7686
|
+
refs.path.setAttribute("stroke", args.outline);
|
|
7687
|
+
}
|
|
7688
|
+
return true;
|
|
7689
|
+
}
|
|
7690
|
+
function pulseCursor(args) {
|
|
7691
|
+
const refs = window[args.id];
|
|
7692
|
+
if (!refs) return false;
|
|
7693
|
+
const scale = args.size / 20;
|
|
7694
|
+
refs.wrapper.style.transform = `translate(${args.x}px, ${args.y}px) scale(${scale})`;
|
|
7695
|
+
refs.wrapper.style.display = "block";
|
|
7696
|
+
if (refs.path) {
|
|
7697
|
+
refs.path.setAttribute("fill", args.fill);
|
|
7698
|
+
refs.path.setAttribute("stroke", args.outline);
|
|
7699
|
+
}
|
|
7700
|
+
const ring = refs.pulse;
|
|
7701
|
+
if (!ring) return true;
|
|
7702
|
+
ring.style.background = args.halo;
|
|
7703
|
+
ring.style.opacity = "0.7";
|
|
7704
|
+
ring.style.width = "24px";
|
|
7705
|
+
ring.style.height = "24px";
|
|
7706
|
+
ring.style.transition = `all ${args.pulseMs}ms ease-out`;
|
|
7707
|
+
ring.offsetHeight;
|
|
7708
|
+
ring.style.width = "48px";
|
|
7709
|
+
ring.style.height = "48px";
|
|
7710
|
+
ring.style.opacity = "0";
|
|
7711
|
+
ring.style.transform = "translate(-20px, -20px)";
|
|
7712
|
+
setTimeout(() => {
|
|
7713
|
+
ring.style.transition = "none";
|
|
7714
|
+
ring.style.width = "24px";
|
|
7715
|
+
ring.style.height = "24px";
|
|
7716
|
+
ring.style.transform = "translate(-8px, -8px)";
|
|
7717
|
+
ring.style.opacity = "0";
|
|
7718
|
+
}, args.pulseMs);
|
|
7719
|
+
return true;
|
|
7720
|
+
}
|
|
7721
|
+
function removeCursor(hostId) {
|
|
7722
|
+
const refs = window[hostId];
|
|
7723
|
+
if (refs) {
|
|
7724
|
+
refs.host.remove();
|
|
7725
|
+
delete window[hostId];
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
function colorToRgba(c) {
|
|
7729
|
+
return `rgba(${Math.round(c.r)},${Math.round(c.g)},${Math.round(c.b)},${c.a})`;
|
|
7730
|
+
}
|
|
7731
|
+
function isPageGone(message) {
|
|
7732
|
+
const m = message.toLowerCase();
|
|
7733
|
+
return m.includes("closed") || m.includes("detached") || m.includes("destroyed") || m.includes("target");
|
|
7734
|
+
}
|
|
7735
|
+
|
|
7736
|
+
// src/cursor/controller.ts
|
|
7737
|
+
var DEFAULT_STYLE = {
|
|
7738
|
+
size: 20,
|
|
7739
|
+
fillColor: {
|
|
7740
|
+
r: 255,
|
|
7741
|
+
g: 255,
|
|
7742
|
+
b: 255,
|
|
7743
|
+
a: 0.96
|
|
7744
|
+
},
|
|
7745
|
+
outlineColor: {
|
|
7746
|
+
r: 0,
|
|
7747
|
+
g: 0,
|
|
7748
|
+
b: 0,
|
|
7749
|
+
a: 1
|
|
7750
|
+
},
|
|
7751
|
+
haloColor: {
|
|
7752
|
+
r: 35,
|
|
7753
|
+
g: 162,
|
|
7754
|
+
b: 255,
|
|
7755
|
+
a: 0.38
|
|
7756
|
+
},
|
|
7757
|
+
pulseScale: 2.15
|
|
7758
|
+
};
|
|
7759
|
+
var REINITIALIZE_BACKOFF_MS = 1e3;
|
|
7760
|
+
var FIRST_MOVE_CENTER_DISTANCE_THRESHOLD = 16;
|
|
7761
|
+
var FIRST_MOVE_MAX_TRAVEL = 220;
|
|
7762
|
+
var FIRST_MOVE_NEAR_TARGET_X_OFFSET = 28;
|
|
7763
|
+
var FIRST_MOVE_NEAR_TARGET_Y_OFFSET = 18;
|
|
7764
|
+
var MOTION_PLANNERS = {
|
|
7765
|
+
snappy: planSnappyCursorMotion
|
|
7766
|
+
};
|
|
7767
|
+
var CursorController = class {
|
|
7768
|
+
debug;
|
|
7769
|
+
renderer;
|
|
7770
|
+
page = null;
|
|
7771
|
+
listenerPage = null;
|
|
7772
|
+
lastPoint = null;
|
|
7773
|
+
initializedForPage = false;
|
|
7774
|
+
lastInitializeAttemptAt = 0;
|
|
7775
|
+
enabled;
|
|
7776
|
+
profile;
|
|
7777
|
+
style;
|
|
7778
|
+
onDomContentLoaded = () => {
|
|
7779
|
+
void this.restoreCursorAfterNavigation();
|
|
7780
|
+
};
|
|
7781
|
+
constructor(options = {}) {
|
|
7782
|
+
const config = options.config || {};
|
|
7783
|
+
this.debug = Boolean(options.debug);
|
|
7784
|
+
this.enabled = config.enabled === true;
|
|
7785
|
+
this.profile = config.profile ?? "snappy";
|
|
7786
|
+
this.style = mergeStyle(config.style);
|
|
7787
|
+
this.renderer = options.renderer ?? new SvgCursorRenderer();
|
|
7788
|
+
}
|
|
7789
|
+
setEnabled(enabled) {
|
|
7790
|
+
if (this.enabled && !enabled) {
|
|
7791
|
+
this.lastPoint = null;
|
|
7792
|
+
void this.clear();
|
|
7793
|
+
}
|
|
7794
|
+
this.enabled = enabled;
|
|
7795
|
+
}
|
|
7796
|
+
isEnabled() {
|
|
7797
|
+
return this.enabled;
|
|
7798
|
+
}
|
|
7799
|
+
getStatus() {
|
|
7800
|
+
if (!this.enabled) {
|
|
7801
|
+
return {
|
|
7802
|
+
enabled: false,
|
|
7803
|
+
active: false,
|
|
7804
|
+
reason: "disabled"
|
|
7805
|
+
};
|
|
7806
|
+
}
|
|
7807
|
+
const status = this.renderer.status();
|
|
7808
|
+
if (!this.initializedForPage && !status.active) {
|
|
7809
|
+
return {
|
|
7810
|
+
enabled: true,
|
|
7811
|
+
active: false,
|
|
7812
|
+
reason: "not_initialized"
|
|
7813
|
+
};
|
|
7814
|
+
}
|
|
7815
|
+
return status;
|
|
7816
|
+
}
|
|
7817
|
+
async attachPage(page) {
|
|
7818
|
+
if (this.page !== page) {
|
|
7819
|
+
this.detachPageListeners();
|
|
7820
|
+
this.page = page;
|
|
7821
|
+
this.lastPoint = null;
|
|
7822
|
+
this.initializedForPage = false;
|
|
7823
|
+
this.lastInitializeAttemptAt = 0;
|
|
7824
|
+
}
|
|
7825
|
+
this.attachPageListeners(page);
|
|
7826
|
+
}
|
|
7827
|
+
async preview(point, intent) {
|
|
7828
|
+
if (!this.enabled || !point) return;
|
|
7829
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7830
|
+
try {
|
|
7831
|
+
await this.ensureInitialized();
|
|
7832
|
+
if (!this.renderer.isActive()) {
|
|
7833
|
+
await this.reinitializeIfEligible();
|
|
7834
|
+
}
|
|
7835
|
+
if (!this.renderer.isActive()) return;
|
|
7836
|
+
const start = this.resolveMotionStart(point);
|
|
7837
|
+
const motion = this.planMotion(start, point);
|
|
7838
|
+
for (const step of motion.points) {
|
|
7839
|
+
await this.renderer.move(step, this.style);
|
|
7840
|
+
if (motion.stepDelayMs > 0) {
|
|
7841
|
+
await sleep4(motion.stepDelayMs);
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
if (shouldPulse(intent)) {
|
|
7845
|
+
await this.renderer.pulse(point, this.style);
|
|
7846
|
+
}
|
|
7847
|
+
this.lastPoint = point;
|
|
7459
7848
|
} catch (error) {
|
|
7460
7849
|
if (this.debug) {
|
|
7461
7850
|
const message = error instanceof Error ? error.message : String(error);
|
|
7462
|
-
console.warn(`[opensteer] cursor
|
|
7851
|
+
console.warn(`[opensteer] cursor preview failed: ${message}`);
|
|
7463
7852
|
}
|
|
7464
7853
|
}
|
|
7465
7854
|
}
|
|
7855
|
+
async clear() {
|
|
7856
|
+
try {
|
|
7857
|
+
await this.renderer.clear();
|
|
7858
|
+
} catch (error) {
|
|
7859
|
+
if (this.debug) {
|
|
7860
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7861
|
+
console.warn(`[opensteer] cursor clear failed: ${message}`);
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
async dispose() {
|
|
7866
|
+
this.detachPageListeners();
|
|
7867
|
+
this.lastPoint = null;
|
|
7868
|
+
this.initializedForPage = false;
|
|
7869
|
+
this.lastInitializeAttemptAt = 0;
|
|
7870
|
+
this.page = null;
|
|
7871
|
+
await this.renderer.dispose();
|
|
7872
|
+
}
|
|
7873
|
+
async ensureInitialized() {
|
|
7874
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7875
|
+
if (this.initializedForPage) return;
|
|
7876
|
+
await this.initializeRenderer();
|
|
7877
|
+
}
|
|
7878
|
+
attachPageListeners(page) {
|
|
7879
|
+
if (this.listenerPage === page) {
|
|
7880
|
+
return;
|
|
7881
|
+
}
|
|
7882
|
+
this.detachPageListeners();
|
|
7883
|
+
page.on("domcontentloaded", this.onDomContentLoaded);
|
|
7884
|
+
this.listenerPage = page;
|
|
7885
|
+
}
|
|
7886
|
+
detachPageListeners() {
|
|
7887
|
+
if (!this.listenerPage) {
|
|
7888
|
+
return;
|
|
7889
|
+
}
|
|
7890
|
+
this.listenerPage.off("domcontentloaded", this.onDomContentLoaded);
|
|
7891
|
+
this.listenerPage = null;
|
|
7892
|
+
}
|
|
7893
|
+
planMotion(from, to) {
|
|
7894
|
+
return MOTION_PLANNERS[this.profile](from, to);
|
|
7895
|
+
}
|
|
7896
|
+
async reinitializeIfEligible() {
|
|
7897
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7898
|
+
const elapsed = Date.now() - this.lastInitializeAttemptAt;
|
|
7899
|
+
if (elapsed < REINITIALIZE_BACKOFF_MS) return;
|
|
7900
|
+
await this.initializeRenderer();
|
|
7901
|
+
}
|
|
7902
|
+
async initializeRenderer() {
|
|
7903
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7904
|
+
this.lastInitializeAttemptAt = Date.now();
|
|
7905
|
+
await this.renderer.initialize(this.page);
|
|
7906
|
+
this.initializedForPage = true;
|
|
7907
|
+
}
|
|
7908
|
+
async restoreCursorAfterNavigation() {
|
|
7909
|
+
if (!this.enabled || !this.lastPoint) return;
|
|
7910
|
+
if (!this.page || this.page.isClosed()) return;
|
|
7911
|
+
try {
|
|
7912
|
+
if (!this.renderer.isActive()) {
|
|
7913
|
+
await this.reinitializeIfEligible();
|
|
7914
|
+
}
|
|
7915
|
+
if (!this.renderer.isActive()) {
|
|
7916
|
+
return;
|
|
7917
|
+
}
|
|
7918
|
+
await this.renderer.move(this.lastPoint, this.style);
|
|
7919
|
+
} catch (error) {
|
|
7920
|
+
if (this.debug) {
|
|
7921
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7922
|
+
console.warn(
|
|
7923
|
+
`[opensteer] cursor restore after navigation failed: ${message}`
|
|
7924
|
+
);
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
resolveMotionStart(target) {
|
|
7929
|
+
if (this.lastPoint) {
|
|
7930
|
+
return this.lastPoint;
|
|
7931
|
+
}
|
|
7932
|
+
const viewport = this.page?.viewportSize();
|
|
7933
|
+
if (!viewport?.width || !viewport?.height) {
|
|
7934
|
+
return target;
|
|
7935
|
+
}
|
|
7936
|
+
const centerPoint = {
|
|
7937
|
+
x: viewport.width / 2,
|
|
7938
|
+
y: viewport.height / 2
|
|
7939
|
+
};
|
|
7940
|
+
if (distanceBetween2(centerPoint, target) > FIRST_MOVE_CENTER_DISTANCE_THRESHOLD) {
|
|
7941
|
+
const dx = target.x - centerPoint.x;
|
|
7942
|
+
const dy = target.y - centerPoint.y;
|
|
7943
|
+
const distance = Math.hypot(dx, dy);
|
|
7944
|
+
if (distance > FIRST_MOVE_MAX_TRAVEL) {
|
|
7945
|
+
const ux = dx / distance;
|
|
7946
|
+
const uy = dy / distance;
|
|
7947
|
+
return {
|
|
7948
|
+
x: target.x - ux * FIRST_MOVE_MAX_TRAVEL,
|
|
7949
|
+
y: target.y - uy * FIRST_MOVE_MAX_TRAVEL
|
|
7950
|
+
};
|
|
7951
|
+
}
|
|
7952
|
+
return centerPoint;
|
|
7953
|
+
}
|
|
7954
|
+
return {
|
|
7955
|
+
x: clamp2(target.x - FIRST_MOVE_NEAR_TARGET_X_OFFSET, 0, viewport.width),
|
|
7956
|
+
y: clamp2(target.y - FIRST_MOVE_NEAR_TARGET_Y_OFFSET, 0, viewport.height)
|
|
7957
|
+
};
|
|
7958
|
+
}
|
|
7466
7959
|
};
|
|
7467
|
-
function
|
|
7468
|
-
return
|
|
7960
|
+
function mergeStyle(style) {
|
|
7961
|
+
return {
|
|
7962
|
+
size: normalizeFinite(style?.size, DEFAULT_STYLE.size, 4, 48),
|
|
7963
|
+
pulseScale: normalizeFinite(
|
|
7964
|
+
style?.pulseScale,
|
|
7965
|
+
DEFAULT_STYLE.pulseScale,
|
|
7966
|
+
1,
|
|
7967
|
+
3
|
|
7968
|
+
),
|
|
7969
|
+
fillColor: normalizeColor(style?.fillColor, DEFAULT_STYLE.fillColor),
|
|
7970
|
+
outlineColor: normalizeColor(
|
|
7971
|
+
style?.outlineColor,
|
|
7972
|
+
DEFAULT_STYLE.outlineColor
|
|
7973
|
+
),
|
|
7974
|
+
haloColor: normalizeColor(style?.haloColor, DEFAULT_STYLE.haloColor)
|
|
7975
|
+
};
|
|
7976
|
+
}
|
|
7977
|
+
function normalizeColor(color, fallback) {
|
|
7978
|
+
if (!color) return { ...fallback };
|
|
7979
|
+
return {
|
|
7980
|
+
r: normalizeFinite(color.r, fallback.r, 0, 255),
|
|
7981
|
+
g: normalizeFinite(color.g, fallback.g, 0, 255),
|
|
7982
|
+
b: normalizeFinite(color.b, fallback.b, 0, 255),
|
|
7983
|
+
a: normalizeFinite(color.a, fallback.a, 0, 1)
|
|
7984
|
+
};
|
|
7985
|
+
}
|
|
7986
|
+
function normalizeFinite(value, fallback, min, max) {
|
|
7987
|
+
const numeric = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
7988
|
+
return Math.min(max, Math.max(min, numeric));
|
|
7989
|
+
}
|
|
7990
|
+
function distanceBetween2(a, b) {
|
|
7991
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
7992
|
+
}
|
|
7993
|
+
function clamp2(value, min, max) {
|
|
7994
|
+
return Math.min(max, Math.max(min, value));
|
|
7995
|
+
}
|
|
7996
|
+
function shouldPulse(intent) {
|
|
7997
|
+
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
7998
|
+
}
|
|
7999
|
+
function sleep4(ms) {
|
|
8000
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7469
8001
|
}
|
|
7470
8002
|
|
|
7471
8003
|
// src/opensteer.ts
|
|
7472
|
-
import { createHash, randomUUID } from "crypto";
|
|
8004
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
7473
8005
|
|
|
7474
8006
|
// src/browser/pool.ts
|
|
7475
8007
|
import {
|
|
@@ -7813,19 +8345,10 @@ function errorMessage(error) {
|
|
|
7813
8345
|
return error instanceof Error ? error.message : String(error);
|
|
7814
8346
|
}
|
|
7815
8347
|
|
|
7816
|
-
// src/browser/chrome.ts
|
|
7817
|
-
import { homedir, platform } from "os";
|
|
7818
|
-
import { join } from "path";
|
|
7819
|
-
function expandHome(p) {
|
|
7820
|
-
if (p.startsWith("~/") || p === "~") {
|
|
7821
|
-
return join(homedir(), p.slice(1));
|
|
7822
|
-
}
|
|
7823
|
-
return p;
|
|
7824
|
-
}
|
|
7825
|
-
|
|
7826
8348
|
// src/browser/pool.ts
|
|
7827
8349
|
var BrowserPool = class {
|
|
7828
8350
|
browser = null;
|
|
8351
|
+
persistentContext = null;
|
|
7829
8352
|
cdpProxy = null;
|
|
7830
8353
|
defaults;
|
|
7831
8354
|
constructor(defaults = {}) {
|
|
@@ -7841,16 +8364,23 @@ var BrowserPool = class {
|
|
|
7841
8364
|
if (connectUrl) {
|
|
7842
8365
|
return this.connectToRunning(connectUrl, options.timeout);
|
|
7843
8366
|
}
|
|
7844
|
-
if (
|
|
7845
|
-
return this.
|
|
8367
|
+
if (profileDir) {
|
|
8368
|
+
return this.launchPersistentProfile(options, channel, profileDir);
|
|
8369
|
+
}
|
|
8370
|
+
if (channel) {
|
|
8371
|
+
return this.launchWithChannel(options, channel);
|
|
7846
8372
|
}
|
|
7847
8373
|
return this.launchSandbox(options);
|
|
7848
8374
|
}
|
|
7849
8375
|
async close() {
|
|
7850
8376
|
const browser = this.browser;
|
|
8377
|
+
const persistentContext = this.persistentContext;
|
|
7851
8378
|
this.browser = null;
|
|
8379
|
+
this.persistentContext = null;
|
|
7852
8380
|
try {
|
|
7853
|
-
if (
|
|
8381
|
+
if (persistentContext) {
|
|
8382
|
+
await persistentContext.close();
|
|
8383
|
+
} else if (browser) {
|
|
7854
8384
|
await browser.close();
|
|
7855
8385
|
}
|
|
7856
8386
|
} finally {
|
|
@@ -7876,6 +8406,7 @@ var BrowserPool = class {
|
|
|
7876
8406
|
timeout: timeout ?? 3e4
|
|
7877
8407
|
});
|
|
7878
8408
|
this.browser = browser;
|
|
8409
|
+
this.persistentContext = null;
|
|
7879
8410
|
const contexts = browser.contexts();
|
|
7880
8411
|
if (contexts.length === 0) {
|
|
7881
8412
|
throw new Error(
|
|
@@ -7891,442 +8422,69 @@ var BrowserPool = class {
|
|
|
7891
8422
|
await browser.close().catch(() => void 0);
|
|
7892
8423
|
}
|
|
7893
8424
|
this.browser = null;
|
|
7894
|
-
this.
|
|
7895
|
-
this.cdpProxy
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
async launchWithProfile(options, channel, profileDir) {
|
|
7900
|
-
const args = [];
|
|
7901
|
-
if (profileDir) {
|
|
7902
|
-
args.push(`--user-data-dir=${expandHome(profileDir)}`);
|
|
7903
|
-
}
|
|
7904
|
-
const browser = await chromium2.launch({
|
|
7905
|
-
channel,
|
|
7906
|
-
headless: options.headless ?? this.defaults.headless,
|
|
7907
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
7908
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
7909
|
-
args
|
|
7910
|
-
});
|
|
7911
|
-
this.browser = browser;
|
|
7912
|
-
const contexts = browser.contexts();
|
|
7913
|
-
let context;
|
|
7914
|
-
let page;
|
|
7915
|
-
if (contexts.length > 0) {
|
|
7916
|
-
context = contexts[0];
|
|
7917
|
-
const pages = context.pages();
|
|
7918
|
-
page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
7919
|
-
} else {
|
|
7920
|
-
context = await browser.newContext(options.context || {});
|
|
7921
|
-
page = await context.newPage();
|
|
7922
|
-
}
|
|
7923
|
-
return { browser, context, page, isExternal: false };
|
|
7924
|
-
}
|
|
7925
|
-
async launchSandbox(options) {
|
|
7926
|
-
const browser = await chromium2.launch({
|
|
7927
|
-
headless: options.headless ?? this.defaults.headless,
|
|
7928
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
7929
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0
|
|
7930
|
-
});
|
|
7931
|
-
const context = await browser.newContext(options.context || {});
|
|
7932
|
-
const page = await context.newPage();
|
|
7933
|
-
this.browser = browser;
|
|
7934
|
-
return { browser, context, page, isExternal: false };
|
|
7935
|
-
}
|
|
7936
|
-
};
|
|
7937
|
-
|
|
7938
|
-
// src/config.ts
|
|
7939
|
-
import fs3 from "fs";
|
|
7940
|
-
import path4 from "path";
|
|
7941
|
-
import { fileURLToPath } from "url";
|
|
7942
|
-
import { parse as parseDotenv } from "dotenv";
|
|
7943
|
-
var DEFAULT_CONFIG = {
|
|
7944
|
-
browser: {
|
|
7945
|
-
headless: false,
|
|
7946
|
-
executablePath: void 0,
|
|
7947
|
-
slowMo: 0,
|
|
7948
|
-
connectUrl: void 0,
|
|
7949
|
-
channel: void 0,
|
|
7950
|
-
profileDir: void 0
|
|
7951
|
-
},
|
|
7952
|
-
storage: {
|
|
7953
|
-
rootDir: process.cwd()
|
|
7954
|
-
},
|
|
7955
|
-
model: "gpt-5.1",
|
|
7956
|
-
debug: false
|
|
7957
|
-
};
|
|
7958
|
-
function dotenvFileOrder(nodeEnv) {
|
|
7959
|
-
const normalized = nodeEnv?.trim() || "";
|
|
7960
|
-
const files = [];
|
|
7961
|
-
if (normalized) {
|
|
7962
|
-
files.push(`.env.${normalized}.local`);
|
|
7963
|
-
}
|
|
7964
|
-
if (normalized !== "test") {
|
|
7965
|
-
files.push(".env.local");
|
|
7966
|
-
}
|
|
7967
|
-
if (normalized) {
|
|
7968
|
-
files.push(`.env.${normalized}`);
|
|
7969
|
-
}
|
|
7970
|
-
files.push(".env");
|
|
7971
|
-
return files;
|
|
7972
|
-
}
|
|
7973
|
-
function loadDotenvValues(rootDir, baseEnv, options = {}) {
|
|
7974
|
-
const values = {};
|
|
7975
|
-
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
7976
|
-
return values;
|
|
7977
|
-
}
|
|
7978
|
-
const debug = options.debug ?? parseBool(baseEnv.OPENSTEER_DEBUG) === true;
|
|
7979
|
-
const baseDir = path4.resolve(rootDir);
|
|
7980
|
-
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
7981
|
-
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
7982
|
-
const filePath = path4.join(baseDir, filename);
|
|
7983
|
-
if (!fs3.existsSync(filePath)) continue;
|
|
7984
|
-
try {
|
|
7985
|
-
const raw = fs3.readFileSync(filePath, "utf8");
|
|
7986
|
-
const parsed = parseDotenv(raw);
|
|
7987
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
7988
|
-
if (values[key] === void 0) {
|
|
7989
|
-
values[key] = value;
|
|
7990
|
-
}
|
|
7991
|
-
}
|
|
7992
|
-
} catch (error) {
|
|
7993
|
-
const message = extractErrorMessage(
|
|
7994
|
-
error,
|
|
7995
|
-
"Unable to read or parse dotenv file."
|
|
7996
|
-
);
|
|
7997
|
-
if (debug) {
|
|
7998
|
-
console.warn(
|
|
7999
|
-
`[opensteer] failed to load dotenv file "${filePath}": ${message}`
|
|
8000
|
-
);
|
|
8001
|
-
}
|
|
8002
|
-
continue;
|
|
8003
|
-
}
|
|
8004
|
-
}
|
|
8005
|
-
return values;
|
|
8006
|
-
}
|
|
8007
|
-
function resolveEnv(rootDir, options = {}) {
|
|
8008
|
-
const baseEnv = process.env;
|
|
8009
|
-
const dotenvValues = loadDotenvValues(rootDir, baseEnv, options);
|
|
8010
|
-
return {
|
|
8011
|
-
...dotenvValues,
|
|
8012
|
-
...baseEnv
|
|
8013
|
-
};
|
|
8014
|
-
}
|
|
8015
|
-
function hasOwn(config, key) {
|
|
8016
|
-
if (!config || typeof config !== "object") return false;
|
|
8017
|
-
return Object.prototype.hasOwnProperty.call(config, key);
|
|
8018
|
-
}
|
|
8019
|
-
function hasLegacyAiConfig(config) {
|
|
8020
|
-
return hasOwn(config, "ai");
|
|
8021
|
-
}
|
|
8022
|
-
function assertNoLegacyAiConfig(source, config) {
|
|
8023
|
-
if (hasLegacyAiConfig(config)) {
|
|
8024
|
-
throw new Error(
|
|
8025
|
-
`Legacy "ai" config is no longer supported in ${source}. Use top-level "model" instead.`
|
|
8026
|
-
);
|
|
8027
|
-
}
|
|
8028
|
-
}
|
|
8029
|
-
function assertNoLegacyRuntimeConfig(source, config) {
|
|
8030
|
-
if (!config || typeof config !== "object") return;
|
|
8031
|
-
const configRecord = config;
|
|
8032
|
-
if (hasOwn(configRecord, "runtime")) {
|
|
8033
|
-
throw new Error(
|
|
8034
|
-
`Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
|
|
8035
|
-
);
|
|
8036
|
-
}
|
|
8037
|
-
if (hasOwn(configRecord, "mode")) {
|
|
8038
|
-
throw new Error(
|
|
8039
|
-
`Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
|
|
8040
|
-
);
|
|
8041
|
-
}
|
|
8042
|
-
if (hasOwn(configRecord, "remote")) {
|
|
8043
|
-
throw new Error(
|
|
8044
|
-
`Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
|
|
8045
|
-
);
|
|
8046
|
-
}
|
|
8047
|
-
if (hasOwn(configRecord, "apiKey")) {
|
|
8048
|
-
throw new Error(
|
|
8049
|
-
`Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
|
|
8050
|
-
);
|
|
8051
|
-
}
|
|
8052
|
-
}
|
|
8053
|
-
function loadConfigFile(rootDir, options = {}) {
|
|
8054
|
-
const configPath = path4.join(rootDir, ".opensteer", "config.json");
|
|
8055
|
-
if (!fs3.existsSync(configPath)) return {};
|
|
8056
|
-
try {
|
|
8057
|
-
const raw = fs3.readFileSync(configPath, "utf8");
|
|
8058
|
-
return JSON.parse(raw);
|
|
8059
|
-
} catch (error) {
|
|
8060
|
-
const message = extractErrorMessage(
|
|
8061
|
-
error,
|
|
8062
|
-
"Unable to read or parse config file."
|
|
8063
|
-
);
|
|
8064
|
-
if (options.debug) {
|
|
8065
|
-
console.warn(
|
|
8066
|
-
`[opensteer] failed to load config file "${configPath}": ${message}`
|
|
8067
|
-
);
|
|
8068
|
-
}
|
|
8069
|
-
return {};
|
|
8070
|
-
}
|
|
8071
|
-
}
|
|
8072
|
-
function mergeDeep(base, patch) {
|
|
8073
|
-
const out = {
|
|
8074
|
-
...base
|
|
8075
|
-
};
|
|
8076
|
-
for (const [key, value] of Object.entries(patch || {})) {
|
|
8077
|
-
const currentValue = out[key];
|
|
8078
|
-
if (value && typeof value === "object" && !Array.isArray(value) && currentValue && typeof currentValue === "object" && !Array.isArray(currentValue)) {
|
|
8079
|
-
out[key] = mergeDeep(
|
|
8080
|
-
currentValue,
|
|
8081
|
-
value
|
|
8082
|
-
);
|
|
8083
|
-
continue;
|
|
8084
|
-
}
|
|
8085
|
-
if (value !== void 0) {
|
|
8086
|
-
out[key] = value;
|
|
8087
|
-
}
|
|
8088
|
-
}
|
|
8089
|
-
return out;
|
|
8090
|
-
}
|
|
8091
|
-
function parseBool(value) {
|
|
8092
|
-
if (value == null) return void 0;
|
|
8093
|
-
const normalized = value.trim().toLowerCase();
|
|
8094
|
-
if (normalized === "true" || normalized === "1") return true;
|
|
8095
|
-
if (normalized === "false" || normalized === "0") return false;
|
|
8096
|
-
return void 0;
|
|
8097
|
-
}
|
|
8098
|
-
function parseNumber(value) {
|
|
8099
|
-
if (value == null || value.trim() === "") return void 0;
|
|
8100
|
-
const parsed = Number(value);
|
|
8101
|
-
if (!Number.isFinite(parsed)) return void 0;
|
|
8102
|
-
return parsed;
|
|
8103
|
-
}
|
|
8104
|
-
function parseRuntimeMode(value, source) {
|
|
8105
|
-
if (value == null) return void 0;
|
|
8106
|
-
if (typeof value !== "string") {
|
|
8107
|
-
throw new Error(
|
|
8108
|
-
`Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
|
|
8109
|
-
);
|
|
8110
|
-
}
|
|
8111
|
-
const normalized = value.trim().toLowerCase();
|
|
8112
|
-
if (!normalized) return void 0;
|
|
8113
|
-
if (normalized === "local" || normalized === "cloud") {
|
|
8114
|
-
return normalized;
|
|
8115
|
-
}
|
|
8116
|
-
throw new Error(
|
|
8117
|
-
`Invalid ${source} value "${value}". Use "local" or "cloud".`
|
|
8118
|
-
);
|
|
8119
|
-
}
|
|
8120
|
-
function parseAuthScheme(value, source) {
|
|
8121
|
-
if (value == null) return void 0;
|
|
8122
|
-
if (typeof value !== "string") {
|
|
8123
|
-
throw new Error(
|
|
8124
|
-
`Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
|
|
8125
|
-
);
|
|
8126
|
-
}
|
|
8127
|
-
const normalized = value.trim().toLowerCase();
|
|
8128
|
-
if (!normalized) return void 0;
|
|
8129
|
-
if (normalized === "api-key" || normalized === "bearer") {
|
|
8130
|
-
return normalized;
|
|
8131
|
-
}
|
|
8132
|
-
throw new Error(
|
|
8133
|
-
`Invalid ${source} value "${value}". Use "api-key" or "bearer".`
|
|
8134
|
-
);
|
|
8135
|
-
}
|
|
8136
|
-
function parseCloudAnnounce(value, source) {
|
|
8137
|
-
if (value == null) return void 0;
|
|
8138
|
-
if (typeof value !== "string") {
|
|
8139
|
-
throw new Error(
|
|
8140
|
-
`Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
|
|
8141
|
-
);
|
|
8142
|
-
}
|
|
8143
|
-
const normalized = value.trim().toLowerCase();
|
|
8144
|
-
if (!normalized) return void 0;
|
|
8145
|
-
if (normalized === "always" || normalized === "off" || normalized === "tty") {
|
|
8146
|
-
return normalized;
|
|
8147
|
-
}
|
|
8148
|
-
throw new Error(
|
|
8149
|
-
`Invalid ${source} value "${value}". Use "always", "off", or "tty".`
|
|
8150
|
-
);
|
|
8151
|
-
}
|
|
8152
|
-
function resolveOpensteerApiKey(env) {
|
|
8153
|
-
const value = env.OPENSTEER_API_KEY?.trim();
|
|
8154
|
-
if (!value) return void 0;
|
|
8155
|
-
return value;
|
|
8156
|
-
}
|
|
8157
|
-
function resolveOpensteerBaseUrl(env) {
|
|
8158
|
-
const value = env.OPENSTEER_BASE_URL?.trim();
|
|
8159
|
-
if (!value) return void 0;
|
|
8160
|
-
return value;
|
|
8161
|
-
}
|
|
8162
|
-
function resolveOpensteerAuthScheme(env) {
|
|
8163
|
-
return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
|
|
8164
|
-
}
|
|
8165
|
-
function normalizeCloudOptions(value) {
|
|
8166
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8167
|
-
return void 0;
|
|
8168
|
-
}
|
|
8169
|
-
return value;
|
|
8170
|
-
}
|
|
8171
|
-
function parseCloudEnabled(value, source) {
|
|
8172
|
-
if (value == null) return void 0;
|
|
8173
|
-
if (typeof value === "boolean") return value;
|
|
8174
|
-
if (typeof value === "object" && !Array.isArray(value)) return true;
|
|
8175
|
-
throw new Error(
|
|
8176
|
-
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
8177
|
-
);
|
|
8178
|
-
}
|
|
8179
|
-
function resolveCloudSelection(config, env = process.env) {
|
|
8180
|
-
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
8181
|
-
if (configCloud !== void 0) {
|
|
8182
|
-
return {
|
|
8183
|
-
cloud: configCloud,
|
|
8184
|
-
source: "config.cloud"
|
|
8185
|
-
};
|
|
8186
|
-
}
|
|
8187
|
-
const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
|
|
8188
|
-
if (envMode) {
|
|
8189
|
-
return {
|
|
8190
|
-
cloud: envMode === "cloud",
|
|
8191
|
-
source: "env.OPENSTEER_MODE"
|
|
8192
|
-
};
|
|
8193
|
-
}
|
|
8194
|
-
return {
|
|
8195
|
-
cloud: false,
|
|
8196
|
-
source: "default"
|
|
8197
|
-
};
|
|
8198
|
-
}
|
|
8199
|
-
function resolveConfigWithEnv(input = {}) {
|
|
8200
|
-
const processEnv = process.env;
|
|
8201
|
-
const debugHint = typeof input.debug === "boolean" ? input.debug : parseBool(processEnv.OPENSTEER_DEBUG) === true;
|
|
8202
|
-
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
8203
|
-
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
8204
|
-
storage: {
|
|
8205
|
-
rootDir: initialRootDir
|
|
8206
|
-
}
|
|
8207
|
-
});
|
|
8208
|
-
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
8209
|
-
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
8210
|
-
const fileConfig = loadConfigFile(initialRootDir, {
|
|
8211
|
-
debug: debugHint
|
|
8212
|
-
});
|
|
8213
|
-
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
8214
|
-
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
8215
|
-
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
8216
|
-
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
8217
|
-
const env = resolveEnv(envRootDir, {
|
|
8218
|
-
debug: debugHint
|
|
8219
|
-
});
|
|
8220
|
-
if (env.OPENSTEER_AI_MODEL) {
|
|
8221
|
-
throw new Error(
|
|
8222
|
-
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
8223
|
-
);
|
|
8425
|
+
this.persistentContext = null;
|
|
8426
|
+
this.cdpProxy?.close();
|
|
8427
|
+
this.cdpProxy = null;
|
|
8428
|
+
throw error;
|
|
8429
|
+
}
|
|
8224
8430
|
}
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8431
|
+
async launchPersistentProfile(options, channel, profileDir) {
|
|
8432
|
+
const args = [];
|
|
8433
|
+
const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
|
|
8434
|
+
if (launchProfile.profileDirectory) {
|
|
8435
|
+
args.push(`--profile-directory=${launchProfile.profileDirectory}`);
|
|
8436
|
+
}
|
|
8437
|
+
const context = await chromium2.launchPersistentContext(
|
|
8438
|
+
launchProfile.userDataDir,
|
|
8439
|
+
{
|
|
8440
|
+
channel,
|
|
8441
|
+
headless: options.headless ?? this.defaults.headless,
|
|
8442
|
+
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
8443
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8444
|
+
timeout: options.timeout,
|
|
8445
|
+
...options.context || {},
|
|
8446
|
+
args
|
|
8447
|
+
}
|
|
8228
8448
|
);
|
|
8449
|
+
const browser = context.browser();
|
|
8450
|
+
if (!browser) {
|
|
8451
|
+
await context.close().catch(() => void 0);
|
|
8452
|
+
throw new Error("Persistent browser launch did not expose a browser instance.");
|
|
8453
|
+
}
|
|
8454
|
+
this.browser = browser;
|
|
8455
|
+
this.persistentContext = context;
|
|
8456
|
+
const pages = context.pages();
|
|
8457
|
+
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
8458
|
+
return { browser, context, page, isExternal: false };
|
|
8229
8459
|
}
|
|
8230
|
-
|
|
8231
|
-
browser
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
8244
|
-
const resolved = mergeDeep(mergedWithEnv, input);
|
|
8245
|
-
const envApiKey = resolveOpensteerApiKey(env);
|
|
8246
|
-
const envBaseUrl = resolveOpensteerBaseUrl(env);
|
|
8247
|
-
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
8248
|
-
const envCloudAnnounce = parseCloudAnnounce(
|
|
8249
|
-
env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
8250
|
-
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
8251
|
-
);
|
|
8252
|
-
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
8253
|
-
const inputAuthScheme = parseAuthScheme(
|
|
8254
|
-
inputCloudOptions?.authScheme,
|
|
8255
|
-
"cloud.authScheme"
|
|
8256
|
-
);
|
|
8257
|
-
const inputCloudAnnounce = parseCloudAnnounce(
|
|
8258
|
-
inputCloudOptions?.announce,
|
|
8259
|
-
"cloud.announce"
|
|
8260
|
-
);
|
|
8261
|
-
const inputHasCloudApiKey = Boolean(
|
|
8262
|
-
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
|
|
8263
|
-
);
|
|
8264
|
-
const inputHasCloudBaseUrl = Boolean(
|
|
8265
|
-
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
|
|
8266
|
-
);
|
|
8267
|
-
const cloudSelection = resolveCloudSelection({
|
|
8268
|
-
cloud: resolved.cloud
|
|
8269
|
-
}, env);
|
|
8270
|
-
if (cloudSelection.cloud) {
|
|
8271
|
-
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
8272
|
-
const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
8273
|
-
const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
|
|
8274
|
-
resolved.cloud = {
|
|
8275
|
-
...resolvedCloud,
|
|
8276
|
-
authScheme,
|
|
8277
|
-
announce
|
|
8278
|
-
};
|
|
8279
|
-
}
|
|
8280
|
-
if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
|
|
8281
|
-
resolved.cloud = {
|
|
8282
|
-
...normalizeCloudOptions(resolved.cloud) ?? {},
|
|
8283
|
-
apiKey: envApiKey
|
|
8284
|
-
};
|
|
8285
|
-
}
|
|
8286
|
-
if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
|
|
8287
|
-
resolved.cloud = {
|
|
8288
|
-
...normalizeCloudOptions(resolved.cloud) ?? {},
|
|
8289
|
-
baseUrl: envBaseUrl
|
|
8290
|
-
};
|
|
8460
|
+
async launchWithChannel(options, channel) {
|
|
8461
|
+
const browser = await chromium2.launch({
|
|
8462
|
+
channel,
|
|
8463
|
+
headless: options.headless ?? this.defaults.headless,
|
|
8464
|
+
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
8465
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8466
|
+
timeout: options.timeout
|
|
8467
|
+
});
|
|
8468
|
+
this.browser = browser;
|
|
8469
|
+
this.persistentContext = null;
|
|
8470
|
+
const context = await browser.newContext(options.context || {});
|
|
8471
|
+
const page = await context.newPage();
|
|
8472
|
+
return { browser, context, page, isExternal: false };
|
|
8291
8473
|
}
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
const cleaned = relative.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
|
|
8305
|
-
return normalizeNamespace(cleaned || "default");
|
|
8306
|
-
}
|
|
8307
|
-
function getCallerFilePath() {
|
|
8308
|
-
const stack = new Error().stack;
|
|
8309
|
-
if (!stack) return null;
|
|
8310
|
-
const lines = stack.split("\n").slice(2);
|
|
8311
|
-
for (const line of lines) {
|
|
8312
|
-
const match = line.match(/\((.*):(\d+):(\d+)\)/) || line.match(/at\s+(.*):(\d+):(\d+)/);
|
|
8313
|
-
if (!match) continue;
|
|
8314
|
-
const rawPath = match[1];
|
|
8315
|
-
if (!rawPath) continue;
|
|
8316
|
-
if (rawPath.includes("node:internal")) continue;
|
|
8317
|
-
if (rawPath.includes("node_modules")) continue;
|
|
8318
|
-
if (rawPath.includes("/opensteer-oss/src/")) continue;
|
|
8319
|
-
try {
|
|
8320
|
-
if (rawPath.startsWith("file://")) {
|
|
8321
|
-
return fileURLToPath(rawPath);
|
|
8322
|
-
}
|
|
8323
|
-
return rawPath;
|
|
8324
|
-
} catch {
|
|
8325
|
-
continue;
|
|
8326
|
-
}
|
|
8474
|
+
async launchSandbox(options) {
|
|
8475
|
+
const browser = await chromium2.launch({
|
|
8476
|
+
headless: options.headless ?? this.defaults.headless,
|
|
8477
|
+
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
8478
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
8479
|
+
timeout: options.timeout
|
|
8480
|
+
});
|
|
8481
|
+
const context = await browser.newContext(options.context || {});
|
|
8482
|
+
const page = await context.newPage();
|
|
8483
|
+
this.browser = browser;
|
|
8484
|
+
this.persistentContext = null;
|
|
8485
|
+
return { browser, context, page, isExternal: false };
|
|
8327
8486
|
}
|
|
8328
|
-
|
|
8329
|
-
}
|
|
8487
|
+
};
|
|
8330
8488
|
|
|
8331
8489
|
// src/action-wait.ts
|
|
8332
8490
|
var ROBUST_PROFILE = {
|
|
@@ -8521,7 +8679,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8521
8679
|
this.idleSince = 0;
|
|
8522
8680
|
}
|
|
8523
8681
|
const remaining = Math.max(1, options.deadline - now);
|
|
8524
|
-
await
|
|
8682
|
+
await sleep5(Math.min(NETWORK_POLL_MS, remaining));
|
|
8525
8683
|
}
|
|
8526
8684
|
}
|
|
8527
8685
|
handleRequestStarted = (request) => {
|
|
@@ -8566,7 +8724,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8566
8724
|
return false;
|
|
8567
8725
|
}
|
|
8568
8726
|
};
|
|
8569
|
-
async function
|
|
8727
|
+
async function sleep5(ms) {
|
|
8570
8728
|
await new Promise((resolve) => {
|
|
8571
8729
|
setTimeout(resolve, ms);
|
|
8572
8730
|
});
|
|
@@ -8590,8 +8748,8 @@ function stableStringify(value) {
|
|
|
8590
8748
|
}
|
|
8591
8749
|
|
|
8592
8750
|
// src/extraction/data-path.ts
|
|
8593
|
-
function parseDataPath(
|
|
8594
|
-
const input = String(
|
|
8751
|
+
function parseDataPath(path3) {
|
|
8752
|
+
const input = String(path3 || "").trim();
|
|
8595
8753
|
if (!input) return [];
|
|
8596
8754
|
if (input.includes("..")) return null;
|
|
8597
8755
|
if (input.startsWith(".") || input.endsWith(".")) return null;
|
|
@@ -8647,8 +8805,8 @@ function joinDataPath(base, key) {
|
|
|
8647
8805
|
function inflateDataPathObject(flat) {
|
|
8648
8806
|
let root = {};
|
|
8649
8807
|
let initialized = false;
|
|
8650
|
-
for (const [
|
|
8651
|
-
const tokens = parseDataPath(
|
|
8808
|
+
for (const [path3, value] of Object.entries(flat || {})) {
|
|
8809
|
+
const tokens = parseDataPath(path3);
|
|
8652
8810
|
if (!tokens || !tokens.length) continue;
|
|
8653
8811
|
if (!initialized) {
|
|
8654
8812
|
root = tokens[0]?.kind === "index" ? [] : {};
|
|
@@ -9023,8 +9181,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
|
|
|
9023
9181
|
fields: mergedFields
|
|
9024
9182
|
};
|
|
9025
9183
|
}
|
|
9026
|
-
function minimizePathMatchClauses(
|
|
9027
|
-
const normalized = sanitizeElementPath(
|
|
9184
|
+
function minimizePathMatchClauses(path3, mode) {
|
|
9185
|
+
const normalized = sanitizeElementPath(path3);
|
|
9028
9186
|
const nodes = normalized.nodes.map((node, index) => {
|
|
9029
9187
|
const isLast = index === normalized.nodes.length - 1;
|
|
9030
9188
|
const attrs = node.attrs || {};
|
|
@@ -9127,8 +9285,8 @@ function seedMinimalAttrClause(attrs) {
|
|
|
9127
9285
|
}
|
|
9128
9286
|
return null;
|
|
9129
9287
|
}
|
|
9130
|
-
function relaxPathForSingleSample(
|
|
9131
|
-
const normalized = sanitizeElementPath(
|
|
9288
|
+
function relaxPathForSingleSample(path3, mode) {
|
|
9289
|
+
const normalized = sanitizeElementPath(path3);
|
|
9132
9290
|
const relaxedNodes = normalized.nodes.map((node, index) => {
|
|
9133
9291
|
const isLast = index === normalized.nodes.length - 1;
|
|
9134
9292
|
const attrs = normalizeAttrsForSingleSample(node.attrs || {});
|
|
@@ -9198,8 +9356,8 @@ function shouldKeepAttrForSingleSample(key) {
|
|
|
9198
9356
|
}
|
|
9199
9357
|
return true;
|
|
9200
9358
|
}
|
|
9201
|
-
function buildPathStructureKey(
|
|
9202
|
-
const normalized = sanitizeElementPath(
|
|
9359
|
+
function buildPathStructureKey(path3) {
|
|
9360
|
+
const normalized = sanitizeElementPath(path3);
|
|
9203
9361
|
return stableStringify({
|
|
9204
9362
|
context: (normalized.context || []).map((hop) => ({
|
|
9205
9363
|
kind: hop.kind,
|
|
@@ -9313,16 +9471,16 @@ function buildArrayItemNode(fields) {
|
|
|
9313
9471
|
}
|
|
9314
9472
|
return node;
|
|
9315
9473
|
}
|
|
9316
|
-
function insertNodeAtPath(root,
|
|
9317
|
-
const tokens = parseDataPath(
|
|
9474
|
+
function insertNodeAtPath(root, path3, node) {
|
|
9475
|
+
const tokens = parseDataPath(path3);
|
|
9318
9476
|
if (!tokens || !tokens.length) {
|
|
9319
9477
|
throw new Error(
|
|
9320
|
-
`Invalid persisted extraction path "${
|
|
9478
|
+
`Invalid persisted extraction path "${path3}": expected a non-empty object path.`
|
|
9321
9479
|
);
|
|
9322
9480
|
}
|
|
9323
9481
|
if (tokens.some((token) => token.kind === "index")) {
|
|
9324
9482
|
throw new Error(
|
|
9325
|
-
`Invalid persisted extraction path "${
|
|
9483
|
+
`Invalid persisted extraction path "${path3}": nested array indices are not supported in cached descriptors.`
|
|
9326
9484
|
);
|
|
9327
9485
|
}
|
|
9328
9486
|
let current = root;
|
|
@@ -9330,7 +9488,7 @@ function insertNodeAtPath(root, path5, node) {
|
|
|
9330
9488
|
const token = tokens[i];
|
|
9331
9489
|
if (token.kind !== "prop") {
|
|
9332
9490
|
throw new Error(
|
|
9333
|
-
`Invalid persisted extraction path "${
|
|
9491
|
+
`Invalid persisted extraction path "${path3}": expected object segment.`
|
|
9334
9492
|
);
|
|
9335
9493
|
}
|
|
9336
9494
|
const isLast = i === tokens.length - 1;
|
|
@@ -9338,7 +9496,7 @@ function insertNodeAtPath(root, path5, node) {
|
|
|
9338
9496
|
const existing = current[token.key];
|
|
9339
9497
|
if (existing) {
|
|
9340
9498
|
throw new Error(
|
|
9341
|
-
`Conflicting persisted extraction path "${
|
|
9499
|
+
`Conflicting persisted extraction path "${path3}" detected while building descriptor tree.`
|
|
9342
9500
|
);
|
|
9343
9501
|
}
|
|
9344
9502
|
current[token.key] = node;
|
|
@@ -9353,7 +9511,7 @@ function insertNodeAtPath(root, path5, node) {
|
|
|
9353
9511
|
}
|
|
9354
9512
|
if (!isPersistedObjectNode(next)) {
|
|
9355
9513
|
throw new Error(
|
|
9356
|
-
`Conflicting persisted extraction path "${
|
|
9514
|
+
`Conflicting persisted extraction path "${path3}" detected at "${token.key}".`
|
|
9357
9515
|
);
|
|
9358
9516
|
}
|
|
9359
9517
|
current = next;
|
|
@@ -9379,7 +9537,7 @@ function buildItemRootForArrayIndex(entries) {
|
|
|
9379
9537
|
if (!entries.length) return null;
|
|
9380
9538
|
const paths = entries.map(
|
|
9381
9539
|
(entry) => isPersistablePathField(entry.source) ? sanitizeElementPath(entry.source.path) : null
|
|
9382
|
-
).filter((
|
|
9540
|
+
).filter((path3) => !!path3);
|
|
9383
9541
|
if (!paths.length) return null;
|
|
9384
9542
|
const prefixLength = getCommonPathPrefixLength(paths);
|
|
9385
9543
|
if (prefixLength <= 0) return null;
|
|
@@ -9392,7 +9550,7 @@ function buildItemRootForArrayIndex(entries) {
|
|
|
9392
9550
|
}
|
|
9393
9551
|
function getCommonPathPrefixLength(paths) {
|
|
9394
9552
|
if (!paths.length) return 0;
|
|
9395
|
-
const nodeChains = paths.map((
|
|
9553
|
+
const nodeChains = paths.map((path3) => path3.nodes);
|
|
9396
9554
|
const minLength = Math.min(...nodeChains.map((nodes) => nodes.length));
|
|
9397
9555
|
if (!Number.isFinite(minLength) || minLength <= 0) return 0;
|
|
9398
9556
|
for (let i = 0; i < minLength; i++) {
|
|
@@ -9449,28 +9607,28 @@ function toRelativeElementPath(absolute, root) {
|
|
|
9449
9607
|
}
|
|
9450
9608
|
function mergeElementPathsByMajority(paths) {
|
|
9451
9609
|
if (!paths.length) return null;
|
|
9452
|
-
const normalized = paths.map((
|
|
9610
|
+
const normalized = paths.map((path3) => sanitizeElementPath(path3));
|
|
9453
9611
|
const contextKey = pickModeString(
|
|
9454
|
-
normalized.map((
|
|
9612
|
+
normalized.map((path3) => stableStringify(path3.context)),
|
|
9455
9613
|
1
|
|
9456
9614
|
);
|
|
9457
9615
|
if (!contextKey) return null;
|
|
9458
9616
|
const sameContext = normalized.filter(
|
|
9459
|
-
(
|
|
9617
|
+
(path3) => stableStringify(path3.context) === contextKey
|
|
9460
9618
|
);
|
|
9461
9619
|
if (!sameContext.length) return null;
|
|
9462
9620
|
const targetLength = pickModeNumber(
|
|
9463
|
-
sameContext.map((
|
|
9621
|
+
sameContext.map((path3) => path3.nodes.length),
|
|
9464
9622
|
1
|
|
9465
9623
|
) ?? sameContext[0]?.nodes.length ?? 0;
|
|
9466
9624
|
const aligned = sameContext.filter(
|
|
9467
|
-
(
|
|
9625
|
+
(path3) => path3.nodes.length === targetLength
|
|
9468
9626
|
);
|
|
9469
9627
|
if (!aligned.length) return null;
|
|
9470
9628
|
const threshold = majorityThreshold(aligned.length);
|
|
9471
9629
|
const nodes = [];
|
|
9472
9630
|
for (let i = 0; i < targetLength; i++) {
|
|
9473
|
-
const nodesAtIndex = aligned.map((
|
|
9631
|
+
const nodesAtIndex = aligned.map((path3) => path3.nodes[i]).filter((node) => !!node);
|
|
9474
9632
|
if (!nodesAtIndex.length) return null;
|
|
9475
9633
|
nodes.push(mergePathNodeByMajority(nodesAtIndex, threshold));
|
|
9476
9634
|
}
|
|
@@ -9720,8 +9878,8 @@ function collectValueNodes(node) {
|
|
|
9720
9878
|
return [
|
|
9721
9879
|
{
|
|
9722
9880
|
path: node.$path,
|
|
9723
|
-
replacePath(
|
|
9724
|
-
node.$path =
|
|
9881
|
+
replacePath(path3) {
|
|
9882
|
+
node.$path = path3;
|
|
9725
9883
|
}
|
|
9726
9884
|
}
|
|
9727
9885
|
];
|
|
@@ -9733,10 +9891,10 @@ function collectValueNodes(node) {
|
|
|
9733
9891
|
if (isPersistedValueNode(child)) {
|
|
9734
9892
|
refs.push({
|
|
9735
9893
|
path: child.$path,
|
|
9736
|
-
replacePath(
|
|
9894
|
+
replacePath(path3) {
|
|
9737
9895
|
const next = current[key];
|
|
9738
9896
|
if (!isPersistedValueNode(next)) return;
|
|
9739
|
-
next.$path =
|
|
9897
|
+
next.$path = path3;
|
|
9740
9898
|
}
|
|
9741
9899
|
});
|
|
9742
9900
|
continue;
|
|
@@ -9749,8 +9907,8 @@ function collectValueNodes(node) {
|
|
|
9749
9907
|
visit(node);
|
|
9750
9908
|
return refs;
|
|
9751
9909
|
}
|
|
9752
|
-
function hasPositionClause(
|
|
9753
|
-
return
|
|
9910
|
+
function hasPositionClause(path3) {
|
|
9911
|
+
return path3.nodes.some(
|
|
9754
9912
|
(node) => (node.match || []).some((clause) => clause.kind === "position")
|
|
9755
9913
|
);
|
|
9756
9914
|
}
|
|
@@ -9866,49 +10024,16 @@ async function validateRowSelectors(item, fields) {
|
|
|
9866
10024
|
return out;
|
|
9867
10025
|
}, fields);
|
|
9868
10026
|
}
|
|
9869
|
-
function stripPositionClauses2(
|
|
10027
|
+
function stripPositionClauses2(path3) {
|
|
9870
10028
|
return {
|
|
9871
|
-
context:
|
|
9872
|
-
nodes:
|
|
10029
|
+
context: path3.context,
|
|
10030
|
+
nodes: path3.nodes.map((node) => ({
|
|
9873
10031
|
...node,
|
|
9874
10032
|
match: (node.match || []).filter((clause) => clause.kind !== "position")
|
|
9875
10033
|
}))
|
|
9876
10034
|
};
|
|
9877
10035
|
}
|
|
9878
10036
|
|
|
9879
|
-
// src/cloud/runtime.ts
|
|
9880
|
-
var DEFAULT_CLOUD_BASE_URL = "https://api.opensteer.com";
|
|
9881
|
-
function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key") {
|
|
9882
|
-
const normalizedBaseUrl = normalizeCloudBaseUrl(baseUrl);
|
|
9883
|
-
return {
|
|
9884
|
-
sessionClient: new CloudSessionClient(
|
|
9885
|
-
normalizedBaseUrl,
|
|
9886
|
-
key,
|
|
9887
|
-
authScheme
|
|
9888
|
-
),
|
|
9889
|
-
cdpClient: new CloudCdpClient(),
|
|
9890
|
-
baseUrl: normalizedBaseUrl,
|
|
9891
|
-
actionClient: null,
|
|
9892
|
-
sessionId: null,
|
|
9893
|
-
localRunId: null,
|
|
9894
|
-
cloudSessionUrl: null
|
|
9895
|
-
};
|
|
9896
|
-
}
|
|
9897
|
-
function resolveCloudBaseUrl() {
|
|
9898
|
-
const value = process.env.OPENSTEER_BASE_URL?.trim();
|
|
9899
|
-
if (!value) return DEFAULT_CLOUD_BASE_URL;
|
|
9900
|
-
return normalizeCloudBaseUrl(value);
|
|
9901
|
-
}
|
|
9902
|
-
function normalizeCloudBaseUrl(value) {
|
|
9903
|
-
return value.replace(/\/+$/, "");
|
|
9904
|
-
}
|
|
9905
|
-
function readCloudActionDescription(payload) {
|
|
9906
|
-
const description = payload.description;
|
|
9907
|
-
if (typeof description !== "string") return void 0;
|
|
9908
|
-
const normalized = description.trim();
|
|
9909
|
-
return normalized.length ? normalized : void 0;
|
|
9910
|
-
}
|
|
9911
|
-
|
|
9912
10037
|
// src/opensteer.ts
|
|
9913
10038
|
var CLOUD_INTERACTION_METHODS = /* @__PURE__ */ new Set([
|
|
9914
10039
|
"click",
|
|
@@ -9935,6 +10060,7 @@ var Opensteer = class _Opensteer {
|
|
|
9935
10060
|
ownsBrowser = false;
|
|
9936
10061
|
snapshotCache = null;
|
|
9937
10062
|
agentExecutionInFlight = false;
|
|
10063
|
+
cursorController = null;
|
|
9938
10064
|
constructor(config = {}) {
|
|
9939
10065
|
const resolvedRuntime = resolveConfigWithEnv(config);
|
|
9940
10066
|
const resolved = resolvedRuntime.config;
|
|
@@ -9956,15 +10082,29 @@ var Opensteer = class _Opensteer {
|
|
|
9956
10082
|
if (cloudSelection.cloud) {
|
|
9957
10083
|
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
9958
10084
|
const apiKey = cloudConfig?.apiKey?.trim();
|
|
9959
|
-
|
|
10085
|
+
const accessToken = cloudConfig?.accessToken?.trim();
|
|
10086
|
+
if (apiKey && accessToken) {
|
|
10087
|
+
throw new Error(
|
|
10088
|
+
"Cloud mode cannot use both cloud.apiKey and cloud.accessToken. Set only one credential."
|
|
10089
|
+
);
|
|
10090
|
+
}
|
|
10091
|
+
let credential = "";
|
|
10092
|
+
let authScheme = cloudConfig?.authScheme ?? "api-key";
|
|
10093
|
+
if (accessToken) {
|
|
10094
|
+
credential = accessToken;
|
|
10095
|
+
authScheme = "bearer";
|
|
10096
|
+
} else if (apiKey) {
|
|
10097
|
+
credential = apiKey;
|
|
10098
|
+
}
|
|
10099
|
+
if (!credential) {
|
|
9960
10100
|
throw new Error(
|
|
9961
|
-
"Cloud mode requires
|
|
10101
|
+
"Cloud mode requires credentials via cloud.apiKey/cloud.accessToken or OPENSTEER_API_KEY/OPENSTEER_ACCESS_TOKEN."
|
|
9962
10102
|
);
|
|
9963
10103
|
}
|
|
9964
10104
|
this.cloud = createCloudRuntimeState(
|
|
9965
|
-
|
|
10105
|
+
credential,
|
|
9966
10106
|
cloudConfig?.baseUrl,
|
|
9967
|
-
|
|
10107
|
+
authScheme
|
|
9968
10108
|
);
|
|
9969
10109
|
} else {
|
|
9970
10110
|
this.cloud = null;
|
|
@@ -10189,6 +10329,19 @@ var Opensteer = class _Opensteer {
|
|
|
10189
10329
|
}
|
|
10190
10330
|
return true;
|
|
10191
10331
|
}
|
|
10332
|
+
buildCloudSessionLaunchConfig(options) {
|
|
10333
|
+
const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : void 0;
|
|
10334
|
+
const browserProfile = normalizeCloudBrowserProfilePreference(
|
|
10335
|
+
options.cloudBrowserProfile ?? cloudConfig?.browserProfile,
|
|
10336
|
+
options.cloudBrowserProfile ? "launch options" : "Opensteer config"
|
|
10337
|
+
);
|
|
10338
|
+
if (!browserProfile) {
|
|
10339
|
+
return void 0;
|
|
10340
|
+
}
|
|
10341
|
+
return {
|
|
10342
|
+
browserProfile
|
|
10343
|
+
};
|
|
10344
|
+
}
|
|
10192
10345
|
async launch(options = {}) {
|
|
10193
10346
|
if (this.pageRef && !this.ownsBrowser) {
|
|
10194
10347
|
throw new Error(
|
|
@@ -10211,6 +10364,7 @@ var Opensteer = class _Opensteer {
|
|
|
10211
10364
|
}
|
|
10212
10365
|
localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
|
|
10213
10366
|
this.cloud.localRunId = localRunId;
|
|
10367
|
+
const launchConfig = this.buildCloudSessionLaunchConfig(options);
|
|
10214
10368
|
const session2 = await this.cloud.sessionClient.create({
|
|
10215
10369
|
cloudSessionContractVersion,
|
|
10216
10370
|
sourceType: "local-cloud",
|
|
@@ -10218,7 +10372,8 @@ var Opensteer = class _Opensteer {
|
|
|
10218
10372
|
localRunId,
|
|
10219
10373
|
name: this.namespace,
|
|
10220
10374
|
model: this.config.model,
|
|
10221
|
-
launchContext: options.context || void 0
|
|
10375
|
+
launchContext: options.context || void 0,
|
|
10376
|
+
launchConfig
|
|
10222
10377
|
});
|
|
10223
10378
|
sessionId = session2.sessionId;
|
|
10224
10379
|
actionClient = await ActionWsClient.connect({
|
|
@@ -10236,6 +10391,9 @@ var Opensteer = class _Opensteer {
|
|
|
10236
10391
|
this.pageRef = cdpConnection.page;
|
|
10237
10392
|
this.ownsBrowser = true;
|
|
10238
10393
|
this.snapshotCache = null;
|
|
10394
|
+
if (this.cursorController) {
|
|
10395
|
+
await this.cursorController.attachPage(this.pageRef);
|
|
10396
|
+
}
|
|
10239
10397
|
this.cloud.actionClient = actionClient;
|
|
10240
10398
|
this.cloud.sessionId = sessionId;
|
|
10241
10399
|
this.cloud.cloudSessionUrl = session2.cloudSessionUrl;
|
|
@@ -10276,6 +10434,9 @@ var Opensteer = class _Opensteer {
|
|
|
10276
10434
|
this.pageRef = session.page;
|
|
10277
10435
|
this.ownsBrowser = true;
|
|
10278
10436
|
this.snapshotCache = null;
|
|
10437
|
+
if (this.cursorController) {
|
|
10438
|
+
await this.cursorController.attachPage(this.pageRef);
|
|
10439
|
+
}
|
|
10279
10440
|
}
|
|
10280
10441
|
static from(page, config = {}) {
|
|
10281
10442
|
const resolvedRuntime = resolveConfigWithEnv(config);
|
|
@@ -10320,6 +10481,9 @@ var Opensteer = class _Opensteer {
|
|
|
10320
10481
|
if (sessionId) {
|
|
10321
10482
|
await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
|
|
10322
10483
|
}
|
|
10484
|
+
if (this.cursorController) {
|
|
10485
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
10486
|
+
}
|
|
10323
10487
|
return;
|
|
10324
10488
|
}
|
|
10325
10489
|
if (this.ownsBrowser) {
|
|
@@ -10329,6 +10493,9 @@ var Opensteer = class _Opensteer {
|
|
|
10329
10493
|
this.pageRef = null;
|
|
10330
10494
|
this.contextRef = null;
|
|
10331
10495
|
this.ownsBrowser = false;
|
|
10496
|
+
if (this.cursorController) {
|
|
10497
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
10498
|
+
}
|
|
10332
10499
|
}
|
|
10333
10500
|
async syncLocalSelectorCacheToCloud() {
|
|
10334
10501
|
if (!this.cloud) return;
|
|
@@ -10455,12 +10622,22 @@ var Opensteer = class _Opensteer {
|
|
|
10455
10622
|
resolution.counter
|
|
10456
10623
|
);
|
|
10457
10624
|
}
|
|
10458
|
-
await this.
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10625
|
+
await this.runWithCursorPreview(
|
|
10626
|
+
() => this.resolveHandleTargetPoint(handle, options.position),
|
|
10627
|
+
"hover",
|
|
10628
|
+
async () => {
|
|
10629
|
+
await this.runWithPostActionWait(
|
|
10630
|
+
"hover",
|
|
10631
|
+
options.wait,
|
|
10632
|
+
async () => {
|
|
10633
|
+
await handle.hover({
|
|
10634
|
+
force: options.force,
|
|
10635
|
+
position: options.position
|
|
10636
|
+
});
|
|
10637
|
+
}
|
|
10638
|
+
);
|
|
10639
|
+
}
|
|
10640
|
+
);
|
|
10464
10641
|
} catch (err) {
|
|
10465
10642
|
const failure = classifyActionFailure({
|
|
10466
10643
|
action: "hover",
|
|
@@ -10494,26 +10671,32 @@ var Opensteer = class _Opensteer {
|
|
|
10494
10671
|
if (!resolution.path) {
|
|
10495
10672
|
throw new Error("Unable to resolve element path for hover action.");
|
|
10496
10673
|
}
|
|
10497
|
-
const
|
|
10498
|
-
const result = await this.
|
|
10674
|
+
const path3 = resolution.path;
|
|
10675
|
+
const result = await this.runWithCursorPreview(
|
|
10676
|
+
() => this.resolvePathTargetPoint(path3, options.position),
|
|
10499
10677
|
"hover",
|
|
10500
|
-
options.wait,
|
|
10501
10678
|
async () => {
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10679
|
+
return await this.runWithPostActionWait(
|
|
10680
|
+
"hover",
|
|
10681
|
+
options.wait,
|
|
10682
|
+
async () => {
|
|
10683
|
+
const actionResult = await performHover(this.page, path3, options);
|
|
10684
|
+
if (!actionResult.ok) {
|
|
10685
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
10686
|
+
action: "hover",
|
|
10687
|
+
error: actionResult.error || defaultActionFailureMessage("hover"),
|
|
10688
|
+
fallbackMessage: defaultActionFailureMessage("hover")
|
|
10689
|
+
});
|
|
10690
|
+
throw this.buildActionError(
|
|
10691
|
+
"hover",
|
|
10692
|
+
options.description,
|
|
10693
|
+
failure,
|
|
10694
|
+
actionResult.usedSelector || null
|
|
10695
|
+
);
|
|
10696
|
+
}
|
|
10697
|
+
return actionResult;
|
|
10698
|
+
}
|
|
10699
|
+
);
|
|
10517
10700
|
}
|
|
10518
10701
|
);
|
|
10519
10702
|
this.snapshotCache = null;
|
|
@@ -10554,16 +10737,26 @@ var Opensteer = class _Opensteer {
|
|
|
10554
10737
|
resolution.counter
|
|
10555
10738
|
);
|
|
10556
10739
|
}
|
|
10557
|
-
await this.
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
await
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10740
|
+
await this.runWithCursorPreview(
|
|
10741
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
10742
|
+
"input",
|
|
10743
|
+
async () => {
|
|
10744
|
+
await this.runWithPostActionWait(
|
|
10745
|
+
"input",
|
|
10746
|
+
options.wait,
|
|
10747
|
+
async () => {
|
|
10748
|
+
if (options.clear !== false) {
|
|
10749
|
+
await handle.fill(options.text);
|
|
10750
|
+
} else {
|
|
10751
|
+
await handle.type(options.text);
|
|
10752
|
+
}
|
|
10753
|
+
if (options.pressEnter) {
|
|
10754
|
+
await handle.press("Enter", { noWaitAfter: true });
|
|
10755
|
+
}
|
|
10756
|
+
}
|
|
10757
|
+
);
|
|
10565
10758
|
}
|
|
10566
|
-
|
|
10759
|
+
);
|
|
10567
10760
|
} catch (err) {
|
|
10568
10761
|
const failure = classifyActionFailure({
|
|
10569
10762
|
action: "input",
|
|
@@ -10597,26 +10790,32 @@ var Opensteer = class _Opensteer {
|
|
|
10597
10790
|
if (!resolution.path) {
|
|
10598
10791
|
throw new Error("Unable to resolve element path for input action.");
|
|
10599
10792
|
}
|
|
10600
|
-
const
|
|
10601
|
-
const result = await this.
|
|
10793
|
+
const path3 = resolution.path;
|
|
10794
|
+
const result = await this.runWithCursorPreview(
|
|
10795
|
+
() => this.resolvePathTargetPoint(path3),
|
|
10602
10796
|
"input",
|
|
10603
|
-
options.wait,
|
|
10604
10797
|
async () => {
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10798
|
+
return await this.runWithPostActionWait(
|
|
10799
|
+
"input",
|
|
10800
|
+
options.wait,
|
|
10801
|
+
async () => {
|
|
10802
|
+
const actionResult = await performInput(this.page, path3, options);
|
|
10803
|
+
if (!actionResult.ok) {
|
|
10804
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
10805
|
+
action: "input",
|
|
10806
|
+
error: actionResult.error || defaultActionFailureMessage("input"),
|
|
10807
|
+
fallbackMessage: defaultActionFailureMessage("input")
|
|
10808
|
+
});
|
|
10809
|
+
throw this.buildActionError(
|
|
10810
|
+
"input",
|
|
10811
|
+
options.description,
|
|
10812
|
+
failure,
|
|
10813
|
+
actionResult.usedSelector || null
|
|
10814
|
+
);
|
|
10815
|
+
}
|
|
10816
|
+
return actionResult;
|
|
10817
|
+
}
|
|
10818
|
+
);
|
|
10620
10819
|
}
|
|
10621
10820
|
);
|
|
10622
10821
|
this.snapshotCache = null;
|
|
@@ -10657,21 +10856,27 @@ var Opensteer = class _Opensteer {
|
|
|
10657
10856
|
resolution.counter
|
|
10658
10857
|
);
|
|
10659
10858
|
}
|
|
10660
|
-
await this.
|
|
10859
|
+
await this.runWithCursorPreview(
|
|
10860
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
10661
10861
|
"select",
|
|
10662
|
-
options.wait,
|
|
10663
10862
|
async () => {
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10863
|
+
await this.runWithPostActionWait(
|
|
10864
|
+
"select",
|
|
10865
|
+
options.wait,
|
|
10866
|
+
async () => {
|
|
10867
|
+
if (options.value != null) {
|
|
10868
|
+
await handle.selectOption(options.value);
|
|
10869
|
+
} else if (options.label != null) {
|
|
10870
|
+
await handle.selectOption({ label: options.label });
|
|
10871
|
+
} else if (options.index != null) {
|
|
10872
|
+
await handle.selectOption({ index: options.index });
|
|
10873
|
+
} else {
|
|
10874
|
+
throw new Error(
|
|
10875
|
+
"Select requires value, label, or index."
|
|
10876
|
+
);
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
);
|
|
10675
10880
|
}
|
|
10676
10881
|
);
|
|
10677
10882
|
} catch (err) {
|
|
@@ -10707,26 +10912,32 @@ var Opensteer = class _Opensteer {
|
|
|
10707
10912
|
if (!resolution.path) {
|
|
10708
10913
|
throw new Error("Unable to resolve element path for select action.");
|
|
10709
10914
|
}
|
|
10710
|
-
const
|
|
10711
|
-
const result = await this.
|
|
10915
|
+
const path3 = resolution.path;
|
|
10916
|
+
const result = await this.runWithCursorPreview(
|
|
10917
|
+
() => this.resolvePathTargetPoint(path3),
|
|
10712
10918
|
"select",
|
|
10713
|
-
options.wait,
|
|
10714
10919
|
async () => {
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10920
|
+
return await this.runWithPostActionWait(
|
|
10921
|
+
"select",
|
|
10922
|
+
options.wait,
|
|
10923
|
+
async () => {
|
|
10924
|
+
const actionResult = await performSelect(this.page, path3, options);
|
|
10925
|
+
if (!actionResult.ok) {
|
|
10926
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
10927
|
+
action: "select",
|
|
10928
|
+
error: actionResult.error || defaultActionFailureMessage("select"),
|
|
10929
|
+
fallbackMessage: defaultActionFailureMessage("select")
|
|
10930
|
+
});
|
|
10931
|
+
throw this.buildActionError(
|
|
10932
|
+
"select",
|
|
10933
|
+
options.description,
|
|
10934
|
+
failure,
|
|
10935
|
+
actionResult.usedSelector || null
|
|
10936
|
+
);
|
|
10937
|
+
}
|
|
10938
|
+
return actionResult;
|
|
10939
|
+
}
|
|
10940
|
+
);
|
|
10730
10941
|
}
|
|
10731
10942
|
);
|
|
10732
10943
|
this.snapshotCache = null;
|
|
@@ -10768,13 +10979,23 @@ var Opensteer = class _Opensteer {
|
|
|
10768
10979
|
);
|
|
10769
10980
|
}
|
|
10770
10981
|
const delta = getScrollDelta2(options);
|
|
10771
|
-
await this.
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10982
|
+
await this.runWithCursorPreview(
|
|
10983
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
10984
|
+
"scroll",
|
|
10985
|
+
async () => {
|
|
10986
|
+
await this.runWithPostActionWait(
|
|
10987
|
+
"scroll",
|
|
10988
|
+
options.wait,
|
|
10989
|
+
async () => {
|
|
10990
|
+
await handle.evaluate((el, value) => {
|
|
10991
|
+
if (el instanceof HTMLElement) {
|
|
10992
|
+
el.scrollBy(value.x, value.y);
|
|
10993
|
+
}
|
|
10994
|
+
}, delta);
|
|
10995
|
+
}
|
|
10996
|
+
);
|
|
10997
|
+
}
|
|
10998
|
+
);
|
|
10778
10999
|
} catch (err) {
|
|
10779
11000
|
const failure = classifyActionFailure({
|
|
10780
11001
|
action: "scroll",
|
|
@@ -10805,29 +11026,35 @@ var Opensteer = class _Opensteer {
|
|
|
10805
11026
|
`[c="${resolution.counter}"]`
|
|
10806
11027
|
);
|
|
10807
11028
|
}
|
|
10808
|
-
const result = await this.
|
|
11029
|
+
const result = await this.runWithCursorPreview(
|
|
11030
|
+
() => resolution.path ? this.resolvePathTargetPoint(resolution.path) : this.resolveViewportAnchorPoint(),
|
|
10809
11031
|
"scroll",
|
|
10810
|
-
options.wait,
|
|
10811
11032
|
async () => {
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
11033
|
+
return await this.runWithPostActionWait(
|
|
11034
|
+
"scroll",
|
|
11035
|
+
options.wait,
|
|
11036
|
+
async () => {
|
|
11037
|
+
const actionResult = await performScroll(
|
|
11038
|
+
this.page,
|
|
11039
|
+
resolution.path,
|
|
11040
|
+
options
|
|
11041
|
+
);
|
|
11042
|
+
if (!actionResult.ok) {
|
|
11043
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
11044
|
+
action: "scroll",
|
|
11045
|
+
error: actionResult.error || defaultActionFailureMessage("scroll"),
|
|
11046
|
+
fallbackMessage: defaultActionFailureMessage("scroll")
|
|
11047
|
+
});
|
|
11048
|
+
throw this.buildActionError(
|
|
11049
|
+
"scroll",
|
|
11050
|
+
options.description,
|
|
11051
|
+
failure,
|
|
11052
|
+
actionResult.usedSelector || null
|
|
11053
|
+
);
|
|
11054
|
+
}
|
|
11055
|
+
return actionResult;
|
|
11056
|
+
}
|
|
10816
11057
|
);
|
|
10817
|
-
if (!actionResult.ok) {
|
|
10818
|
-
const failure = actionResult.failure || classifyActionFailure({
|
|
10819
|
-
action: "scroll",
|
|
10820
|
-
error: actionResult.error || defaultActionFailureMessage("scroll"),
|
|
10821
|
-
fallbackMessage: defaultActionFailureMessage("scroll")
|
|
10822
|
-
});
|
|
10823
|
-
throw this.buildActionError(
|
|
10824
|
-
"scroll",
|
|
10825
|
-
options.description,
|
|
10826
|
-
failure,
|
|
10827
|
-
actionResult.usedSelector || null
|
|
10828
|
-
);
|
|
10829
|
-
}
|
|
10830
|
-
return actionResult;
|
|
10831
11058
|
}
|
|
10832
11059
|
);
|
|
10833
11060
|
this.snapshotCache = null;
|
|
@@ -10981,7 +11208,7 @@ var Opensteer = class _Opensteer {
|
|
|
10981
11208
|
const text = await handle.textContent();
|
|
10982
11209
|
return text ?? "";
|
|
10983
11210
|
},
|
|
10984
|
-
(
|
|
11211
|
+
(path3) => getElementText(this.page, path3)
|
|
10985
11212
|
);
|
|
10986
11213
|
}
|
|
10987
11214
|
async getElementValue(options) {
|
|
@@ -10997,7 +11224,7 @@ var Opensteer = class _Opensteer {
|
|
|
10997
11224
|
async (handle) => {
|
|
10998
11225
|
return await handle.inputValue();
|
|
10999
11226
|
},
|
|
11000
|
-
(
|
|
11227
|
+
(path3) => getElementValue(this.page, path3)
|
|
11001
11228
|
);
|
|
11002
11229
|
}
|
|
11003
11230
|
async getElementAttributes(options) {
|
|
@@ -11019,7 +11246,7 @@ var Opensteer = class _Opensteer {
|
|
|
11019
11246
|
return attrs;
|
|
11020
11247
|
});
|
|
11021
11248
|
},
|
|
11022
|
-
(
|
|
11249
|
+
(path3) => getElementAttributes(this.page, path3)
|
|
11023
11250
|
);
|
|
11024
11251
|
}
|
|
11025
11252
|
async getElementBoundingBox(options) {
|
|
@@ -11035,7 +11262,7 @@ var Opensteer = class _Opensteer {
|
|
|
11035
11262
|
async (handle) => {
|
|
11036
11263
|
return await handle.boundingBox();
|
|
11037
11264
|
},
|
|
11038
|
-
(
|
|
11265
|
+
(path3) => getElementBoundingBox(this.page, path3)
|
|
11039
11266
|
);
|
|
11040
11267
|
}
|
|
11041
11268
|
async getHtml(selector) {
|
|
@@ -11113,11 +11340,17 @@ var Opensteer = class _Opensteer {
|
|
|
11113
11340
|
resolution.counter
|
|
11114
11341
|
);
|
|
11115
11342
|
}
|
|
11116
|
-
await this.
|
|
11343
|
+
await this.runWithCursorPreview(
|
|
11344
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
11117
11345
|
"uploadFile",
|
|
11118
|
-
options.wait,
|
|
11119
11346
|
async () => {
|
|
11120
|
-
await
|
|
11347
|
+
await this.runWithPostActionWait(
|
|
11348
|
+
"uploadFile",
|
|
11349
|
+
options.wait,
|
|
11350
|
+
async () => {
|
|
11351
|
+
await handle.setInputFiles(options.paths);
|
|
11352
|
+
}
|
|
11353
|
+
);
|
|
11121
11354
|
}
|
|
11122
11355
|
);
|
|
11123
11356
|
} catch (err) {
|
|
@@ -11153,30 +11386,36 @@ var Opensteer = class _Opensteer {
|
|
|
11153
11386
|
if (!resolution.path) {
|
|
11154
11387
|
throw new Error("Unable to resolve element path for file upload.");
|
|
11155
11388
|
}
|
|
11156
|
-
const
|
|
11157
|
-
const result = await this.
|
|
11389
|
+
const path3 = resolution.path;
|
|
11390
|
+
const result = await this.runWithCursorPreview(
|
|
11391
|
+
() => this.resolvePathTargetPoint(path3),
|
|
11158
11392
|
"uploadFile",
|
|
11159
|
-
options.wait,
|
|
11160
11393
|
async () => {
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11394
|
+
return await this.runWithPostActionWait(
|
|
11395
|
+
"uploadFile",
|
|
11396
|
+
options.wait,
|
|
11397
|
+
async () => {
|
|
11398
|
+
const actionResult = await performFileUpload(
|
|
11399
|
+
this.page,
|
|
11400
|
+
path3,
|
|
11401
|
+
options.paths
|
|
11402
|
+
);
|
|
11403
|
+
if (!actionResult.ok) {
|
|
11404
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
11405
|
+
action: "uploadFile",
|
|
11406
|
+
error: actionResult.error || defaultActionFailureMessage("uploadFile"),
|
|
11407
|
+
fallbackMessage: defaultActionFailureMessage("uploadFile")
|
|
11408
|
+
});
|
|
11409
|
+
throw this.buildActionError(
|
|
11410
|
+
"uploadFile",
|
|
11411
|
+
options.description,
|
|
11412
|
+
failure,
|
|
11413
|
+
actionResult.usedSelector || null
|
|
11414
|
+
);
|
|
11415
|
+
}
|
|
11416
|
+
return actionResult;
|
|
11417
|
+
}
|
|
11165
11418
|
);
|
|
11166
|
-
if (!actionResult.ok) {
|
|
11167
|
-
const failure = actionResult.failure || classifyActionFailure({
|
|
11168
|
-
action: "uploadFile",
|
|
11169
|
-
error: actionResult.error || defaultActionFailureMessage("uploadFile"),
|
|
11170
|
-
fallbackMessage: defaultActionFailureMessage("uploadFile")
|
|
11171
|
-
});
|
|
11172
|
-
throw this.buildActionError(
|
|
11173
|
-
"uploadFile",
|
|
11174
|
-
options.description,
|
|
11175
|
-
failure,
|
|
11176
|
-
actionResult.usedSelector || null
|
|
11177
|
-
);
|
|
11178
|
-
}
|
|
11179
|
-
return actionResult;
|
|
11180
11419
|
}
|
|
11181
11420
|
);
|
|
11182
11421
|
this.snapshotCache = null;
|
|
@@ -11277,9 +11516,9 @@ var Opensteer = class _Opensteer {
|
|
|
11277
11516
|
const schemaHash = computeSchemaHash(options.schema);
|
|
11278
11517
|
let fields = await this.buildFieldTargetsFromPlan(options.plan);
|
|
11279
11518
|
if (!fields.length && options.plan.paths) {
|
|
11280
|
-
fields = Object.entries(options.plan.paths).map(([key,
|
|
11519
|
+
fields = Object.entries(options.plan.paths).map(([key, path3]) => ({
|
|
11281
11520
|
key,
|
|
11282
|
-
path: this.normalizePath(
|
|
11521
|
+
path: this.normalizePath(path3)
|
|
11283
11522
|
}));
|
|
11284
11523
|
}
|
|
11285
11524
|
if (!fields.length) {
|
|
@@ -11312,6 +11551,25 @@ var Opensteer = class _Opensteer {
|
|
|
11312
11551
|
getConfig() {
|
|
11313
11552
|
return this.config;
|
|
11314
11553
|
}
|
|
11554
|
+
setCursorEnabled(enabled) {
|
|
11555
|
+
this.getCursorController().setEnabled(enabled);
|
|
11556
|
+
}
|
|
11557
|
+
getCursorState() {
|
|
11558
|
+
const controller = this.cursorController;
|
|
11559
|
+
if (!controller) {
|
|
11560
|
+
return {
|
|
11561
|
+
enabled: this.config.cursor?.enabled === true,
|
|
11562
|
+
active: false,
|
|
11563
|
+
reason: this.config.cursor?.enabled === true ? "not_initialized" : "disabled"
|
|
11564
|
+
};
|
|
11565
|
+
}
|
|
11566
|
+
const status = controller.getStatus();
|
|
11567
|
+
return {
|
|
11568
|
+
enabled: status.enabled,
|
|
11569
|
+
active: status.active,
|
|
11570
|
+
reason: status.reason
|
|
11571
|
+
};
|
|
11572
|
+
}
|
|
11315
11573
|
getStorage() {
|
|
11316
11574
|
return this.storage;
|
|
11317
11575
|
}
|
|
@@ -11339,24 +11597,107 @@ var Opensteer = class _Opensteer {
|
|
|
11339
11597
|
this.agentExecutionInFlight = true;
|
|
11340
11598
|
try {
|
|
11341
11599
|
const options = normalizeExecuteOptions(instructionOrOptions);
|
|
11600
|
+
const cursorController = this.getCursorController();
|
|
11601
|
+
const previousCursorEnabled = cursorController.isEnabled();
|
|
11602
|
+
if (options.highlightCursor !== void 0) {
|
|
11603
|
+
cursorController.setEnabled(options.highlightCursor);
|
|
11604
|
+
}
|
|
11342
11605
|
const handler = new OpensteerCuaAgentHandler({
|
|
11343
11606
|
page: this.page,
|
|
11344
11607
|
config: resolvedAgentConfig,
|
|
11345
11608
|
client: createCuaClient(resolvedAgentConfig),
|
|
11346
|
-
|
|
11609
|
+
cursorController,
|
|
11347
11610
|
onMutatingAction: () => {
|
|
11348
11611
|
this.snapshotCache = null;
|
|
11349
11612
|
}
|
|
11350
11613
|
});
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11614
|
+
try {
|
|
11615
|
+
const result = await handler.execute(options);
|
|
11616
|
+
this.snapshotCache = null;
|
|
11617
|
+
return result;
|
|
11618
|
+
} finally {
|
|
11619
|
+
if (options.highlightCursor !== void 0) {
|
|
11620
|
+
cursorController.setEnabled(previousCursorEnabled);
|
|
11621
|
+
}
|
|
11622
|
+
}
|
|
11354
11623
|
} finally {
|
|
11355
11624
|
this.agentExecutionInFlight = false;
|
|
11356
11625
|
}
|
|
11357
11626
|
}
|
|
11358
11627
|
};
|
|
11359
11628
|
}
|
|
11629
|
+
getCursorController() {
|
|
11630
|
+
if (!this.cursorController) {
|
|
11631
|
+
this.cursorController = new CursorController({
|
|
11632
|
+
config: this.config.cursor,
|
|
11633
|
+
debug: Boolean(this.config.debug)
|
|
11634
|
+
});
|
|
11635
|
+
if (this.pageRef) {
|
|
11636
|
+
void this.cursorController.attachPage(this.pageRef);
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11639
|
+
return this.cursorController;
|
|
11640
|
+
}
|
|
11641
|
+
async runWithCursorPreview(pointResolver, intent, execute) {
|
|
11642
|
+
if (this.isCursorPreviewEnabled()) {
|
|
11643
|
+
const point = await pointResolver();
|
|
11644
|
+
await this.previewCursorPoint(point, intent);
|
|
11645
|
+
}
|
|
11646
|
+
return await execute();
|
|
11647
|
+
}
|
|
11648
|
+
isCursorPreviewEnabled() {
|
|
11649
|
+
return this.cursorController ? this.cursorController.isEnabled() : this.config.cursor?.enabled === true;
|
|
11650
|
+
}
|
|
11651
|
+
async previewCursorPoint(point, intent) {
|
|
11652
|
+
const cursor = this.getCursorController();
|
|
11653
|
+
await cursor.attachPage(this.page);
|
|
11654
|
+
await cursor.preview(point, intent);
|
|
11655
|
+
}
|
|
11656
|
+
resolveCursorPointFromBoundingBox(box, position) {
|
|
11657
|
+
if (position) {
|
|
11658
|
+
return {
|
|
11659
|
+
x: box.x + position.x,
|
|
11660
|
+
y: box.y + position.y
|
|
11661
|
+
};
|
|
11662
|
+
}
|
|
11663
|
+
return {
|
|
11664
|
+
x: box.x + box.width / 2,
|
|
11665
|
+
y: box.y + box.height / 2
|
|
11666
|
+
};
|
|
11667
|
+
}
|
|
11668
|
+
async resolveHandleTargetPoint(handle, position) {
|
|
11669
|
+
try {
|
|
11670
|
+
const box = await handle.boundingBox();
|
|
11671
|
+
if (!box) return null;
|
|
11672
|
+
return this.resolveCursorPointFromBoundingBox(box, position);
|
|
11673
|
+
} catch {
|
|
11674
|
+
return null;
|
|
11675
|
+
}
|
|
11676
|
+
}
|
|
11677
|
+
async resolvePathTargetPoint(path3, position) {
|
|
11678
|
+
if (!path3) {
|
|
11679
|
+
return null;
|
|
11680
|
+
}
|
|
11681
|
+
let resolved = null;
|
|
11682
|
+
try {
|
|
11683
|
+
resolved = await resolveElementPath(this.page, path3);
|
|
11684
|
+
return await this.resolveHandleTargetPoint(resolved.element, position);
|
|
11685
|
+
} catch {
|
|
11686
|
+
return null;
|
|
11687
|
+
} finally {
|
|
11688
|
+
await resolved?.element.dispose().catch(() => void 0);
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
async resolveViewportAnchorPoint() {
|
|
11692
|
+
const viewport = this.page.viewportSize();
|
|
11693
|
+
if (viewport?.width && viewport?.height) {
|
|
11694
|
+
return {
|
|
11695
|
+
x: viewport.width / 2,
|
|
11696
|
+
y: viewport.height / 2
|
|
11697
|
+
};
|
|
11698
|
+
}
|
|
11699
|
+
return null;
|
|
11700
|
+
}
|
|
11360
11701
|
async runWithPostActionWait(action, waitOverride, execute) {
|
|
11361
11702
|
const waitSession = createPostActionWaitSession(
|
|
11362
11703
|
this.page,
|
|
@@ -11389,13 +11730,19 @@ var Opensteer = class _Opensteer {
|
|
|
11389
11730
|
resolution.counter
|
|
11390
11731
|
);
|
|
11391
11732
|
}
|
|
11392
|
-
await this.
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11733
|
+
await this.runWithCursorPreview(
|
|
11734
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
11735
|
+
method,
|
|
11736
|
+
async () => {
|
|
11737
|
+
await this.runWithPostActionWait(method, options.wait, async () => {
|
|
11738
|
+
await handle.click({
|
|
11739
|
+
button: options.button,
|
|
11740
|
+
clickCount: options.clickCount,
|
|
11741
|
+
modifiers: options.modifiers
|
|
11742
|
+
});
|
|
11743
|
+
});
|
|
11744
|
+
}
|
|
11745
|
+
);
|
|
11399
11746
|
} catch (err) {
|
|
11400
11747
|
const failure = classifyActionFailure({
|
|
11401
11748
|
action: method,
|
|
@@ -11429,26 +11776,32 @@ var Opensteer = class _Opensteer {
|
|
|
11429
11776
|
if (!resolution.path) {
|
|
11430
11777
|
throw new Error("Unable to resolve element path for click action.");
|
|
11431
11778
|
}
|
|
11432
|
-
const
|
|
11433
|
-
const result = await this.
|
|
11779
|
+
const path3 = resolution.path;
|
|
11780
|
+
const result = await this.runWithCursorPreview(
|
|
11781
|
+
() => this.resolvePathTargetPoint(path3),
|
|
11434
11782
|
method,
|
|
11435
|
-
options.wait,
|
|
11436
11783
|
async () => {
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11784
|
+
return await this.runWithPostActionWait(
|
|
11785
|
+
method,
|
|
11786
|
+
options.wait,
|
|
11787
|
+
async () => {
|
|
11788
|
+
const actionResult = await performClick(this.page, path3, options);
|
|
11789
|
+
if (!actionResult.ok) {
|
|
11790
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
11791
|
+
action: method,
|
|
11792
|
+
error: actionResult.error || defaultActionFailureMessage(method),
|
|
11793
|
+
fallbackMessage: defaultActionFailureMessage(method)
|
|
11794
|
+
});
|
|
11795
|
+
throw this.buildActionError(
|
|
11796
|
+
method,
|
|
11797
|
+
options.description,
|
|
11798
|
+
failure,
|
|
11799
|
+
actionResult.usedSelector || null
|
|
11800
|
+
);
|
|
11801
|
+
}
|
|
11802
|
+
return actionResult;
|
|
11803
|
+
}
|
|
11804
|
+
);
|
|
11452
11805
|
}
|
|
11453
11806
|
);
|
|
11454
11807
|
this.snapshotCache = null;
|
|
@@ -11487,14 +11840,14 @@ var Opensteer = class _Opensteer {
|
|
|
11487
11840
|
};
|
|
11488
11841
|
}
|
|
11489
11842
|
if (options.selector) {
|
|
11490
|
-
const
|
|
11491
|
-
if (!
|
|
11843
|
+
const path3 = await this.buildPathFromSelector(options.selector);
|
|
11844
|
+
if (!path3) {
|
|
11492
11845
|
throw new Error(
|
|
11493
11846
|
`Unable to build element path from selector: ${options.selector}`
|
|
11494
11847
|
);
|
|
11495
11848
|
}
|
|
11496
11849
|
return {
|
|
11497
|
-
path:
|
|
11850
|
+
path: path3,
|
|
11498
11851
|
counter: null,
|
|
11499
11852
|
shouldPersist: Boolean(storageKey),
|
|
11500
11853
|
source: "selector"
|
|
@@ -11554,9 +11907,9 @@ var Opensteer = class _Opensteer {
|
|
|
11554
11907
|
counter: parsedCounter
|
|
11555
11908
|
};
|
|
11556
11909
|
}
|
|
11557
|
-
const
|
|
11558
|
-
return
|
|
11559
|
-
path:
|
|
11910
|
+
const path3 = await this.buildPathFromSelector(response);
|
|
11911
|
+
return path3 ? {
|
|
11912
|
+
path: path3
|
|
11560
11913
|
} : null;
|
|
11561
11914
|
}
|
|
11562
11915
|
if (!response || typeof response !== "object") {
|
|
@@ -11574,9 +11927,9 @@ var Opensteer = class _Opensteer {
|
|
|
11574
11927
|
};
|
|
11575
11928
|
}
|
|
11576
11929
|
if (record.selector) {
|
|
11577
|
-
const
|
|
11578
|
-
return
|
|
11579
|
-
path:
|
|
11930
|
+
const path3 = await this.buildPathFromSelector(record.selector);
|
|
11931
|
+
return path3 ? {
|
|
11932
|
+
path: path3
|
|
11580
11933
|
} : null;
|
|
11581
11934
|
}
|
|
11582
11935
|
return null;
|
|
@@ -11678,10 +12031,10 @@ var Opensteer = class _Opensteer {
|
|
|
11678
12031
|
if (fallback.nodes.length) return fallback;
|
|
11679
12032
|
return normalizedBuilt;
|
|
11680
12033
|
}
|
|
11681
|
-
async withHandleIframeContext(handle,
|
|
12034
|
+
async withHandleIframeContext(handle, path3) {
|
|
11682
12035
|
const ownFrame = await handle.ownerFrame();
|
|
11683
12036
|
if (!ownFrame) {
|
|
11684
|
-
return this.normalizePath(
|
|
12037
|
+
return this.normalizePath(path3);
|
|
11685
12038
|
}
|
|
11686
12039
|
let frame = ownFrame;
|
|
11687
12040
|
let prefix = [];
|
|
@@ -11708,11 +12061,11 @@ var Opensteer = class _Opensteer {
|
|
|
11708
12061
|
frame = parent;
|
|
11709
12062
|
}
|
|
11710
12063
|
if (!prefix.length) {
|
|
11711
|
-
return this.normalizePath(
|
|
12064
|
+
return this.normalizePath(path3);
|
|
11712
12065
|
}
|
|
11713
12066
|
return this.normalizePath({
|
|
11714
|
-
context: [...prefix, ...cloneContextHops(
|
|
11715
|
-
nodes: cloneElementPath(
|
|
12067
|
+
context: [...prefix, ...cloneContextHops(path3.context)],
|
|
12068
|
+
nodes: cloneElementPath(path3).nodes
|
|
11716
12069
|
});
|
|
11717
12070
|
}
|
|
11718
12071
|
async readPathFromCounterIndex(counter) {
|
|
@@ -11726,11 +12079,11 @@ var Opensteer = class _Opensteer {
|
|
|
11726
12079
|
return normalized;
|
|
11727
12080
|
}
|
|
11728
12081
|
async buildPathFromSelector(selector) {
|
|
11729
|
-
const
|
|
11730
|
-
if (!
|
|
11731
|
-
return this.normalizePath(
|
|
12082
|
+
const path3 = await buildElementPathFromSelector(this.page, selector);
|
|
12083
|
+
if (!path3) return null;
|
|
12084
|
+
return this.normalizePath(path3);
|
|
11732
12085
|
}
|
|
11733
|
-
persistPath(id, method, description,
|
|
12086
|
+
persistPath(id, method, description, path3) {
|
|
11734
12087
|
const now = Date.now();
|
|
11735
12088
|
const safeFile = this.storage.getSelectorFileName(id);
|
|
11736
12089
|
const existing = this.storage.readSelector(id);
|
|
@@ -11739,7 +12092,7 @@ var Opensteer = class _Opensteer {
|
|
|
11739
12092
|
id,
|
|
11740
12093
|
method,
|
|
11741
12094
|
description: description || `${method} path`,
|
|
11742
|
-
path: this.normalizePath(
|
|
12095
|
+
path: this.normalizePath(path3),
|
|
11743
12096
|
metadata: {
|
|
11744
12097
|
createdAt,
|
|
11745
12098
|
updatedAt: now,
|
|
@@ -11929,9 +12282,9 @@ var Opensteer = class _Opensteer {
|
|
|
11929
12282
|
}
|
|
11930
12283
|
let fields = await this.buildFieldTargetsFromPlan(normalized);
|
|
11931
12284
|
if (!fields.length && normalized.paths) {
|
|
11932
|
-
fields = Object.entries(normalized.paths).map(([key,
|
|
12285
|
+
fields = Object.entries(normalized.paths).map(([key, path3]) => ({
|
|
11933
12286
|
key,
|
|
11934
|
-
path: this.normalizePath(
|
|
12287
|
+
path: this.normalizePath(path3)
|
|
11935
12288
|
}));
|
|
11936
12289
|
}
|
|
11937
12290
|
if (!fields.length && normalized.data !== void 0) {
|
|
@@ -11995,13 +12348,13 @@ var Opensteer = class _Opensteer {
|
|
|
11995
12348
|
return;
|
|
11996
12349
|
}
|
|
11997
12350
|
if (normalized.selector) {
|
|
11998
|
-
const
|
|
12351
|
+
const path3 = await this.buildPathFromSelector(
|
|
11999
12352
|
normalized.selector
|
|
12000
12353
|
);
|
|
12001
|
-
if (
|
|
12354
|
+
if (path3) {
|
|
12002
12355
|
fields.push({
|
|
12003
12356
|
key: fieldKey,
|
|
12004
|
-
path:
|
|
12357
|
+
path: path3,
|
|
12005
12358
|
attribute: normalized.attribute
|
|
12006
12359
|
});
|
|
12007
12360
|
} else {
|
|
@@ -12039,11 +12392,11 @@ var Opensteer = class _Opensteer {
|
|
|
12039
12392
|
continue;
|
|
12040
12393
|
}
|
|
12041
12394
|
if (!fieldPlan.selector) continue;
|
|
12042
|
-
const
|
|
12043
|
-
if (!
|
|
12395
|
+
const path3 = await this.buildPathFromSelector(fieldPlan.selector);
|
|
12396
|
+
if (!path3) continue;
|
|
12044
12397
|
fields.push({
|
|
12045
12398
|
key,
|
|
12046
|
-
path:
|
|
12399
|
+
path: path3,
|
|
12047
12400
|
attribute: fieldPlan.attribute
|
|
12048
12401
|
});
|
|
12049
12402
|
}
|
|
@@ -12112,15 +12465,15 @@ var Opensteer = class _Opensteer {
|
|
|
12112
12465
|
});
|
|
12113
12466
|
continue;
|
|
12114
12467
|
}
|
|
12115
|
-
const
|
|
12116
|
-
if (!
|
|
12468
|
+
const path3 = await this.buildPathFromElement(field.counter);
|
|
12469
|
+
if (!path3) {
|
|
12117
12470
|
throw new Error(
|
|
12118
12471
|
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
12119
12472
|
);
|
|
12120
12473
|
}
|
|
12121
12474
|
resolved.push({
|
|
12122
12475
|
key: field.key,
|
|
12123
|
-
path:
|
|
12476
|
+
path: path3,
|
|
12124
12477
|
attribute: field.attribute
|
|
12125
12478
|
});
|
|
12126
12479
|
}
|
|
@@ -12137,10 +12490,10 @@ var Opensteer = class _Opensteer {
|
|
|
12137
12490
|
}
|
|
12138
12491
|
resolveStorageKey(description) {
|
|
12139
12492
|
if (!description) return null;
|
|
12140
|
-
return
|
|
12493
|
+
return createHash2("sha256").update(description).digest("hex").slice(0, 16);
|
|
12141
12494
|
}
|
|
12142
|
-
normalizePath(
|
|
12143
|
-
return sanitizeElementPath(
|
|
12495
|
+
normalizePath(path3) {
|
|
12496
|
+
return sanitizeElementPath(path3);
|
|
12144
12497
|
}
|
|
12145
12498
|
};
|
|
12146
12499
|
function formatActionFailureMessage(action, description, cause) {
|
|
@@ -12150,8 +12503,8 @@ function formatActionFailureMessage(action, description, cause) {
|
|
|
12150
12503
|
function cloneContextHops(context) {
|
|
12151
12504
|
return JSON.parse(JSON.stringify(context || []));
|
|
12152
12505
|
}
|
|
12153
|
-
function collectIframeContextPrefix(
|
|
12154
|
-
const context =
|
|
12506
|
+
function collectIframeContextPrefix(path3) {
|
|
12507
|
+
const context = path3.context || [];
|
|
12155
12508
|
let lastIframeIndex = -1;
|
|
12156
12509
|
for (let index = 0; index < context.length; index += 1) {
|
|
12157
12510
|
if (context[index]?.kind === "iframe") {
|
|
@@ -12209,7 +12562,7 @@ function normalizeExtractSource(source) {
|
|
|
12209
12562
|
}
|
|
12210
12563
|
function computeSchemaHash(schema) {
|
|
12211
12564
|
const stable = stableStringify(schema);
|
|
12212
|
-
return
|
|
12565
|
+
return createHash2("sha256").update(stable).digest("hex");
|
|
12213
12566
|
}
|
|
12214
12567
|
function buildPathMap(fields) {
|
|
12215
12568
|
const out = {};
|
|
@@ -12443,15 +12796,34 @@ function isInternalOrBlankPageUrl(url) {
|
|
|
12443
12796
|
if (url === "about:blank") return true;
|
|
12444
12797
|
return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
|
|
12445
12798
|
}
|
|
12799
|
+
function normalizeCloudBrowserProfilePreference(value, source) {
|
|
12800
|
+
if (!value) {
|
|
12801
|
+
return void 0;
|
|
12802
|
+
}
|
|
12803
|
+
const profileId = typeof value.profileId === "string" ? value.profileId.trim() : "";
|
|
12804
|
+
if (!profileId) {
|
|
12805
|
+
throw new Error(
|
|
12806
|
+
`Invalid cloud browser profile in ${source}: profileId must be a non-empty string.`
|
|
12807
|
+
);
|
|
12808
|
+
}
|
|
12809
|
+
if (value.reuseIfActive !== void 0 && typeof value.reuseIfActive !== "boolean") {
|
|
12810
|
+
throw new Error(
|
|
12811
|
+
`Invalid cloud browser profile in ${source}: reuseIfActive must be a boolean.`
|
|
12812
|
+
);
|
|
12813
|
+
}
|
|
12814
|
+
return {
|
|
12815
|
+
profileId,
|
|
12816
|
+
reuseIfActive: value.reuseIfActive
|
|
12817
|
+
};
|
|
12818
|
+
}
|
|
12446
12819
|
function buildLocalRunId(namespace) {
|
|
12447
12820
|
const normalized = namespace.trim() || "default";
|
|
12448
12821
|
return `${normalized}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
12449
12822
|
}
|
|
12450
12823
|
|
|
12451
12824
|
export {
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
resolveNamespaceDir,
|
|
12825
|
+
expandHome,
|
|
12826
|
+
loadCookiesFromLocalProfileDir,
|
|
12455
12827
|
waitForVisualStability,
|
|
12456
12828
|
createEmptyRegistry,
|
|
12457
12829
|
LocalSelectorStorage,
|
|
@@ -12512,13 +12884,8 @@ export {
|
|
|
12512
12884
|
performFileUpload,
|
|
12513
12885
|
OpensteerActionError,
|
|
12514
12886
|
cloudSessionContractVersion,
|
|
12515
|
-
OpensteerCloudError,
|
|
12516
|
-
cloudUnsupportedMethodError,
|
|
12517
|
-
cloudNotLaunchedError,
|
|
12518
12887
|
ActionWsClient,
|
|
12519
12888
|
collectLocalSelectorCacheEntries,
|
|
12520
|
-
CloudCdpClient,
|
|
12521
|
-
CloudSessionClient,
|
|
12522
12889
|
OpensteerAgentError,
|
|
12523
12890
|
OpensteerAgentConfigError,
|
|
12524
12891
|
OpensteerAgentProviderError,
|
|
@@ -12529,5 +12896,8 @@ export {
|
|
|
12529
12896
|
resolveAgentConfig,
|
|
12530
12897
|
createCuaClient,
|
|
12531
12898
|
OpensteerCuaAgentHandler,
|
|
12899
|
+
planSnappyCursorMotion,
|
|
12900
|
+
SvgCursorRenderer,
|
|
12901
|
+
CursorController,
|
|
12532
12902
|
Opensteer
|
|
12533
12903
|
};
|