opensteer 0.4.7 → 0.4.9
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 +2 -0
- package/README.md +5 -0
- package/dist/{chunk-PIJI7FBH.js → chunk-U6YJI5GO.js} +128 -47
- package/dist/cli/server.cjs +128 -47
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +128 -47
- package/dist/index.js +1 -1
- package/package.json +80 -73
package/CHANGELOG.md
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
- Breaking: `OPENSTEER_MODE` now uses `local` or `cloud`; `remote` is no longer a supported value.
|
|
22
22
|
- Opensteer now enables built-in LLM resolve/extract by default with model `gpt-5.1`.
|
|
23
23
|
- Cloud mode now falls back to `OPENSTEER_API_KEY` when `cloud.apiKey` is omitted.
|
|
24
|
+
- Added automatic `.env` loading from `storage.rootDir` (default `process.cwd()`) so constructor config can consume env vars without requiring `import 'dotenv/config'`.
|
|
25
|
+
- `.env` autoload follows common precedence (`.env.<NODE_ENV>.local`, `.env.local`, `.env.<NODE_ENV>`, `.env`) with `.env.local` skipped in `test`, does not overwrite existing env values, and can be disabled via `OPENSTEER_DISABLE_DOTENV_AUTOLOAD`.
|
|
24
26
|
- Mutating actions now include smart best-effort post-action wait with per-action
|
|
25
27
|
profiles and optional per-call overrides via `wait`.
|
|
26
28
|
- Added structured interaction diagnostics via `OpensteerActionError` for
|
package/README.md
CHANGED
|
@@ -164,6 +164,11 @@ Opensteer defaults to local mode.
|
|
|
164
164
|
- `OPENSTEER_MODE=local` runs local Playwright.
|
|
165
165
|
- `OPENSTEER_MODE=cloud` enables cloud mode (requires `OPENSTEER_API_KEY`).
|
|
166
166
|
- `cloud: true` in constructor config always enables cloud mode.
|
|
167
|
+
- Opensteer auto-loads `.env` files from your `storage.rootDir` (default:
|
|
168
|
+
`process.cwd()`) using this order: `.env.<NODE_ENV>.local`, `.env.local`
|
|
169
|
+
(skipped when `NODE_ENV=test`), `.env.<NODE_ENV>`, `.env`.
|
|
170
|
+
- Existing `process.env` values are never overwritten by `.env` values.
|
|
171
|
+
- Set `OPENSTEER_DISABLE_DOTENV_AUTOLOAD=true` to disable auto-loading.
|
|
167
172
|
|
|
168
173
|
Cloud mode is fail-fast: it does not automatically fall back to local mode.
|
|
169
174
|
|
|
@@ -341,7 +341,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
341
341
|
const frameRecords = await this.getFrameRecords();
|
|
342
342
|
const mainFrame = frameRecords[0];
|
|
343
343
|
if (!mainFrame) return;
|
|
344
|
-
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs
|
|
344
|
+
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
|
|
345
|
+
retryTransientContextErrors: true
|
|
346
|
+
});
|
|
345
347
|
}
|
|
346
348
|
async collectVisibleFrameIds() {
|
|
347
349
|
const frameRecords = await this.getFrameRecords();
|
|
@@ -370,19 +372,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
370
372
|
}
|
|
371
373
|
return visibleFrameIds;
|
|
372
374
|
}
|
|
373
|
-
async waitForFrameVisualStability(frameId, timeout, settleMs) {
|
|
375
|
+
async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
|
|
374
376
|
if (timeout <= 0) return;
|
|
375
377
|
const script = buildStabilityScript(timeout, settleMs);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
await this.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
|
|
379
|
+
if (!retryTransientContextErrors) {
|
|
380
|
+
let contextId = await this.ensureFrameContextId(frameId);
|
|
381
|
+
try {
|
|
382
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (!isMissingExecutionContextReferenceError(error)) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
this.contextsByFrame.delete(frameId);
|
|
388
|
+
contextId = await this.ensureFrameContextId(frameId);
|
|
389
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const deadline = Date.now() + timeout;
|
|
394
|
+
while (true) {
|
|
395
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
396
|
+
if (remaining === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const contextId = await this.ensureFrameContextId(frameId);
|
|
400
|
+
try {
|
|
401
|
+
await this.evaluateWithGuard(contextId, script, remaining);
|
|
402
|
+
return;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (!isTransientExecutionContextError(error)) {
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
this.contextsByFrame.delete(frameId);
|
|
382
408
|
}
|
|
383
|
-
this.contextsByFrame.delete(frameId);
|
|
384
|
-
contextId = await this.ensureFrameContextId(frameId);
|
|
385
|
-
await this.evaluateWithGuard(contextId, script, timeout);
|
|
386
409
|
}
|
|
387
410
|
}
|
|
388
411
|
async initialize() {
|
|
@@ -502,7 +525,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
|
|
|
502
525
|
await runtime.waitForFrameVisualStability(
|
|
503
526
|
frameId,
|
|
504
527
|
remaining,
|
|
505
|
-
settleMs
|
|
528
|
+
settleMs,
|
|
529
|
+
{
|
|
530
|
+
retryTransientContextErrors: false
|
|
531
|
+
}
|
|
506
532
|
);
|
|
507
533
|
} catch (error) {
|
|
508
534
|
if (isIgnorableFrameError(error)) return;
|
|
@@ -540,7 +566,12 @@ function sameFrameIds(before, after) {
|
|
|
540
566
|
function formatCdpException(details) {
|
|
541
567
|
return details.exception?.description || details.text || "CDP runtime evaluation failed.";
|
|
542
568
|
}
|
|
543
|
-
function
|
|
569
|
+
function isTransientExecutionContextError(error) {
|
|
570
|
+
if (!(error instanceof Error)) return false;
|
|
571
|
+
const message = error.message;
|
|
572
|
+
return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
573
|
+
}
|
|
574
|
+
function isMissingExecutionContextReferenceError(error) {
|
|
544
575
|
if (!(error instanceof Error)) return false;
|
|
545
576
|
const message = error.message;
|
|
546
577
|
return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
@@ -548,7 +579,7 @@ function isMissingExecutionContextError(error) {
|
|
|
548
579
|
function isIgnorableFrameError(error) {
|
|
549
580
|
if (!(error instanceof Error)) return false;
|
|
550
581
|
const message = error.message;
|
|
551
|
-
return message.includes("Frame was detached") || message.includes("
|
|
582
|
+
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
552
583
|
}
|
|
553
584
|
function sleep(ms) {
|
|
554
585
|
return new Promise((resolve) => {
|
|
@@ -5981,6 +6012,7 @@ var BrowserPool = class {
|
|
|
5981
6012
|
import fs3 from "fs";
|
|
5982
6013
|
import path4 from "path";
|
|
5983
6014
|
import { fileURLToPath } from "url";
|
|
6015
|
+
import { parse as parseDotenv } from "dotenv";
|
|
5984
6016
|
var DEFAULT_CONFIG = {
|
|
5985
6017
|
browser: {
|
|
5986
6018
|
headless: false,
|
|
@@ -5996,6 +6028,53 @@ var DEFAULT_CONFIG = {
|
|
|
5996
6028
|
model: "gpt-5.1",
|
|
5997
6029
|
debug: false
|
|
5998
6030
|
};
|
|
6031
|
+
function dotenvFileOrder(nodeEnv) {
|
|
6032
|
+
const normalized = nodeEnv?.trim() || "";
|
|
6033
|
+
const files = [];
|
|
6034
|
+
if (normalized) {
|
|
6035
|
+
files.push(`.env.${normalized}.local`);
|
|
6036
|
+
}
|
|
6037
|
+
if (normalized !== "test") {
|
|
6038
|
+
files.push(".env.local");
|
|
6039
|
+
}
|
|
6040
|
+
if (normalized) {
|
|
6041
|
+
files.push(`.env.${normalized}`);
|
|
6042
|
+
}
|
|
6043
|
+
files.push(".env");
|
|
6044
|
+
return files;
|
|
6045
|
+
}
|
|
6046
|
+
function loadDotenvValues(rootDir, baseEnv) {
|
|
6047
|
+
const values = {};
|
|
6048
|
+
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
6049
|
+
return values;
|
|
6050
|
+
}
|
|
6051
|
+
const baseDir = path4.resolve(rootDir);
|
|
6052
|
+
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
6053
|
+
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
6054
|
+
const filePath = path4.join(baseDir, filename);
|
|
6055
|
+
if (!fs3.existsSync(filePath)) continue;
|
|
6056
|
+
try {
|
|
6057
|
+
const raw = fs3.readFileSync(filePath, "utf8");
|
|
6058
|
+
const parsed = parseDotenv(raw);
|
|
6059
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
6060
|
+
if (values[key] === void 0) {
|
|
6061
|
+
values[key] = value;
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
} catch {
|
|
6065
|
+
continue;
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
return values;
|
|
6069
|
+
}
|
|
6070
|
+
function resolveEnv(rootDir) {
|
|
6071
|
+
const baseEnv = process.env;
|
|
6072
|
+
const dotenvValues = loadDotenvValues(rootDir, baseEnv);
|
|
6073
|
+
return {
|
|
6074
|
+
...dotenvValues,
|
|
6075
|
+
...baseEnv
|
|
6076
|
+
};
|
|
6077
|
+
}
|
|
5999
6078
|
function hasOwn(config, key) {
|
|
6000
6079
|
if (!config || typeof config !== "object") return false;
|
|
6001
6080
|
return Object.prototype.hasOwnProperty.call(config, key);
|
|
@@ -6124,16 +6203,13 @@ function parseCloudAnnounce(value, source) {
|
|
|
6124
6203
|
`Invalid ${source} value "${value}". Use "always", "off", or "tty".`
|
|
6125
6204
|
);
|
|
6126
6205
|
}
|
|
6127
|
-
function resolveOpensteerApiKey() {
|
|
6128
|
-
const value =
|
|
6206
|
+
function resolveOpensteerApiKey(env) {
|
|
6207
|
+
const value = env.OPENSTEER_API_KEY?.trim();
|
|
6129
6208
|
if (!value) return void 0;
|
|
6130
6209
|
return value;
|
|
6131
6210
|
}
|
|
6132
|
-
function resolveOpensteerAuthScheme() {
|
|
6133
|
-
return parseAuthScheme(
|
|
6134
|
-
process.env.OPENSTEER_AUTH_SCHEME,
|
|
6135
|
-
"OPENSTEER_AUTH_SCHEME"
|
|
6136
|
-
);
|
|
6211
|
+
function resolveOpensteerAuthScheme(env) {
|
|
6212
|
+
return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
|
|
6137
6213
|
}
|
|
6138
6214
|
function normalizeCloudOptions(value) {
|
|
6139
6215
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -6149,7 +6225,7 @@ function parseCloudEnabled(value, source) {
|
|
|
6149
6225
|
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
6150
6226
|
);
|
|
6151
6227
|
}
|
|
6152
|
-
function resolveCloudSelection(config) {
|
|
6228
|
+
function resolveCloudSelection(config, env = process.env) {
|
|
6153
6229
|
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
6154
6230
|
if (configCloud !== void 0) {
|
|
6155
6231
|
return {
|
|
@@ -6157,10 +6233,7 @@ function resolveCloudSelection(config) {
|
|
|
6157
6233
|
source: "config.cloud"
|
|
6158
6234
|
};
|
|
6159
6235
|
}
|
|
6160
|
-
const envMode = parseRuntimeMode(
|
|
6161
|
-
process.env.OPENSTEER_MODE,
|
|
6162
|
-
"OPENSTEER_MODE"
|
|
6163
|
-
);
|
|
6236
|
+
const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
|
|
6164
6237
|
if (envMode) {
|
|
6165
6238
|
return {
|
|
6166
6239
|
cloud: envMode === "cloud",
|
|
@@ -6173,41 +6246,49 @@ function resolveCloudSelection(config) {
|
|
|
6173
6246
|
};
|
|
6174
6247
|
}
|
|
6175
6248
|
function resolveConfig(input = {}) {
|
|
6176
|
-
|
|
6249
|
+
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
6250
|
+
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
6251
|
+
storage: {
|
|
6252
|
+
rootDir: initialRootDir
|
|
6253
|
+
}
|
|
6254
|
+
});
|
|
6255
|
+
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
6256
|
+
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
6257
|
+
const fileConfig = loadConfigFile(initialRootDir);
|
|
6258
|
+
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
6259
|
+
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
6260
|
+
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
6261
|
+
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
6262
|
+
const env = resolveEnv(envRootDir);
|
|
6263
|
+
if (env.OPENSTEER_AI_MODEL) {
|
|
6177
6264
|
throw new Error(
|
|
6178
6265
|
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
6179
6266
|
);
|
|
6180
6267
|
}
|
|
6181
|
-
if (
|
|
6268
|
+
if (env.OPENSTEER_RUNTIME != null) {
|
|
6182
6269
|
throw new Error(
|
|
6183
6270
|
"OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
|
|
6184
6271
|
);
|
|
6185
6272
|
}
|
|
6186
|
-
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
6187
|
-
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
6188
|
-
const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
|
|
6189
|
-
const fileConfig = loadConfigFile(rootDir);
|
|
6190
|
-
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
6191
|
-
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
6192
6273
|
const envConfig = {
|
|
6193
6274
|
browser: {
|
|
6194
|
-
headless: parseBool(
|
|
6195
|
-
executablePath:
|
|
6196
|
-
slowMo: parseNumber(
|
|
6197
|
-
connectUrl:
|
|
6198
|
-
channel:
|
|
6199
|
-
profileDir:
|
|
6275
|
+
headless: parseBool(env.OPENSTEER_HEADLESS),
|
|
6276
|
+
executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
|
|
6277
|
+
slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
|
|
6278
|
+
connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
|
|
6279
|
+
channel: env.OPENSTEER_CHANNEL || void 0,
|
|
6280
|
+
profileDir: env.OPENSTEER_PROFILE_DIR || void 0
|
|
6200
6281
|
},
|
|
6201
|
-
model:
|
|
6202
|
-
debug: parseBool(
|
|
6282
|
+
model: env.OPENSTEER_MODEL || void 0,
|
|
6283
|
+
debug: parseBool(env.OPENSTEER_DEBUG)
|
|
6203
6284
|
};
|
|
6204
|
-
const mergedWithFile = mergeDeep(
|
|
6285
|
+
const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
|
|
6205
6286
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
6206
6287
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
6207
|
-
const envApiKey = resolveOpensteerApiKey();
|
|
6208
|
-
const envAuthScheme = resolveOpensteerAuthScheme();
|
|
6288
|
+
const envApiKey = resolveOpensteerApiKey(env);
|
|
6289
|
+
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
6209
6290
|
const envCloudAnnounce = parseCloudAnnounce(
|
|
6210
|
-
|
|
6291
|
+
env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
6211
6292
|
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
6212
6293
|
);
|
|
6213
6294
|
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
@@ -6224,7 +6305,7 @@ function resolveConfig(input = {}) {
|
|
|
6224
6305
|
);
|
|
6225
6306
|
const cloudSelection = resolveCloudSelection({
|
|
6226
6307
|
cloud: resolved.cloud
|
|
6227
|
-
});
|
|
6308
|
+
}, env);
|
|
6228
6309
|
if (cloudSelection.cloud) {
|
|
6229
6310
|
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
6230
6311
|
const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
package/dist/cli/server.cjs
CHANGED
|
@@ -806,6 +806,7 @@ var BrowserPool = class {
|
|
|
806
806
|
var import_fs = __toESM(require("fs"), 1);
|
|
807
807
|
var import_path3 = __toESM(require("path"), 1);
|
|
808
808
|
var import_url = require("url");
|
|
809
|
+
var import_dotenv = require("dotenv");
|
|
809
810
|
|
|
810
811
|
// src/storage/namespace.ts
|
|
811
812
|
var import_path2 = __toESM(require("path"), 1);
|
|
@@ -855,6 +856,53 @@ var DEFAULT_CONFIG = {
|
|
|
855
856
|
model: "gpt-5.1",
|
|
856
857
|
debug: false
|
|
857
858
|
};
|
|
859
|
+
function dotenvFileOrder(nodeEnv) {
|
|
860
|
+
const normalized = nodeEnv?.trim() || "";
|
|
861
|
+
const files = [];
|
|
862
|
+
if (normalized) {
|
|
863
|
+
files.push(`.env.${normalized}.local`);
|
|
864
|
+
}
|
|
865
|
+
if (normalized !== "test") {
|
|
866
|
+
files.push(".env.local");
|
|
867
|
+
}
|
|
868
|
+
if (normalized) {
|
|
869
|
+
files.push(`.env.${normalized}`);
|
|
870
|
+
}
|
|
871
|
+
files.push(".env");
|
|
872
|
+
return files;
|
|
873
|
+
}
|
|
874
|
+
function loadDotenvValues(rootDir, baseEnv) {
|
|
875
|
+
const values = {};
|
|
876
|
+
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
877
|
+
return values;
|
|
878
|
+
}
|
|
879
|
+
const baseDir = import_path3.default.resolve(rootDir);
|
|
880
|
+
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
881
|
+
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
882
|
+
const filePath = import_path3.default.join(baseDir, filename);
|
|
883
|
+
if (!import_fs.default.existsSync(filePath)) continue;
|
|
884
|
+
try {
|
|
885
|
+
const raw = import_fs.default.readFileSync(filePath, "utf8");
|
|
886
|
+
const parsed = (0, import_dotenv.parse)(raw);
|
|
887
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
888
|
+
if (values[key] === void 0) {
|
|
889
|
+
values[key] = value;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return values;
|
|
897
|
+
}
|
|
898
|
+
function resolveEnv(rootDir) {
|
|
899
|
+
const baseEnv = process.env;
|
|
900
|
+
const dotenvValues = loadDotenvValues(rootDir, baseEnv);
|
|
901
|
+
return {
|
|
902
|
+
...dotenvValues,
|
|
903
|
+
...baseEnv
|
|
904
|
+
};
|
|
905
|
+
}
|
|
858
906
|
function hasOwn(config, key) {
|
|
859
907
|
if (!config || typeof config !== "object") return false;
|
|
860
908
|
return Object.prototype.hasOwnProperty.call(config, key);
|
|
@@ -983,16 +1031,13 @@ function parseCloudAnnounce(value, source) {
|
|
|
983
1031
|
`Invalid ${source} value "${value}". Use "always", "off", or "tty".`
|
|
984
1032
|
);
|
|
985
1033
|
}
|
|
986
|
-
function resolveOpensteerApiKey() {
|
|
987
|
-
const value =
|
|
1034
|
+
function resolveOpensteerApiKey(env) {
|
|
1035
|
+
const value = env.OPENSTEER_API_KEY?.trim();
|
|
988
1036
|
if (!value) return void 0;
|
|
989
1037
|
return value;
|
|
990
1038
|
}
|
|
991
|
-
function resolveOpensteerAuthScheme() {
|
|
992
|
-
return parseAuthScheme(
|
|
993
|
-
process.env.OPENSTEER_AUTH_SCHEME,
|
|
994
|
-
"OPENSTEER_AUTH_SCHEME"
|
|
995
|
-
);
|
|
1039
|
+
function resolveOpensteerAuthScheme(env) {
|
|
1040
|
+
return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
|
|
996
1041
|
}
|
|
997
1042
|
function normalizeCloudOptions(value) {
|
|
998
1043
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -1008,7 +1053,7 @@ function parseCloudEnabled(value, source) {
|
|
|
1008
1053
|
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
1009
1054
|
);
|
|
1010
1055
|
}
|
|
1011
|
-
function resolveCloudSelection(config) {
|
|
1056
|
+
function resolveCloudSelection(config, env = process.env) {
|
|
1012
1057
|
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
1013
1058
|
if (configCloud !== void 0) {
|
|
1014
1059
|
return {
|
|
@@ -1016,10 +1061,7 @@ function resolveCloudSelection(config) {
|
|
|
1016
1061
|
source: "config.cloud"
|
|
1017
1062
|
};
|
|
1018
1063
|
}
|
|
1019
|
-
const envMode = parseRuntimeMode(
|
|
1020
|
-
process.env.OPENSTEER_MODE,
|
|
1021
|
-
"OPENSTEER_MODE"
|
|
1022
|
-
);
|
|
1064
|
+
const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
|
|
1023
1065
|
if (envMode) {
|
|
1024
1066
|
return {
|
|
1025
1067
|
cloud: envMode === "cloud",
|
|
@@ -1032,41 +1074,49 @@ function resolveCloudSelection(config) {
|
|
|
1032
1074
|
};
|
|
1033
1075
|
}
|
|
1034
1076
|
function resolveConfig(input = {}) {
|
|
1035
|
-
|
|
1077
|
+
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
1078
|
+
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
1079
|
+
storage: {
|
|
1080
|
+
rootDir: initialRootDir
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1084
|
+
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1085
|
+
const fileConfig = loadConfigFile(initialRootDir);
|
|
1086
|
+
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1087
|
+
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1088
|
+
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1089
|
+
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1090
|
+
const env = resolveEnv(envRootDir);
|
|
1091
|
+
if (env.OPENSTEER_AI_MODEL) {
|
|
1036
1092
|
throw new Error(
|
|
1037
1093
|
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
1038
1094
|
);
|
|
1039
1095
|
}
|
|
1040
|
-
if (
|
|
1096
|
+
if (env.OPENSTEER_RUNTIME != null) {
|
|
1041
1097
|
throw new Error(
|
|
1042
1098
|
"OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
|
|
1043
1099
|
);
|
|
1044
1100
|
}
|
|
1045
|
-
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1046
|
-
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1047
|
-
const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
|
|
1048
|
-
const fileConfig = loadConfigFile(rootDir);
|
|
1049
|
-
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1050
|
-
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1051
1101
|
const envConfig = {
|
|
1052
1102
|
browser: {
|
|
1053
|
-
headless: parseBool(
|
|
1054
|
-
executablePath:
|
|
1055
|
-
slowMo: parseNumber(
|
|
1056
|
-
connectUrl:
|
|
1057
|
-
channel:
|
|
1058
|
-
profileDir:
|
|
1103
|
+
headless: parseBool(env.OPENSTEER_HEADLESS),
|
|
1104
|
+
executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
|
|
1105
|
+
slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
|
|
1106
|
+
connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
|
|
1107
|
+
channel: env.OPENSTEER_CHANNEL || void 0,
|
|
1108
|
+
profileDir: env.OPENSTEER_PROFILE_DIR || void 0
|
|
1059
1109
|
},
|
|
1060
|
-
model:
|
|
1061
|
-
debug: parseBool(
|
|
1110
|
+
model: env.OPENSTEER_MODEL || void 0,
|
|
1111
|
+
debug: parseBool(env.OPENSTEER_DEBUG)
|
|
1062
1112
|
};
|
|
1063
|
-
const mergedWithFile = mergeDeep(
|
|
1113
|
+
const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
|
|
1064
1114
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
1065
1115
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
1066
|
-
const envApiKey = resolveOpensteerApiKey();
|
|
1067
|
-
const envAuthScheme = resolveOpensteerAuthScheme();
|
|
1116
|
+
const envApiKey = resolveOpensteerApiKey(env);
|
|
1117
|
+
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
1068
1118
|
const envCloudAnnounce = parseCloudAnnounce(
|
|
1069
|
-
|
|
1119
|
+
env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
1070
1120
|
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
1071
1121
|
);
|
|
1072
1122
|
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
@@ -1083,7 +1133,7 @@ function resolveConfig(input = {}) {
|
|
|
1083
1133
|
);
|
|
1084
1134
|
const cloudSelection = resolveCloudSelection({
|
|
1085
1135
|
cloud: resolved.cloud
|
|
1086
|
-
});
|
|
1136
|
+
}, env);
|
|
1087
1137
|
if (cloudSelection.cloud) {
|
|
1088
1138
|
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
1089
1139
|
const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
@@ -1443,7 +1493,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1443
1493
|
const frameRecords = await this.getFrameRecords();
|
|
1444
1494
|
const mainFrame = frameRecords[0];
|
|
1445
1495
|
if (!mainFrame) return;
|
|
1446
|
-
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs
|
|
1496
|
+
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
|
|
1497
|
+
retryTransientContextErrors: true
|
|
1498
|
+
});
|
|
1447
1499
|
}
|
|
1448
1500
|
async collectVisibleFrameIds() {
|
|
1449
1501
|
const frameRecords = await this.getFrameRecords();
|
|
@@ -1472,19 +1524,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1472
1524
|
}
|
|
1473
1525
|
return visibleFrameIds;
|
|
1474
1526
|
}
|
|
1475
|
-
async waitForFrameVisualStability(frameId, timeout, settleMs) {
|
|
1527
|
+
async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
|
|
1476
1528
|
if (timeout <= 0) return;
|
|
1477
1529
|
const script = buildStabilityScript(timeout, settleMs);
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
await this.
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1530
|
+
const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
|
|
1531
|
+
if (!retryTransientContextErrors) {
|
|
1532
|
+
let contextId = await this.ensureFrameContextId(frameId);
|
|
1533
|
+
try {
|
|
1534
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
if (!isMissingExecutionContextReferenceError(error)) {
|
|
1537
|
+
throw error;
|
|
1538
|
+
}
|
|
1539
|
+
this.contextsByFrame.delete(frameId);
|
|
1540
|
+
contextId = await this.ensureFrameContextId(frameId);
|
|
1541
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1542
|
+
}
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
const deadline = Date.now() + timeout;
|
|
1546
|
+
while (true) {
|
|
1547
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
1548
|
+
if (remaining === 0) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const contextId = await this.ensureFrameContextId(frameId);
|
|
1552
|
+
try {
|
|
1553
|
+
await this.evaluateWithGuard(contextId, script, remaining);
|
|
1554
|
+
return;
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
if (!isTransientExecutionContextError(error)) {
|
|
1557
|
+
throw error;
|
|
1558
|
+
}
|
|
1559
|
+
this.contextsByFrame.delete(frameId);
|
|
1484
1560
|
}
|
|
1485
|
-
this.contextsByFrame.delete(frameId);
|
|
1486
|
-
contextId = await this.ensureFrameContextId(frameId);
|
|
1487
|
-
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1488
1561
|
}
|
|
1489
1562
|
}
|
|
1490
1563
|
async initialize() {
|
|
@@ -1604,7 +1677,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
|
|
|
1604
1677
|
await runtime.waitForFrameVisualStability(
|
|
1605
1678
|
frameId,
|
|
1606
1679
|
remaining,
|
|
1607
|
-
settleMs
|
|
1680
|
+
settleMs,
|
|
1681
|
+
{
|
|
1682
|
+
retryTransientContextErrors: false
|
|
1683
|
+
}
|
|
1608
1684
|
);
|
|
1609
1685
|
} catch (error) {
|
|
1610
1686
|
if (isIgnorableFrameError(error)) return;
|
|
@@ -1642,7 +1718,12 @@ function sameFrameIds(before, after) {
|
|
|
1642
1718
|
function formatCdpException(details) {
|
|
1643
1719
|
return details.exception?.description || details.text || "CDP runtime evaluation failed.";
|
|
1644
1720
|
}
|
|
1645
|
-
function
|
|
1721
|
+
function isTransientExecutionContextError(error) {
|
|
1722
|
+
if (!(error instanceof Error)) return false;
|
|
1723
|
+
const message = error.message;
|
|
1724
|
+
return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
1725
|
+
}
|
|
1726
|
+
function isMissingExecutionContextReferenceError(error) {
|
|
1646
1727
|
if (!(error instanceof Error)) return false;
|
|
1647
1728
|
const message = error.message;
|
|
1648
1729
|
return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
@@ -1650,7 +1731,7 @@ function isMissingExecutionContextError(error) {
|
|
|
1650
1731
|
function isIgnorableFrameError(error) {
|
|
1651
1732
|
if (!(error instanceof Error)) return false;
|
|
1652
1733
|
const message = error.message;
|
|
1653
|
-
return message.includes("Frame was detached") || message.includes("
|
|
1734
|
+
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
1654
1735
|
}
|
|
1655
1736
|
function sleep(ms) {
|
|
1656
1737
|
return new Promise((resolve) => {
|
package/dist/cli/server.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -881,6 +881,7 @@ var BrowserPool = class {
|
|
|
881
881
|
var import_fs = __toESM(require("fs"), 1);
|
|
882
882
|
var import_path3 = __toESM(require("path"), 1);
|
|
883
883
|
var import_url = require("url");
|
|
884
|
+
var import_dotenv = require("dotenv");
|
|
884
885
|
|
|
885
886
|
// src/storage/namespace.ts
|
|
886
887
|
var import_path2 = __toESM(require("path"), 1);
|
|
@@ -930,6 +931,53 @@ var DEFAULT_CONFIG = {
|
|
|
930
931
|
model: "gpt-5.1",
|
|
931
932
|
debug: false
|
|
932
933
|
};
|
|
934
|
+
function dotenvFileOrder(nodeEnv) {
|
|
935
|
+
const normalized = nodeEnv?.trim() || "";
|
|
936
|
+
const files = [];
|
|
937
|
+
if (normalized) {
|
|
938
|
+
files.push(`.env.${normalized}.local`);
|
|
939
|
+
}
|
|
940
|
+
if (normalized !== "test") {
|
|
941
|
+
files.push(".env.local");
|
|
942
|
+
}
|
|
943
|
+
if (normalized) {
|
|
944
|
+
files.push(`.env.${normalized}`);
|
|
945
|
+
}
|
|
946
|
+
files.push(".env");
|
|
947
|
+
return files;
|
|
948
|
+
}
|
|
949
|
+
function loadDotenvValues(rootDir, baseEnv) {
|
|
950
|
+
const values = {};
|
|
951
|
+
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
952
|
+
return values;
|
|
953
|
+
}
|
|
954
|
+
const baseDir = import_path3.default.resolve(rootDir);
|
|
955
|
+
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
956
|
+
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
957
|
+
const filePath = import_path3.default.join(baseDir, filename);
|
|
958
|
+
if (!import_fs.default.existsSync(filePath)) continue;
|
|
959
|
+
try {
|
|
960
|
+
const raw = import_fs.default.readFileSync(filePath, "utf8");
|
|
961
|
+
const parsed = (0, import_dotenv.parse)(raw);
|
|
962
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
963
|
+
if (values[key] === void 0) {
|
|
964
|
+
values[key] = value;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return values;
|
|
972
|
+
}
|
|
973
|
+
function resolveEnv(rootDir) {
|
|
974
|
+
const baseEnv = process.env;
|
|
975
|
+
const dotenvValues = loadDotenvValues(rootDir, baseEnv);
|
|
976
|
+
return {
|
|
977
|
+
...dotenvValues,
|
|
978
|
+
...baseEnv
|
|
979
|
+
};
|
|
980
|
+
}
|
|
933
981
|
function hasOwn(config, key) {
|
|
934
982
|
if (!config || typeof config !== "object") return false;
|
|
935
983
|
return Object.prototype.hasOwnProperty.call(config, key);
|
|
@@ -1058,16 +1106,13 @@ function parseCloudAnnounce(value, source) {
|
|
|
1058
1106
|
`Invalid ${source} value "${value}". Use "always", "off", or "tty".`
|
|
1059
1107
|
);
|
|
1060
1108
|
}
|
|
1061
|
-
function resolveOpensteerApiKey() {
|
|
1062
|
-
const value =
|
|
1109
|
+
function resolveOpensteerApiKey(env) {
|
|
1110
|
+
const value = env.OPENSTEER_API_KEY?.trim();
|
|
1063
1111
|
if (!value) return void 0;
|
|
1064
1112
|
return value;
|
|
1065
1113
|
}
|
|
1066
|
-
function resolveOpensteerAuthScheme() {
|
|
1067
|
-
return parseAuthScheme(
|
|
1068
|
-
process.env.OPENSTEER_AUTH_SCHEME,
|
|
1069
|
-
"OPENSTEER_AUTH_SCHEME"
|
|
1070
|
-
);
|
|
1114
|
+
function resolveOpensteerAuthScheme(env) {
|
|
1115
|
+
return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
|
|
1071
1116
|
}
|
|
1072
1117
|
function normalizeCloudOptions(value) {
|
|
1073
1118
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -1083,7 +1128,7 @@ function parseCloudEnabled(value, source) {
|
|
|
1083
1128
|
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
1084
1129
|
);
|
|
1085
1130
|
}
|
|
1086
|
-
function resolveCloudSelection(config) {
|
|
1131
|
+
function resolveCloudSelection(config, env = process.env) {
|
|
1087
1132
|
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
1088
1133
|
if (configCloud !== void 0) {
|
|
1089
1134
|
return {
|
|
@@ -1091,10 +1136,7 @@ function resolveCloudSelection(config) {
|
|
|
1091
1136
|
source: "config.cloud"
|
|
1092
1137
|
};
|
|
1093
1138
|
}
|
|
1094
|
-
const envMode = parseRuntimeMode(
|
|
1095
|
-
process.env.OPENSTEER_MODE,
|
|
1096
|
-
"OPENSTEER_MODE"
|
|
1097
|
-
);
|
|
1139
|
+
const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
|
|
1098
1140
|
if (envMode) {
|
|
1099
1141
|
return {
|
|
1100
1142
|
cloud: envMode === "cloud",
|
|
@@ -1107,41 +1149,49 @@ function resolveCloudSelection(config) {
|
|
|
1107
1149
|
};
|
|
1108
1150
|
}
|
|
1109
1151
|
function resolveConfig(input = {}) {
|
|
1110
|
-
|
|
1152
|
+
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
1153
|
+
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
1154
|
+
storage: {
|
|
1155
|
+
rootDir: initialRootDir
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1159
|
+
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1160
|
+
const fileConfig = loadConfigFile(initialRootDir);
|
|
1161
|
+
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1162
|
+
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1163
|
+
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1164
|
+
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1165
|
+
const env = resolveEnv(envRootDir);
|
|
1166
|
+
if (env.OPENSTEER_AI_MODEL) {
|
|
1111
1167
|
throw new Error(
|
|
1112
1168
|
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
1113
1169
|
);
|
|
1114
1170
|
}
|
|
1115
|
-
if (
|
|
1171
|
+
if (env.OPENSTEER_RUNTIME != null) {
|
|
1116
1172
|
throw new Error(
|
|
1117
1173
|
"OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
|
|
1118
1174
|
);
|
|
1119
1175
|
}
|
|
1120
|
-
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1121
|
-
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1122
|
-
const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
|
|
1123
|
-
const fileConfig = loadConfigFile(rootDir);
|
|
1124
|
-
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1125
|
-
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1126
1176
|
const envConfig = {
|
|
1127
1177
|
browser: {
|
|
1128
|
-
headless: parseBool(
|
|
1129
|
-
executablePath:
|
|
1130
|
-
slowMo: parseNumber(
|
|
1131
|
-
connectUrl:
|
|
1132
|
-
channel:
|
|
1133
|
-
profileDir:
|
|
1178
|
+
headless: parseBool(env.OPENSTEER_HEADLESS),
|
|
1179
|
+
executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
|
|
1180
|
+
slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
|
|
1181
|
+
connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
|
|
1182
|
+
channel: env.OPENSTEER_CHANNEL || void 0,
|
|
1183
|
+
profileDir: env.OPENSTEER_PROFILE_DIR || void 0
|
|
1134
1184
|
},
|
|
1135
|
-
model:
|
|
1136
|
-
debug: parseBool(
|
|
1185
|
+
model: env.OPENSTEER_MODEL || void 0,
|
|
1186
|
+
debug: parseBool(env.OPENSTEER_DEBUG)
|
|
1137
1187
|
};
|
|
1138
|
-
const mergedWithFile = mergeDeep(
|
|
1188
|
+
const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
|
|
1139
1189
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
1140
1190
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
1141
|
-
const envApiKey = resolveOpensteerApiKey();
|
|
1142
|
-
const envAuthScheme = resolveOpensteerAuthScheme();
|
|
1191
|
+
const envApiKey = resolveOpensteerApiKey(env);
|
|
1192
|
+
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
1143
1193
|
const envCloudAnnounce = parseCloudAnnounce(
|
|
1144
|
-
|
|
1194
|
+
env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
1145
1195
|
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
1146
1196
|
);
|
|
1147
1197
|
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
@@ -1158,7 +1208,7 @@ function resolveConfig(input = {}) {
|
|
|
1158
1208
|
);
|
|
1159
1209
|
const cloudSelection = resolveCloudSelection({
|
|
1160
1210
|
cloud: resolved.cloud
|
|
1161
|
-
});
|
|
1211
|
+
}, env);
|
|
1162
1212
|
if (cloudSelection.cloud) {
|
|
1163
1213
|
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
1164
1214
|
const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
@@ -1518,7 +1568,9 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1518
1568
|
const frameRecords = await this.getFrameRecords();
|
|
1519
1569
|
const mainFrame = frameRecords[0];
|
|
1520
1570
|
if (!mainFrame) return;
|
|
1521
|
-
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs
|
|
1571
|
+
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs, {
|
|
1572
|
+
retryTransientContextErrors: true
|
|
1573
|
+
});
|
|
1522
1574
|
}
|
|
1523
1575
|
async collectVisibleFrameIds() {
|
|
1524
1576
|
const frameRecords = await this.getFrameRecords();
|
|
@@ -1547,19 +1599,40 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1547
1599
|
}
|
|
1548
1600
|
return visibleFrameIds;
|
|
1549
1601
|
}
|
|
1550
|
-
async waitForFrameVisualStability(frameId, timeout, settleMs) {
|
|
1602
|
+
async waitForFrameVisualStability(frameId, timeout, settleMs, options = {}) {
|
|
1551
1603
|
if (timeout <= 0) return;
|
|
1552
1604
|
const script = buildStabilityScript(timeout, settleMs);
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
await this.
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1605
|
+
const retryTransientContextErrors = options.retryTransientContextErrors ?? true;
|
|
1606
|
+
if (!retryTransientContextErrors) {
|
|
1607
|
+
let contextId = await this.ensureFrameContextId(frameId);
|
|
1608
|
+
try {
|
|
1609
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
if (!isMissingExecutionContextReferenceError(error)) {
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
this.contextsByFrame.delete(frameId);
|
|
1615
|
+
contextId = await this.ensureFrameContextId(frameId);
|
|
1616
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1617
|
+
}
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const deadline = Date.now() + timeout;
|
|
1621
|
+
while (true) {
|
|
1622
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
1623
|
+
if (remaining === 0) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const contextId = await this.ensureFrameContextId(frameId);
|
|
1627
|
+
try {
|
|
1628
|
+
await this.evaluateWithGuard(contextId, script, remaining);
|
|
1629
|
+
return;
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
if (!isTransientExecutionContextError(error)) {
|
|
1632
|
+
throw error;
|
|
1633
|
+
}
|
|
1634
|
+
this.contextsByFrame.delete(frameId);
|
|
1559
1635
|
}
|
|
1560
|
-
this.contextsByFrame.delete(frameId);
|
|
1561
|
-
contextId = await this.ensureFrameContextId(frameId);
|
|
1562
|
-
await this.evaluateWithGuard(contextId, script, timeout);
|
|
1563
1636
|
}
|
|
1564
1637
|
}
|
|
1565
1638
|
async initialize() {
|
|
@@ -1679,7 +1752,10 @@ async function waitForVisualStabilityAcrossFrames(page, options = {}) {
|
|
|
1679
1752
|
await runtime.waitForFrameVisualStability(
|
|
1680
1753
|
frameId,
|
|
1681
1754
|
remaining,
|
|
1682
|
-
settleMs
|
|
1755
|
+
settleMs,
|
|
1756
|
+
{
|
|
1757
|
+
retryTransientContextErrors: false
|
|
1758
|
+
}
|
|
1683
1759
|
);
|
|
1684
1760
|
} catch (error) {
|
|
1685
1761
|
if (isIgnorableFrameError(error)) return;
|
|
@@ -1717,7 +1793,12 @@ function sameFrameIds(before, after) {
|
|
|
1717
1793
|
function formatCdpException(details) {
|
|
1718
1794
|
return details.exception?.description || details.text || "CDP runtime evaluation failed.";
|
|
1719
1795
|
}
|
|
1720
|
-
function
|
|
1796
|
+
function isTransientExecutionContextError(error) {
|
|
1797
|
+
if (!(error instanceof Error)) return false;
|
|
1798
|
+
const message = error.message;
|
|
1799
|
+
return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
1800
|
+
}
|
|
1801
|
+
function isMissingExecutionContextReferenceError(error) {
|
|
1721
1802
|
if (!(error instanceof Error)) return false;
|
|
1722
1803
|
const message = error.message;
|
|
1723
1804
|
return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
@@ -1725,7 +1806,7 @@ function isMissingExecutionContextError(error) {
|
|
|
1725
1806
|
function isIgnorableFrameError(error) {
|
|
1726
1807
|
if (!(error instanceof Error)) return false;
|
|
1727
1808
|
const message = error.message;
|
|
1728
|
-
return message.includes("Frame was detached") || message.includes("
|
|
1809
|
+
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
1729
1810
|
}
|
|
1730
1811
|
function sleep(ms) {
|
|
1731
1812
|
return new Promise((resolve) => {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,75 +1,82 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
"name": "opensteer",
|
|
3
|
+
"version": "0.4.9",
|
|
4
|
+
"packageManager": "pnpm@10.29.3",
|
|
5
|
+
"description": "Open-source browser automation SDK with robust selectors and deterministic replay.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"opensteer": "./bin/opensteer.mjs"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"bin",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"CHANGELOG.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup src/index.ts src/cli/server.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq",
|
|
30
|
+
"prepublishOnly": "pnpm run build",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:live-web": "vitest run --config vitest.live-web.config.ts",
|
|
33
|
+
"test:unit": "vitest run tests/html tests/element-path tests/config.test.ts tests/storage",
|
|
34
|
+
"test:actions": "vitest run tests/actions",
|
|
35
|
+
"test:integration": "vitest run tests/integration",
|
|
36
|
+
"test:ai": "vitest run tests/ai tests/e2e/ai-resolve.test.ts tests/e2e/ai-extract-products.test.ts",
|
|
37
|
+
"test:e2e": "vitest run tests/e2e",
|
|
38
|
+
"test:app:dev": "pnpm --dir tests/test-app run dev",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@ai-sdk/anthropic": "^3.0.46",
|
|
43
|
+
"@ai-sdk/google": "^3.0.30",
|
|
44
|
+
"@ai-sdk/groq": "^3.0.24",
|
|
45
|
+
"@ai-sdk/openai": "^3.0.26",
|
|
46
|
+
"@ai-sdk/xai": "^3.0.57",
|
|
47
|
+
"ai": "^6.0.77",
|
|
48
|
+
"cheerio": "^1.0.0-rc.12",
|
|
49
|
+
"dotenv": "^17.2.4",
|
|
50
|
+
"playwright": "^1.50.0",
|
|
51
|
+
"ws": "^8.18.0",
|
|
52
|
+
"zod": "^4.3.6"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.10.0",
|
|
56
|
+
"@types/ws": "^8.5.13",
|
|
57
|
+
"domhandler": "^5.0.3",
|
|
58
|
+
"tsup": "^8.0.1",
|
|
59
|
+
"typescript": "^5.6.3",
|
|
60
|
+
"vite": "^7.3.1",
|
|
61
|
+
"vitest": "^2.1.8"
|
|
62
|
+
},
|
|
63
|
+
"pnpm": {
|
|
64
|
+
"onlyBuiltDependencies": [
|
|
65
|
+
"esbuild"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
70
|
+
},
|
|
71
|
+
"keywords": [
|
|
72
|
+
"browser-automation",
|
|
73
|
+
"selectors",
|
|
74
|
+
"playwright",
|
|
75
|
+
"llm",
|
|
76
|
+
"agents"
|
|
77
|
+
],
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "https://github.com/opensteer-ai/opensteer"
|
|
18
81
|
}
|
|
19
|
-
|
|
20
|
-
"files": [
|
|
21
|
-
"dist",
|
|
22
|
-
"bin",
|
|
23
|
-
"README.md",
|
|
24
|
-
"LICENSE",
|
|
25
|
-
"CHANGELOG.md"
|
|
26
|
-
],
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@ai-sdk/anthropic": "^3.0.46",
|
|
29
|
-
"@ai-sdk/google": "^3.0.30",
|
|
30
|
-
"@ai-sdk/groq": "^3.0.24",
|
|
31
|
-
"@ai-sdk/openai": "^3.0.26",
|
|
32
|
-
"@ai-sdk/xai": "^3.0.57",
|
|
33
|
-
"ai": "^6.0.77",
|
|
34
|
-
"cheerio": "^1.0.0-rc.12",
|
|
35
|
-
"dotenv": "^17.2.4",
|
|
36
|
-
"playwright": "^1.50.0",
|
|
37
|
-
"ws": "^8.18.0",
|
|
38
|
-
"zod": "^4.3.6"
|
|
39
|
-
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@types/node": "^22.10.0",
|
|
42
|
-
"@types/ws": "^8.5.13",
|
|
43
|
-
"domhandler": "^5.0.3",
|
|
44
|
-
"tsup": "^8.0.1",
|
|
45
|
-
"typescript": "^5.6.3",
|
|
46
|
-
"vite": "^7.3.1",
|
|
47
|
-
"vitest": "^2.1.8"
|
|
48
|
-
},
|
|
49
|
-
"engines": {
|
|
50
|
-
"node": ">=20"
|
|
51
|
-
},
|
|
52
|
-
"keywords": [
|
|
53
|
-
"browser-automation",
|
|
54
|
-
"selectors",
|
|
55
|
-
"playwright",
|
|
56
|
-
"llm",
|
|
57
|
-
"agents"
|
|
58
|
-
],
|
|
59
|
-
"repository": {
|
|
60
|
-
"type": "git",
|
|
61
|
-
"url": "https://github.com/opensteer-ai/opensteer"
|
|
62
|
-
},
|
|
63
|
-
"scripts": {
|
|
64
|
-
"build": "tsup src/index.ts src/cli/server.ts --dts --format esm,cjs --clean --external ai --external zod --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/google --external @ai-sdk/xai --external @ai-sdk/groq",
|
|
65
|
-
"test": "vitest run",
|
|
66
|
-
"test:live-web": "vitest run --config vitest.live-web.config.ts",
|
|
67
|
-
"test:unit": "vitest run tests/html tests/element-path tests/config.test.ts tests/storage",
|
|
68
|
-
"test:actions": "vitest run tests/actions",
|
|
69
|
-
"test:integration": "vitest run tests/integration",
|
|
70
|
-
"test:ai": "vitest run tests/ai tests/e2e/ai-resolve.test.ts tests/e2e/ai-extract-products.test.ts",
|
|
71
|
-
"test:e2e": "vitest run tests/e2e",
|
|
72
|
-
"test:app:dev": "pnpm --dir tests/test-app run dev",
|
|
73
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
82
|
+
}
|