opensteer 0.4.13 → 0.4.14
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/README.md +5 -0
- package/bin/opensteer.mjs +42 -6
- package/dist/{chunk-QTGJO7RC.js → chunk-QHZFY3ZK.js} +9 -0
- package/dist/{chunk-V4OOJO4S.js → chunk-SPHS6YWD.js} +1 -1
- package/dist/{chunk-JSH3VLMH.js → chunk-WXUXG76V.js} +404 -47
- package/dist/{chunk-UIUDSWZV.js → chunk-YIQDOALV.js} +1 -1
- package/dist/cli/server.cjs +497 -67
- package/dist/cli/server.js +90 -24
- package/dist/{extractor-I6TJPTXV.js → extractor-CZFCFUME.js} +2 -2
- package/dist/index.cjs +410 -45
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +4 -4
- package/dist/{resolver-HVZJQZ32.js → resolver-ZREUOOTV.js} +2 -2
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -109,6 +109,15 @@ function resolveProviderInfo(modelStr) {
|
|
|
109
109
|
return info;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
const slash = modelStr.indexOf("/");
|
|
113
|
+
if (slash > 0) {
|
|
114
|
+
const provider = modelStr.slice(0, slash).trim().toLowerCase();
|
|
115
|
+
if (provider) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Unsupported model provider prefix "${provider}" in model "${modelStr}". Use one of: openai, anthropic, google, xai, groq.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
112
121
|
return { pkg: "@ai-sdk/openai", providerFn: "openai" };
|
|
113
122
|
}
|
|
114
123
|
function stripProviderPrefix(modelStr) {
|
|
@@ -906,6 +915,232 @@ var import_path3 = __toESM(require("path"), 1);
|
|
|
906
915
|
var import_url = require("url");
|
|
907
916
|
var import_dotenv = require("dotenv");
|
|
908
917
|
|
|
918
|
+
// src/error-normalization.ts
|
|
919
|
+
function extractErrorMessage(error, fallback = "Unknown error.") {
|
|
920
|
+
if (error instanceof Error) {
|
|
921
|
+
const message = error.message.trim();
|
|
922
|
+
if (message) return message;
|
|
923
|
+
const name = error.name.trim();
|
|
924
|
+
if (name) return name;
|
|
925
|
+
}
|
|
926
|
+
if (typeof error === "string" && error.trim()) {
|
|
927
|
+
return error.trim();
|
|
928
|
+
}
|
|
929
|
+
const record = asRecord(error);
|
|
930
|
+
const recordMessage = toNonEmptyString(record?.message) || toNonEmptyString(record?.error);
|
|
931
|
+
if (recordMessage) {
|
|
932
|
+
return recordMessage;
|
|
933
|
+
}
|
|
934
|
+
return fallback;
|
|
935
|
+
}
|
|
936
|
+
function normalizeError(error, fallback = "Unknown error.", maxCauseDepth = 2) {
|
|
937
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
938
|
+
return normalizeErrorInternal(error, fallback, maxCauseDepth, seen);
|
|
939
|
+
}
|
|
940
|
+
function normalizeErrorInternal(error, fallback, depthRemaining, seen) {
|
|
941
|
+
const record = asRecord(error);
|
|
942
|
+
if (record) {
|
|
943
|
+
if (seen.has(record)) {
|
|
944
|
+
return {
|
|
945
|
+
message: extractErrorMessage(error, fallback)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
seen.add(record);
|
|
949
|
+
}
|
|
950
|
+
const message = extractErrorMessage(error, fallback);
|
|
951
|
+
const code = extractCode(error);
|
|
952
|
+
const name = extractName(error);
|
|
953
|
+
const details = extractDetails(error);
|
|
954
|
+
if (depthRemaining <= 0) {
|
|
955
|
+
return compactErrorInfo({
|
|
956
|
+
message,
|
|
957
|
+
...code ? { code } : {},
|
|
958
|
+
...name ? { name } : {},
|
|
959
|
+
...details ? { details } : {}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
const cause = extractCause(error);
|
|
963
|
+
if (!cause) {
|
|
964
|
+
return compactErrorInfo({
|
|
965
|
+
message,
|
|
966
|
+
...code ? { code } : {},
|
|
967
|
+
...name ? { name } : {},
|
|
968
|
+
...details ? { details } : {}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
const normalizedCause = normalizeErrorInternal(
|
|
972
|
+
cause,
|
|
973
|
+
"Caused by an unknown error.",
|
|
974
|
+
depthRemaining - 1,
|
|
975
|
+
seen
|
|
976
|
+
);
|
|
977
|
+
return compactErrorInfo({
|
|
978
|
+
message,
|
|
979
|
+
...code ? { code } : {},
|
|
980
|
+
...name ? { name } : {},
|
|
981
|
+
...details ? { details } : {},
|
|
982
|
+
cause: normalizedCause
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
function compactErrorInfo(info) {
|
|
986
|
+
const safeDetails = toJsonSafeRecord(info.details);
|
|
987
|
+
return {
|
|
988
|
+
message: info.message,
|
|
989
|
+
...info.code ? { code: info.code } : {},
|
|
990
|
+
...info.name ? { name: info.name } : {},
|
|
991
|
+
...safeDetails ? { details: safeDetails } : {},
|
|
992
|
+
...info.cause ? { cause: info.cause } : {}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function extractCode(error) {
|
|
996
|
+
const record = asRecord(error);
|
|
997
|
+
const raw = record?.code;
|
|
998
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
999
|
+
return raw.trim();
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
1002
|
+
return String(raw);
|
|
1003
|
+
}
|
|
1004
|
+
return void 0;
|
|
1005
|
+
}
|
|
1006
|
+
function extractName(error) {
|
|
1007
|
+
if (error instanceof Error && error.name.trim()) {
|
|
1008
|
+
return error.name.trim();
|
|
1009
|
+
}
|
|
1010
|
+
const record = asRecord(error);
|
|
1011
|
+
return toNonEmptyString(record?.name);
|
|
1012
|
+
}
|
|
1013
|
+
function extractDetails(error) {
|
|
1014
|
+
const record = asRecord(error);
|
|
1015
|
+
if (!record) return void 0;
|
|
1016
|
+
const details = {};
|
|
1017
|
+
const rawDetails = asRecord(record.details);
|
|
1018
|
+
if (rawDetails) {
|
|
1019
|
+
Object.assign(details, rawDetails);
|
|
1020
|
+
}
|
|
1021
|
+
const action = toNonEmptyString(record.action);
|
|
1022
|
+
if (action) {
|
|
1023
|
+
details.action = action;
|
|
1024
|
+
}
|
|
1025
|
+
const selectorUsed = toNonEmptyString(record.selectorUsed);
|
|
1026
|
+
if (selectorUsed) {
|
|
1027
|
+
details.selectorUsed = selectorUsed;
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof record.status === "number" && Number.isFinite(record.status)) {
|
|
1030
|
+
details.status = record.status;
|
|
1031
|
+
}
|
|
1032
|
+
const failure = asRecord(record.failure);
|
|
1033
|
+
if (failure) {
|
|
1034
|
+
const failureCode = toNonEmptyString(failure.code);
|
|
1035
|
+
const classificationSource = toNonEmptyString(
|
|
1036
|
+
failure.classificationSource
|
|
1037
|
+
);
|
|
1038
|
+
const failureDetails = asRecord(failure.details);
|
|
1039
|
+
if (failureCode || classificationSource || failureDetails) {
|
|
1040
|
+
details.actionFailure = {
|
|
1041
|
+
...failureCode ? { code: failureCode } : {},
|
|
1042
|
+
...classificationSource ? { classificationSource } : {},
|
|
1043
|
+
...failureDetails ? { details: failureDetails } : {}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return Object.keys(details).length ? details : void 0;
|
|
1048
|
+
}
|
|
1049
|
+
function extractCause(error) {
|
|
1050
|
+
if (error instanceof Error) {
|
|
1051
|
+
return error.cause;
|
|
1052
|
+
}
|
|
1053
|
+
const record = asRecord(error);
|
|
1054
|
+
return record?.cause;
|
|
1055
|
+
}
|
|
1056
|
+
function asRecord(value) {
|
|
1057
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
return value;
|
|
1061
|
+
}
|
|
1062
|
+
function toNonEmptyString(value) {
|
|
1063
|
+
if (typeof value !== "string") return void 0;
|
|
1064
|
+
const normalized = value.trim();
|
|
1065
|
+
return normalized.length ? normalized : void 0;
|
|
1066
|
+
}
|
|
1067
|
+
function toJsonSafeRecord(value) {
|
|
1068
|
+
if (!value) return void 0;
|
|
1069
|
+
const sanitized = toJsonSafeValue(value, /* @__PURE__ */ new WeakSet());
|
|
1070
|
+
if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
const record = sanitized;
|
|
1074
|
+
return Object.keys(record).length > 0 ? record : void 0;
|
|
1075
|
+
}
|
|
1076
|
+
function toJsonSafeValue(value, seen) {
|
|
1077
|
+
if (value === null) return null;
|
|
1078
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
1079
|
+
return value;
|
|
1080
|
+
}
|
|
1081
|
+
if (typeof value === "number") {
|
|
1082
|
+
return Number.isFinite(value) ? value : null;
|
|
1083
|
+
}
|
|
1084
|
+
if (typeof value === "bigint") {
|
|
1085
|
+
return value.toString();
|
|
1086
|
+
}
|
|
1087
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
1088
|
+
return void 0;
|
|
1089
|
+
}
|
|
1090
|
+
if (value instanceof Date) {
|
|
1091
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
1092
|
+
}
|
|
1093
|
+
if (Array.isArray(value)) {
|
|
1094
|
+
if (seen.has(value)) return "[Circular]";
|
|
1095
|
+
seen.add(value);
|
|
1096
|
+
const output = value.map((item) => {
|
|
1097
|
+
const next = toJsonSafeValue(item, seen);
|
|
1098
|
+
return next === void 0 ? null : next;
|
|
1099
|
+
});
|
|
1100
|
+
seen.delete(value);
|
|
1101
|
+
return output;
|
|
1102
|
+
}
|
|
1103
|
+
if (value instanceof Set) {
|
|
1104
|
+
if (seen.has(value)) return "[Circular]";
|
|
1105
|
+
seen.add(value);
|
|
1106
|
+
const output = Array.from(value, (item) => {
|
|
1107
|
+
const next = toJsonSafeValue(item, seen);
|
|
1108
|
+
return next === void 0 ? null : next;
|
|
1109
|
+
});
|
|
1110
|
+
seen.delete(value);
|
|
1111
|
+
return output;
|
|
1112
|
+
}
|
|
1113
|
+
if (value instanceof Map) {
|
|
1114
|
+
if (seen.has(value)) return "[Circular]";
|
|
1115
|
+
seen.add(value);
|
|
1116
|
+
const output = {};
|
|
1117
|
+
for (const [key, item] of value.entries()) {
|
|
1118
|
+
const normalizedKey = String(key);
|
|
1119
|
+
const next = toJsonSafeValue(item, seen);
|
|
1120
|
+
if (next !== void 0) {
|
|
1121
|
+
output[normalizedKey] = next;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
seen.delete(value);
|
|
1125
|
+
return output;
|
|
1126
|
+
}
|
|
1127
|
+
if (typeof value === "object") {
|
|
1128
|
+
const objectValue = value;
|
|
1129
|
+
if (seen.has(objectValue)) return "[Circular]";
|
|
1130
|
+
seen.add(objectValue);
|
|
1131
|
+
const output = {};
|
|
1132
|
+
for (const [key, item] of Object.entries(objectValue)) {
|
|
1133
|
+
const next = toJsonSafeValue(item, seen);
|
|
1134
|
+
if (next !== void 0) {
|
|
1135
|
+
output[key] = next;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
seen.delete(objectValue);
|
|
1139
|
+
return output;
|
|
1140
|
+
}
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
909
1144
|
// src/storage/namespace.ts
|
|
910
1145
|
var import_path2 = __toESM(require("path"), 1);
|
|
911
1146
|
var DEFAULT_NAMESPACE = "default";
|
|
@@ -969,11 +1204,12 @@ function dotenvFileOrder(nodeEnv) {
|
|
|
969
1204
|
files.push(".env");
|
|
970
1205
|
return files;
|
|
971
1206
|
}
|
|
972
|
-
function loadDotenvValues(rootDir, baseEnv) {
|
|
1207
|
+
function loadDotenvValues(rootDir, baseEnv, options = {}) {
|
|
973
1208
|
const values = {};
|
|
974
1209
|
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
975
1210
|
return values;
|
|
976
1211
|
}
|
|
1212
|
+
const debug = options.debug ?? parseBool(baseEnv.OPENSTEER_DEBUG) === true;
|
|
977
1213
|
const baseDir = import_path3.default.resolve(rootDir);
|
|
978
1214
|
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
979
1215
|
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
@@ -987,15 +1223,24 @@ function loadDotenvValues(rootDir, baseEnv) {
|
|
|
987
1223
|
values[key] = value;
|
|
988
1224
|
}
|
|
989
1225
|
}
|
|
990
|
-
} catch {
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
const message = extractErrorMessage(
|
|
1228
|
+
error,
|
|
1229
|
+
"Unable to read or parse dotenv file."
|
|
1230
|
+
);
|
|
1231
|
+
if (debug) {
|
|
1232
|
+
console.warn(
|
|
1233
|
+
`[opensteer] failed to load dotenv file "${filePath}": ${message}`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
991
1236
|
continue;
|
|
992
1237
|
}
|
|
993
1238
|
}
|
|
994
1239
|
return values;
|
|
995
1240
|
}
|
|
996
|
-
function resolveEnv(rootDir) {
|
|
1241
|
+
function resolveEnv(rootDir, options = {}) {
|
|
997
1242
|
const baseEnv = process.env;
|
|
998
|
-
const dotenvValues = loadDotenvValues(rootDir, baseEnv);
|
|
1243
|
+
const dotenvValues = loadDotenvValues(rootDir, baseEnv, options);
|
|
999
1244
|
return {
|
|
1000
1245
|
...dotenvValues,
|
|
1001
1246
|
...baseEnv
|
|
@@ -1039,13 +1284,22 @@ function assertNoLegacyRuntimeConfig(source, config) {
|
|
|
1039
1284
|
);
|
|
1040
1285
|
}
|
|
1041
1286
|
}
|
|
1042
|
-
function loadConfigFile(rootDir) {
|
|
1287
|
+
function loadConfigFile(rootDir, options = {}) {
|
|
1043
1288
|
const configPath = import_path3.default.join(rootDir, ".opensteer", "config.json");
|
|
1044
1289
|
if (!import_fs.default.existsSync(configPath)) return {};
|
|
1045
1290
|
try {
|
|
1046
1291
|
const raw = import_fs.default.readFileSync(configPath, "utf8");
|
|
1047
1292
|
return JSON.parse(raw);
|
|
1048
|
-
} catch {
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
const message = extractErrorMessage(
|
|
1295
|
+
error,
|
|
1296
|
+
"Unable to read or parse config file."
|
|
1297
|
+
);
|
|
1298
|
+
if (options.debug) {
|
|
1299
|
+
console.warn(
|
|
1300
|
+
`[opensteer] failed to load config file "${configPath}": ${message}`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1049
1303
|
return {};
|
|
1050
1304
|
}
|
|
1051
1305
|
}
|
|
@@ -1177,6 +1431,8 @@ function resolveCloudSelection(config, env = process.env) {
|
|
|
1177
1431
|
};
|
|
1178
1432
|
}
|
|
1179
1433
|
function resolveConfig(input = {}) {
|
|
1434
|
+
const processEnv = process.env;
|
|
1435
|
+
const debugHint = typeof input.debug === "boolean" ? input.debug : parseBool(processEnv.OPENSTEER_DEBUG) === true;
|
|
1180
1436
|
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
1181
1437
|
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
1182
1438
|
storage: {
|
|
@@ -1185,12 +1441,16 @@ function resolveConfig(input = {}) {
|
|
|
1185
1441
|
});
|
|
1186
1442
|
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1187
1443
|
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1188
|
-
const fileConfig = loadConfigFile(initialRootDir
|
|
1444
|
+
const fileConfig = loadConfigFile(initialRootDir, {
|
|
1445
|
+
debug: debugHint
|
|
1446
|
+
});
|
|
1189
1447
|
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1190
1448
|
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1191
1449
|
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1192
1450
|
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1193
|
-
const env = resolveEnv(envRootDir
|
|
1451
|
+
const env = resolveEnv(envRootDir, {
|
|
1452
|
+
debug: debugHint
|
|
1453
|
+
});
|
|
1194
1454
|
if (env.OPENSTEER_AI_MODEL) {
|
|
1195
1455
|
throw new Error(
|
|
1196
1456
|
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
@@ -1874,9 +2134,11 @@ function createEmptyRegistry(name) {
|
|
|
1874
2134
|
var LocalSelectorStorage = class {
|
|
1875
2135
|
rootDir;
|
|
1876
2136
|
namespace;
|
|
1877
|
-
|
|
2137
|
+
debug;
|
|
2138
|
+
constructor(rootDir, namespace, options = {}) {
|
|
1878
2139
|
this.rootDir = rootDir;
|
|
1879
2140
|
this.namespace = normalizeNamespace(namespace);
|
|
2141
|
+
this.debug = options.debug === true;
|
|
1880
2142
|
}
|
|
1881
2143
|
getRootDir() {
|
|
1882
2144
|
return this.rootDir;
|
|
@@ -1910,7 +2172,16 @@ var LocalSelectorStorage = class {
|
|
|
1910
2172
|
try {
|
|
1911
2173
|
const raw = import_fs2.default.readFileSync(file, "utf8");
|
|
1912
2174
|
return JSON.parse(raw);
|
|
1913
|
-
} catch {
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
const message = extractErrorMessage(
|
|
2177
|
+
error,
|
|
2178
|
+
"Unable to parse selector registry JSON."
|
|
2179
|
+
);
|
|
2180
|
+
if (this.debug) {
|
|
2181
|
+
console.warn(
|
|
2182
|
+
`[opensteer] failed to read selector registry "${file}": ${message}`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
1914
2185
|
return createEmptyRegistry(this.namespace);
|
|
1915
2186
|
}
|
|
1916
2187
|
}
|
|
@@ -1927,7 +2198,16 @@ var LocalSelectorStorage = class {
|
|
|
1927
2198
|
try {
|
|
1928
2199
|
const raw = import_fs2.default.readFileSync(file, "utf8");
|
|
1929
2200
|
return JSON.parse(raw);
|
|
1930
|
-
} catch {
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
const message = extractErrorMessage(
|
|
2203
|
+
error,
|
|
2204
|
+
"Unable to parse selector file JSON."
|
|
2205
|
+
);
|
|
2206
|
+
if (this.debug) {
|
|
2207
|
+
console.warn(
|
|
2208
|
+
`[opensteer] failed to read selector file "${file}": ${message}`
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
1931
2211
|
return null;
|
|
1932
2212
|
}
|
|
1933
2213
|
}
|
|
@@ -5177,7 +5457,7 @@ function defaultActionFailureMessage(action) {
|
|
|
5177
5457
|
function classifyActionFailure(input) {
|
|
5178
5458
|
const typed = classifyTypedError(input.error);
|
|
5179
5459
|
if (typed) return typed;
|
|
5180
|
-
const message =
|
|
5460
|
+
const message = extractErrorMessage2(input.error, input.fallbackMessage);
|
|
5181
5461
|
const fromCallLog = classifyFromPlaywrightMessage(message, input.probe);
|
|
5182
5462
|
if (fromCallLog) return fromCallLog;
|
|
5183
5463
|
const fromProbe = classifyFromProbe(input.probe);
|
|
@@ -5186,7 +5466,7 @@ function classifyActionFailure(input) {
|
|
|
5186
5466
|
if (fromHeuristic) return fromHeuristic;
|
|
5187
5467
|
return buildFailure({
|
|
5188
5468
|
code: "UNKNOWN",
|
|
5189
|
-
message: ensureMessage(input.fallbackMessage
|
|
5469
|
+
message: ensureMessage(message, input.fallbackMessage),
|
|
5190
5470
|
classificationSource: "unknown"
|
|
5191
5471
|
});
|
|
5192
5472
|
}
|
|
@@ -5446,13 +5726,22 @@ function defaultRetryableForCode(code) {
|
|
|
5446
5726
|
return true;
|
|
5447
5727
|
}
|
|
5448
5728
|
}
|
|
5449
|
-
function
|
|
5729
|
+
function extractErrorMessage2(error, fallbackMessage) {
|
|
5450
5730
|
if (error instanceof Error && error.message.trim()) {
|
|
5451
5731
|
return error.message;
|
|
5452
5732
|
}
|
|
5453
5733
|
if (typeof error === "string" && error.trim()) {
|
|
5454
5734
|
return error.trim();
|
|
5455
5735
|
}
|
|
5736
|
+
if (error && typeof error === "object" && !Array.isArray(error)) {
|
|
5737
|
+
const record = error;
|
|
5738
|
+
if (typeof record.message === "string" && record.message.trim()) {
|
|
5739
|
+
return record.message.trim();
|
|
5740
|
+
}
|
|
5741
|
+
if (typeof record.error === "string" && record.error.trim()) {
|
|
5742
|
+
return record.error.trim();
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5456
5745
|
return ensureMessage(fallbackMessage, "Action failed.");
|
|
5457
5746
|
}
|
|
5458
5747
|
function ensureMessage(value, fallback) {
|
|
@@ -7772,7 +8061,8 @@ function withTokenQuery(wsUrl, token) {
|
|
|
7772
8061
|
// src/cloud/local-cache-sync.ts
|
|
7773
8062
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
7774
8063
|
var import_path5 = __toESM(require("path"), 1);
|
|
7775
|
-
function collectLocalSelectorCacheEntries(storage) {
|
|
8064
|
+
function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
8065
|
+
const debug = options.debug === true;
|
|
7776
8066
|
const namespace = storage.getNamespace();
|
|
7777
8067
|
const namespaceDir = storage.getNamespaceDir();
|
|
7778
8068
|
if (!import_fs3.default.existsSync(namespaceDir)) return [];
|
|
@@ -7781,7 +8071,7 @@ function collectLocalSelectorCacheEntries(storage) {
|
|
|
7781
8071
|
for (const fileName of fileNames) {
|
|
7782
8072
|
if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
|
|
7783
8073
|
const filePath = import_path5.default.join(namespaceDir, fileName);
|
|
7784
|
-
const selector = readSelectorFile(filePath);
|
|
8074
|
+
const selector = readSelectorFile(filePath, debug);
|
|
7785
8075
|
if (!selector) continue;
|
|
7786
8076
|
const descriptionHash = normalizeDescriptionHash(selector.id);
|
|
7787
8077
|
const method = normalizeMethod(selector.method);
|
|
@@ -7806,11 +8096,20 @@ function collectLocalSelectorCacheEntries(storage) {
|
|
|
7806
8096
|
}
|
|
7807
8097
|
return dedupeNewest(entries);
|
|
7808
8098
|
}
|
|
7809
|
-
function readSelectorFile(filePath) {
|
|
8099
|
+
function readSelectorFile(filePath, debug) {
|
|
7810
8100
|
try {
|
|
7811
8101
|
const raw = import_fs3.default.readFileSync(filePath, "utf8");
|
|
7812
8102
|
return JSON.parse(raw);
|
|
7813
|
-
} catch {
|
|
8103
|
+
} catch (error) {
|
|
8104
|
+
const message = extractErrorMessage(
|
|
8105
|
+
error,
|
|
8106
|
+
"Unable to parse selector cache file JSON."
|
|
8107
|
+
);
|
|
8108
|
+
if (debug) {
|
|
8109
|
+
console.warn(
|
|
8110
|
+
`[opensteer] failed to read local selector cache file "${filePath}": ${message}`
|
|
8111
|
+
);
|
|
8112
|
+
}
|
|
7814
8113
|
return null;
|
|
7815
8114
|
}
|
|
7816
8115
|
}
|
|
@@ -10018,7 +10317,9 @@ var Opensteer = class _Opensteer {
|
|
|
10018
10317
|
this.aiExtract = this.createLazyExtractCallback(model);
|
|
10019
10318
|
const rootDir = resolved.storage?.rootDir || process.cwd();
|
|
10020
10319
|
this.namespace = resolveNamespace(resolved, rootDir);
|
|
10021
|
-
this.storage = new LocalSelectorStorage(rootDir, this.namespace
|
|
10320
|
+
this.storage = new LocalSelectorStorage(rootDir, this.namespace, {
|
|
10321
|
+
debug: Boolean(resolved.debug)
|
|
10322
|
+
});
|
|
10022
10323
|
this.pool = new BrowserPool(resolved.browser || {});
|
|
10023
10324
|
if (cloudSelection.cloud) {
|
|
10024
10325
|
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
@@ -10037,6 +10338,14 @@ var Opensteer = class _Opensteer {
|
|
|
10037
10338
|
this.cloud = null;
|
|
10038
10339
|
}
|
|
10039
10340
|
}
|
|
10341
|
+
logDebugError(context, error) {
|
|
10342
|
+
if (!this.config.debug) return;
|
|
10343
|
+
const normalized = normalizeError(error, "Unknown error.");
|
|
10344
|
+
const codeSuffix = normalized.code && normalized.code.trim() ? ` [${normalized.code.trim()}]` : "";
|
|
10345
|
+
console.warn(
|
|
10346
|
+
`[opensteer] ${context}: ${normalized.message}${codeSuffix}`
|
|
10347
|
+
);
|
|
10348
|
+
}
|
|
10040
10349
|
createLazyResolveCallback(model) {
|
|
10041
10350
|
let resolverPromise = null;
|
|
10042
10351
|
return async (...args) => {
|
|
@@ -10127,7 +10436,8 @@ var Opensteer = class _Opensteer {
|
|
|
10127
10436
|
let tabs;
|
|
10128
10437
|
try {
|
|
10129
10438
|
tabs = await this.invokeCloudAction("tabs", {});
|
|
10130
|
-
} catch {
|
|
10439
|
+
} catch (error) {
|
|
10440
|
+
this.logDebugError("cloud page reference sync (tabs lookup) failed", error);
|
|
10131
10441
|
return;
|
|
10132
10442
|
}
|
|
10133
10443
|
if (!tabs.length) {
|
|
@@ -10265,12 +10575,7 @@ var Opensteer = class _Opensteer {
|
|
|
10265
10575
|
try {
|
|
10266
10576
|
await this.syncLocalSelectorCacheToCloud();
|
|
10267
10577
|
} catch (error) {
|
|
10268
|
-
|
|
10269
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
10270
|
-
console.warn(
|
|
10271
|
-
`[opensteer] cloud selector cache sync failed: ${message}`
|
|
10272
|
-
);
|
|
10273
|
-
}
|
|
10578
|
+
this.logDebugError("cloud selector cache sync failed", error);
|
|
10274
10579
|
}
|
|
10275
10580
|
localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
|
|
10276
10581
|
this.cloud.localRunId = localRunId;
|
|
@@ -10302,7 +10607,12 @@ var Opensteer = class _Opensteer {
|
|
|
10302
10607
|
this.cloud.actionClient = actionClient;
|
|
10303
10608
|
this.cloud.sessionId = sessionId;
|
|
10304
10609
|
this.cloud.cloudSessionUrl = session2.cloudSessionUrl;
|
|
10305
|
-
await this.syncCloudPageRef().catch(() =>
|
|
10610
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
10611
|
+
this.logDebugError(
|
|
10612
|
+
"cloud page reference sync after launch failed",
|
|
10613
|
+
error
|
|
10614
|
+
);
|
|
10615
|
+
});
|
|
10306
10616
|
this.announceCloudSession({
|
|
10307
10617
|
sessionId: session2.sessionId,
|
|
10308
10618
|
workspaceId: session2.cloudSession.workspaceId,
|
|
@@ -10389,7 +10699,9 @@ var Opensteer = class _Opensteer {
|
|
|
10389
10699
|
}
|
|
10390
10700
|
async syncLocalSelectorCacheToCloud() {
|
|
10391
10701
|
if (!this.cloud) return;
|
|
10392
|
-
const entries = collectLocalSelectorCacheEntries(this.storage
|
|
10702
|
+
const entries = collectLocalSelectorCacheEntries(this.storage, {
|
|
10703
|
+
debug: Boolean(this.config.debug)
|
|
10704
|
+
});
|
|
10393
10705
|
if (!entries.length) return;
|
|
10394
10706
|
await this.cloud.sessionClient.importSelectorCache({
|
|
10395
10707
|
entries
|
|
@@ -10398,9 +10710,12 @@ var Opensteer = class _Opensteer {
|
|
|
10398
10710
|
async goto(url, options) {
|
|
10399
10711
|
if (this.cloud) {
|
|
10400
10712
|
await this.invokeCloudActionAndResetCache("goto", { url, options });
|
|
10401
|
-
await this.syncCloudPageRef({ expectedUrl: url }).catch(
|
|
10402
|
-
(
|
|
10403
|
-
|
|
10713
|
+
await this.syncCloudPageRef({ expectedUrl: url }).catch((error) => {
|
|
10714
|
+
this.logDebugError(
|
|
10715
|
+
"cloud page reference sync after goto failed",
|
|
10716
|
+
error
|
|
10717
|
+
);
|
|
10718
|
+
});
|
|
10404
10719
|
return;
|
|
10405
10720
|
}
|
|
10406
10721
|
const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
|
|
@@ -10912,7 +11227,12 @@ var Opensteer = class _Opensteer {
|
|
|
10912
11227
|
}
|
|
10913
11228
|
);
|
|
10914
11229
|
await this.syncCloudPageRef({ expectedUrl: result.url }).catch(
|
|
10915
|
-
() =>
|
|
11230
|
+
(error) => {
|
|
11231
|
+
this.logDebugError(
|
|
11232
|
+
"cloud page reference sync after newTab failed",
|
|
11233
|
+
error
|
|
11234
|
+
);
|
|
11235
|
+
}
|
|
10916
11236
|
);
|
|
10917
11237
|
return result;
|
|
10918
11238
|
}
|
|
@@ -10924,7 +11244,12 @@ var Opensteer = class _Opensteer {
|
|
|
10924
11244
|
async switchTab(index) {
|
|
10925
11245
|
if (this.cloud) {
|
|
10926
11246
|
await this.invokeCloudActionAndResetCache("switchTab", { index });
|
|
10927
|
-
await this.syncCloudPageRef().catch(() =>
|
|
11247
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
11248
|
+
this.logDebugError(
|
|
11249
|
+
"cloud page reference sync after switchTab failed",
|
|
11250
|
+
error
|
|
11251
|
+
);
|
|
11252
|
+
});
|
|
10928
11253
|
return;
|
|
10929
11254
|
}
|
|
10930
11255
|
const page = await switchTab(this.context, index);
|
|
@@ -10934,7 +11259,12 @@ var Opensteer = class _Opensteer {
|
|
|
10934
11259
|
async closeTab(index) {
|
|
10935
11260
|
if (this.cloud) {
|
|
10936
11261
|
await this.invokeCloudActionAndResetCache("closeTab", { index });
|
|
10937
|
-
await this.syncCloudPageRef().catch(() =>
|
|
11262
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
11263
|
+
this.logDebugError(
|
|
11264
|
+
"cloud page reference sync after closeTab failed",
|
|
11265
|
+
error
|
|
11266
|
+
);
|
|
11267
|
+
});
|
|
10938
11268
|
return;
|
|
10939
11269
|
}
|
|
10940
11270
|
const newPage = await closeTab(this.context, this.page, index);
|
|
@@ -11108,8 +11438,12 @@ var Opensteer = class _Opensteer {
|
|
|
11108
11438
|
}
|
|
11109
11439
|
return await counterFn(handle);
|
|
11110
11440
|
} catch (err) {
|
|
11111
|
-
|
|
11112
|
-
|
|
11441
|
+
if (err instanceof Error) {
|
|
11442
|
+
throw err;
|
|
11443
|
+
}
|
|
11444
|
+
throw new Error(
|
|
11445
|
+
`${method} failed. ${extractErrorMessage(err, "Unknown error.")}`
|
|
11446
|
+
);
|
|
11113
11447
|
} finally {
|
|
11114
11448
|
await handle.dispose();
|
|
11115
11449
|
}
|
|
@@ -11236,6 +11570,9 @@ var Opensteer = class _Opensteer {
|
|
|
11236
11570
|
if (this.cloud) {
|
|
11237
11571
|
return await this.invokeCloudAction("extract", options);
|
|
11238
11572
|
}
|
|
11573
|
+
if (options.schema !== void 0) {
|
|
11574
|
+
assertValidExtractSchemaRoot(options.schema);
|
|
11575
|
+
}
|
|
11239
11576
|
const storageKey = this.resolveStorageKey(options.description);
|
|
11240
11577
|
const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
|
|
11241
11578
|
const stored = storageKey ? this.storage.readSelector(storageKey) : null;
|
|
@@ -11244,7 +11581,7 @@ var Opensteer = class _Opensteer {
|
|
|
11244
11581
|
try {
|
|
11245
11582
|
payload = normalizePersistedExtractPayload(stored.path);
|
|
11246
11583
|
} catch (err) {
|
|
11247
|
-
const message = err
|
|
11584
|
+
const message = extractErrorMessage(err, "Unknown error.");
|
|
11248
11585
|
const selectorFile = storageKey ? this.storage.getSelectorPath(storageKey) : "unknown selector file";
|
|
11249
11586
|
throw new Error(
|
|
11250
11587
|
`Cached extraction selector is invalid for the current schema at "${selectorFile}". Delete the cached selector and rerun extraction. ${message}`
|
|
@@ -11261,7 +11598,16 @@ var Opensteer = class _Opensteer {
|
|
|
11261
11598
|
fields.push(...schemaFields);
|
|
11262
11599
|
}
|
|
11263
11600
|
if (!fields.length) {
|
|
11264
|
-
|
|
11601
|
+
let planResult;
|
|
11602
|
+
try {
|
|
11603
|
+
planResult = await this.parseAiExtractPlan(options);
|
|
11604
|
+
} catch (error) {
|
|
11605
|
+
const message = extractErrorMessage(error, "Unknown error.");
|
|
11606
|
+
const contextMessage = options.schema ? "Schema extraction did not resolve deterministic field targets, so Opensteer attempted AI extraction planning." : "Opensteer attempted AI extraction planning.";
|
|
11607
|
+
throw new Error(`${contextMessage} ${message}`, {
|
|
11608
|
+
cause: error
|
|
11609
|
+
});
|
|
11610
|
+
}
|
|
11265
11611
|
if (planResult.fields.length) {
|
|
11266
11612
|
fields.push(...planResult.fields);
|
|
11267
11613
|
} else if (planResult.data !== void 0) {
|
|
@@ -11923,12 +12269,6 @@ var Opensteer = class _Opensteer {
|
|
|
11923
12269
|
};
|
|
11924
12270
|
}
|
|
11925
12271
|
async buildFieldTargetsFromSchema(schema) {
|
|
11926
|
-
if (!schema || typeof schema !== "object") {
|
|
11927
|
-
return [];
|
|
11928
|
-
}
|
|
11929
|
-
if (Array.isArray(schema)) {
|
|
11930
|
-
return [];
|
|
11931
|
-
}
|
|
11932
12272
|
const fields = [];
|
|
11933
12273
|
await this.collectFieldTargetsFromSchemaObject(
|
|
11934
12274
|
schema,
|
|
@@ -12002,6 +12342,10 @@ var Opensteer = class _Opensteer {
|
|
|
12002
12342
|
path: path5,
|
|
12003
12343
|
attribute: normalized.attribute
|
|
12004
12344
|
});
|
|
12345
|
+
} else {
|
|
12346
|
+
throw new Error(
|
|
12347
|
+
`Extraction schema field "${fieldKey}" uses selector "${normalized.selector}", but no matching element path could be built from the current page snapshot.`
|
|
12348
|
+
);
|
|
12005
12349
|
}
|
|
12006
12350
|
return;
|
|
12007
12351
|
}
|
|
@@ -12352,13 +12696,28 @@ function countNonNullLeaves(value) {
|
|
|
12352
12696
|
function isPrimitiveLike(value) {
|
|
12353
12697
|
return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
12354
12698
|
}
|
|
12699
|
+
function assertValidExtractSchemaRoot(schema) {
|
|
12700
|
+
if (!schema || typeof schema !== "object") {
|
|
12701
|
+
throw new Error(
|
|
12702
|
+
"Invalid extraction schema: expected a JSON object at the top level."
|
|
12703
|
+
);
|
|
12704
|
+
}
|
|
12705
|
+
if (Array.isArray(schema)) {
|
|
12706
|
+
throw new Error(
|
|
12707
|
+
'Invalid extraction schema: top-level arrays are not supported. Wrap array fields in an object (for example {"items":[...]}).'
|
|
12708
|
+
);
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12355
12711
|
function parseAiExtractResponse(response) {
|
|
12356
12712
|
if (typeof response === "string") {
|
|
12357
12713
|
const trimmed = stripCodeFence2(response);
|
|
12358
12714
|
try {
|
|
12359
12715
|
return JSON.parse(trimmed);
|
|
12360
12716
|
} catch {
|
|
12361
|
-
|
|
12717
|
+
const preview = summarizeForError(trimmed);
|
|
12718
|
+
throw new Error(
|
|
12719
|
+
`LLM extraction returned a non-JSON response.${preview ? ` Preview: "${preview}"` : ""}`
|
|
12720
|
+
);
|
|
12362
12721
|
}
|
|
12363
12722
|
}
|
|
12364
12723
|
if (response && typeof response === "object") {
|
|
@@ -12383,6 +12742,12 @@ function stripCodeFence2(input) {
|
|
|
12383
12742
|
if (lastFence === -1) return withoutHeader.trim();
|
|
12384
12743
|
return withoutHeader.slice(0, lastFence).trim();
|
|
12385
12744
|
}
|
|
12745
|
+
function summarizeForError(input, maxLength = 180) {
|
|
12746
|
+
const compact = input.replace(/\s+/g, " ").trim();
|
|
12747
|
+
if (!compact) return "";
|
|
12748
|
+
if (compact.length <= maxLength) return compact;
|
|
12749
|
+
return `${compact.slice(0, maxLength)}...`;
|
|
12750
|
+
}
|
|
12386
12751
|
function getScrollDelta2(options) {
|
|
12387
12752
|
const amount = typeof options.amount === "number" ? options.amount : 600;
|
|
12388
12753
|
const absoluteAmount = Math.abs(amount);
|