opensteer 0.4.13 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-SXPIGCSD.js} +1143 -939
- package/dist/{chunk-UIUDSWZV.js → chunk-YIQDOALV.js} +1 -1
- package/dist/cli/server.cjs +1454 -1176
- package/dist/cli/server.js +90 -24
- package/dist/{extractor-I6TJPTXV.js → extractor-CZFCFUME.js} +2 -2
- package/dist/index.cjs +1372 -1161
- package/dist/index.d.cts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.js +4 -6
- package/dist/{resolver-HVZJQZ32.js → resolver-ZREUOOTV.js} +2 -2
- package/package.json +3 -3
package/dist/cli/server.cjs
CHANGED
|
@@ -108,6 +108,15 @@ function resolveProviderInfo(modelStr) {
|
|
|
108
108
|
return info;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
const slash = modelStr.indexOf("/");
|
|
112
|
+
if (slash > 0) {
|
|
113
|
+
const provider = modelStr.slice(0, slash).trim().toLowerCase();
|
|
114
|
+
if (provider) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Unsupported model provider prefix "${provider}" in model "${modelStr}". Use one of: openai, anthropic, google, xai, groq.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
111
120
|
return { pkg: "@ai-sdk/openai", providerFn: "openai" };
|
|
112
121
|
}
|
|
113
122
|
function stripProviderPrefix(modelStr) {
|
|
@@ -351,7 +360,7 @@ var import_net = require("net");
|
|
|
351
360
|
var import_fs4 = require("fs");
|
|
352
361
|
|
|
353
362
|
// src/opensteer.ts
|
|
354
|
-
var
|
|
363
|
+
var import_crypto = require("crypto");
|
|
355
364
|
|
|
356
365
|
// src/browser/pool.ts
|
|
357
366
|
var import_playwright = require("playwright");
|
|
@@ -821,6 +830,232 @@ var import_path3 = __toESM(require("path"), 1);
|
|
|
821
830
|
var import_url = require("url");
|
|
822
831
|
var import_dotenv = require("dotenv");
|
|
823
832
|
|
|
833
|
+
// src/error-normalization.ts
|
|
834
|
+
function extractErrorMessage(error, fallback = "Unknown error.") {
|
|
835
|
+
if (error instanceof Error) {
|
|
836
|
+
const message = error.message.trim();
|
|
837
|
+
if (message) return message;
|
|
838
|
+
const name = error.name.trim();
|
|
839
|
+
if (name) return name;
|
|
840
|
+
}
|
|
841
|
+
if (typeof error === "string" && error.trim()) {
|
|
842
|
+
return error.trim();
|
|
843
|
+
}
|
|
844
|
+
const record = asRecord(error);
|
|
845
|
+
const recordMessage = toNonEmptyString(record?.message) || toNonEmptyString(record?.error);
|
|
846
|
+
if (recordMessage) {
|
|
847
|
+
return recordMessage;
|
|
848
|
+
}
|
|
849
|
+
return fallback;
|
|
850
|
+
}
|
|
851
|
+
function normalizeError(error, fallback = "Unknown error.", maxCauseDepth = 2) {
|
|
852
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
853
|
+
return normalizeErrorInternal(error, fallback, maxCauseDepth, seen);
|
|
854
|
+
}
|
|
855
|
+
function normalizeErrorInternal(error, fallback, depthRemaining, seen) {
|
|
856
|
+
const record = asRecord(error);
|
|
857
|
+
if (record) {
|
|
858
|
+
if (seen.has(record)) {
|
|
859
|
+
return {
|
|
860
|
+
message: extractErrorMessage(error, fallback)
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
seen.add(record);
|
|
864
|
+
}
|
|
865
|
+
const message = extractErrorMessage(error, fallback);
|
|
866
|
+
const code = extractCode(error);
|
|
867
|
+
const name = extractName(error);
|
|
868
|
+
const details = extractDetails(error);
|
|
869
|
+
if (depthRemaining <= 0) {
|
|
870
|
+
return compactErrorInfo({
|
|
871
|
+
message,
|
|
872
|
+
...code ? { code } : {},
|
|
873
|
+
...name ? { name } : {},
|
|
874
|
+
...details ? { details } : {}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
const cause = extractCause(error);
|
|
878
|
+
if (!cause) {
|
|
879
|
+
return compactErrorInfo({
|
|
880
|
+
message,
|
|
881
|
+
...code ? { code } : {},
|
|
882
|
+
...name ? { name } : {},
|
|
883
|
+
...details ? { details } : {}
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
const normalizedCause = normalizeErrorInternal(
|
|
887
|
+
cause,
|
|
888
|
+
"Caused by an unknown error.",
|
|
889
|
+
depthRemaining - 1,
|
|
890
|
+
seen
|
|
891
|
+
);
|
|
892
|
+
return compactErrorInfo({
|
|
893
|
+
message,
|
|
894
|
+
...code ? { code } : {},
|
|
895
|
+
...name ? { name } : {},
|
|
896
|
+
...details ? { details } : {},
|
|
897
|
+
cause: normalizedCause
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function compactErrorInfo(info) {
|
|
901
|
+
const safeDetails = toJsonSafeRecord(info.details);
|
|
902
|
+
return {
|
|
903
|
+
message: info.message,
|
|
904
|
+
...info.code ? { code: info.code } : {},
|
|
905
|
+
...info.name ? { name: info.name } : {},
|
|
906
|
+
...safeDetails ? { details: safeDetails } : {},
|
|
907
|
+
...info.cause ? { cause: info.cause } : {}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function extractCode(error) {
|
|
911
|
+
const record = asRecord(error);
|
|
912
|
+
const raw = record?.code;
|
|
913
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
914
|
+
return raw.trim();
|
|
915
|
+
}
|
|
916
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
917
|
+
return String(raw);
|
|
918
|
+
}
|
|
919
|
+
return void 0;
|
|
920
|
+
}
|
|
921
|
+
function extractName(error) {
|
|
922
|
+
if (error instanceof Error && error.name.trim()) {
|
|
923
|
+
return error.name.trim();
|
|
924
|
+
}
|
|
925
|
+
const record = asRecord(error);
|
|
926
|
+
return toNonEmptyString(record?.name);
|
|
927
|
+
}
|
|
928
|
+
function extractDetails(error) {
|
|
929
|
+
const record = asRecord(error);
|
|
930
|
+
if (!record) return void 0;
|
|
931
|
+
const details = {};
|
|
932
|
+
const rawDetails = asRecord(record.details);
|
|
933
|
+
if (rawDetails) {
|
|
934
|
+
Object.assign(details, rawDetails);
|
|
935
|
+
}
|
|
936
|
+
const action = toNonEmptyString(record.action);
|
|
937
|
+
if (action) {
|
|
938
|
+
details.action = action;
|
|
939
|
+
}
|
|
940
|
+
const selectorUsed = toNonEmptyString(record.selectorUsed);
|
|
941
|
+
if (selectorUsed) {
|
|
942
|
+
details.selectorUsed = selectorUsed;
|
|
943
|
+
}
|
|
944
|
+
if (typeof record.status === "number" && Number.isFinite(record.status)) {
|
|
945
|
+
details.status = record.status;
|
|
946
|
+
}
|
|
947
|
+
const failure = asRecord(record.failure);
|
|
948
|
+
if (failure) {
|
|
949
|
+
const failureCode = toNonEmptyString(failure.code);
|
|
950
|
+
const classificationSource = toNonEmptyString(
|
|
951
|
+
failure.classificationSource
|
|
952
|
+
);
|
|
953
|
+
const failureDetails = asRecord(failure.details);
|
|
954
|
+
if (failureCode || classificationSource || failureDetails) {
|
|
955
|
+
details.actionFailure = {
|
|
956
|
+
...failureCode ? { code: failureCode } : {},
|
|
957
|
+
...classificationSource ? { classificationSource } : {},
|
|
958
|
+
...failureDetails ? { details: failureDetails } : {}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return Object.keys(details).length ? details : void 0;
|
|
963
|
+
}
|
|
964
|
+
function extractCause(error) {
|
|
965
|
+
if (error instanceof Error) {
|
|
966
|
+
return error.cause;
|
|
967
|
+
}
|
|
968
|
+
const record = asRecord(error);
|
|
969
|
+
return record?.cause;
|
|
970
|
+
}
|
|
971
|
+
function asRecord(value) {
|
|
972
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
return value;
|
|
976
|
+
}
|
|
977
|
+
function toNonEmptyString(value) {
|
|
978
|
+
if (typeof value !== "string") return void 0;
|
|
979
|
+
const normalized = value.trim();
|
|
980
|
+
return normalized.length ? normalized : void 0;
|
|
981
|
+
}
|
|
982
|
+
function toJsonSafeRecord(value) {
|
|
983
|
+
if (!value) return void 0;
|
|
984
|
+
const sanitized = toJsonSafeValue(value, /* @__PURE__ */ new WeakSet());
|
|
985
|
+
if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
const record = sanitized;
|
|
989
|
+
return Object.keys(record).length > 0 ? record : void 0;
|
|
990
|
+
}
|
|
991
|
+
function toJsonSafeValue(value, seen) {
|
|
992
|
+
if (value === null) return null;
|
|
993
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
994
|
+
return value;
|
|
995
|
+
}
|
|
996
|
+
if (typeof value === "number") {
|
|
997
|
+
return Number.isFinite(value) ? value : null;
|
|
998
|
+
}
|
|
999
|
+
if (typeof value === "bigint") {
|
|
1000
|
+
return value.toString();
|
|
1001
|
+
}
|
|
1002
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
if (value instanceof Date) {
|
|
1006
|
+
return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
1007
|
+
}
|
|
1008
|
+
if (Array.isArray(value)) {
|
|
1009
|
+
if (seen.has(value)) return "[Circular]";
|
|
1010
|
+
seen.add(value);
|
|
1011
|
+
const output = value.map((item) => {
|
|
1012
|
+
const next = toJsonSafeValue(item, seen);
|
|
1013
|
+
return next === void 0 ? null : next;
|
|
1014
|
+
});
|
|
1015
|
+
seen.delete(value);
|
|
1016
|
+
return output;
|
|
1017
|
+
}
|
|
1018
|
+
if (value instanceof Set) {
|
|
1019
|
+
if (seen.has(value)) return "[Circular]";
|
|
1020
|
+
seen.add(value);
|
|
1021
|
+
const output = Array.from(value, (item) => {
|
|
1022
|
+
const next = toJsonSafeValue(item, seen);
|
|
1023
|
+
return next === void 0 ? null : next;
|
|
1024
|
+
});
|
|
1025
|
+
seen.delete(value);
|
|
1026
|
+
return output;
|
|
1027
|
+
}
|
|
1028
|
+
if (value instanceof Map) {
|
|
1029
|
+
if (seen.has(value)) return "[Circular]";
|
|
1030
|
+
seen.add(value);
|
|
1031
|
+
const output = {};
|
|
1032
|
+
for (const [key, item] of value.entries()) {
|
|
1033
|
+
const normalizedKey = String(key);
|
|
1034
|
+
const next = toJsonSafeValue(item, seen);
|
|
1035
|
+
if (next !== void 0) {
|
|
1036
|
+
output[normalizedKey] = next;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
seen.delete(value);
|
|
1040
|
+
return output;
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof value === "object") {
|
|
1043
|
+
const objectValue = value;
|
|
1044
|
+
if (seen.has(objectValue)) return "[Circular]";
|
|
1045
|
+
seen.add(objectValue);
|
|
1046
|
+
const output = {};
|
|
1047
|
+
for (const [key, item] of Object.entries(objectValue)) {
|
|
1048
|
+
const next = toJsonSafeValue(item, seen);
|
|
1049
|
+
if (next !== void 0) {
|
|
1050
|
+
output[key] = next;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
seen.delete(objectValue);
|
|
1054
|
+
return output;
|
|
1055
|
+
}
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
824
1059
|
// src/storage/namespace.ts
|
|
825
1060
|
var import_path2 = __toESM(require("path"), 1);
|
|
826
1061
|
var DEFAULT_NAMESPACE = "default";
|
|
@@ -884,11 +1119,12 @@ function dotenvFileOrder(nodeEnv) {
|
|
|
884
1119
|
files.push(".env");
|
|
885
1120
|
return files;
|
|
886
1121
|
}
|
|
887
|
-
function loadDotenvValues(rootDir, baseEnv) {
|
|
1122
|
+
function loadDotenvValues(rootDir, baseEnv, options = {}) {
|
|
888
1123
|
const values = {};
|
|
889
1124
|
if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
|
|
890
1125
|
return values;
|
|
891
1126
|
}
|
|
1127
|
+
const debug = options.debug ?? parseBool(baseEnv.OPENSTEER_DEBUG) === true;
|
|
892
1128
|
const baseDir = import_path3.default.resolve(rootDir);
|
|
893
1129
|
const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
|
|
894
1130
|
for (const filename of dotenvFileOrder(nodeEnv)) {
|
|
@@ -902,15 +1138,24 @@ function loadDotenvValues(rootDir, baseEnv) {
|
|
|
902
1138
|
values[key] = value;
|
|
903
1139
|
}
|
|
904
1140
|
}
|
|
905
|
-
} catch {
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
const message = extractErrorMessage(
|
|
1143
|
+
error,
|
|
1144
|
+
"Unable to read or parse dotenv file."
|
|
1145
|
+
);
|
|
1146
|
+
if (debug) {
|
|
1147
|
+
console.warn(
|
|
1148
|
+
`[opensteer] failed to load dotenv file "${filePath}": ${message}`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
906
1151
|
continue;
|
|
907
1152
|
}
|
|
908
1153
|
}
|
|
909
1154
|
return values;
|
|
910
1155
|
}
|
|
911
|
-
function resolveEnv(rootDir) {
|
|
1156
|
+
function resolveEnv(rootDir, options = {}) {
|
|
912
1157
|
const baseEnv = process.env;
|
|
913
|
-
const dotenvValues = loadDotenvValues(rootDir, baseEnv);
|
|
1158
|
+
const dotenvValues = loadDotenvValues(rootDir, baseEnv, options);
|
|
914
1159
|
return {
|
|
915
1160
|
...dotenvValues,
|
|
916
1161
|
...baseEnv
|
|
@@ -954,13 +1199,22 @@ function assertNoLegacyRuntimeConfig(source, config) {
|
|
|
954
1199
|
);
|
|
955
1200
|
}
|
|
956
1201
|
}
|
|
957
|
-
function loadConfigFile(rootDir) {
|
|
1202
|
+
function loadConfigFile(rootDir, options = {}) {
|
|
958
1203
|
const configPath = import_path3.default.join(rootDir, ".opensteer", "config.json");
|
|
959
1204
|
if (!import_fs.default.existsSync(configPath)) return {};
|
|
960
1205
|
try {
|
|
961
1206
|
const raw = import_fs.default.readFileSync(configPath, "utf8");
|
|
962
1207
|
return JSON.parse(raw);
|
|
963
|
-
} catch {
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
const message = extractErrorMessage(
|
|
1210
|
+
error,
|
|
1211
|
+
"Unable to read or parse config file."
|
|
1212
|
+
);
|
|
1213
|
+
if (options.debug) {
|
|
1214
|
+
console.warn(
|
|
1215
|
+
`[opensteer] failed to load config file "${configPath}": ${message}`
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
964
1218
|
return {};
|
|
965
1219
|
}
|
|
966
1220
|
}
|
|
@@ -1092,6 +1346,8 @@ function resolveCloudSelection(config, env = process.env) {
|
|
|
1092
1346
|
};
|
|
1093
1347
|
}
|
|
1094
1348
|
function resolveConfig(input = {}) {
|
|
1349
|
+
const processEnv = process.env;
|
|
1350
|
+
const debugHint = typeof input.debug === "boolean" ? input.debug : parseBool(processEnv.OPENSTEER_DEBUG) === true;
|
|
1095
1351
|
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
1096
1352
|
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
1097
1353
|
storage: {
|
|
@@ -1100,12 +1356,16 @@ function resolveConfig(input = {}) {
|
|
|
1100
1356
|
});
|
|
1101
1357
|
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
1102
1358
|
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
1103
|
-
const fileConfig = loadConfigFile(initialRootDir
|
|
1359
|
+
const fileConfig = loadConfigFile(initialRootDir, {
|
|
1360
|
+
debug: debugHint
|
|
1361
|
+
});
|
|
1104
1362
|
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1105
1363
|
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1106
1364
|
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1107
1365
|
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1108
|
-
const env = resolveEnv(envRootDir
|
|
1366
|
+
const env = resolveEnv(envRootDir, {
|
|
1367
|
+
debug: debugHint
|
|
1368
|
+
});
|
|
1109
1369
|
if (env.OPENSTEER_AI_MODEL) {
|
|
1110
1370
|
throw new Error(
|
|
1111
1371
|
"OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
|
|
@@ -1789,9 +2049,11 @@ function createEmptyRegistry(name) {
|
|
|
1789
2049
|
var LocalSelectorStorage = class {
|
|
1790
2050
|
rootDir;
|
|
1791
2051
|
namespace;
|
|
1792
|
-
|
|
2052
|
+
debug;
|
|
2053
|
+
constructor(rootDir, namespace, options = {}) {
|
|
1793
2054
|
this.rootDir = rootDir;
|
|
1794
2055
|
this.namespace = normalizeNamespace(namespace);
|
|
2056
|
+
this.debug = options.debug === true;
|
|
1795
2057
|
}
|
|
1796
2058
|
getRootDir() {
|
|
1797
2059
|
return this.rootDir;
|
|
@@ -1825,7 +2087,16 @@ var LocalSelectorStorage = class {
|
|
|
1825
2087
|
try {
|
|
1826
2088
|
const raw = import_fs2.default.readFileSync(file, "utf8");
|
|
1827
2089
|
return JSON.parse(raw);
|
|
1828
|
-
} catch {
|
|
2090
|
+
} catch (error) {
|
|
2091
|
+
const message = extractErrorMessage(
|
|
2092
|
+
error,
|
|
2093
|
+
"Unable to parse selector registry JSON."
|
|
2094
|
+
);
|
|
2095
|
+
if (this.debug) {
|
|
2096
|
+
console.warn(
|
|
2097
|
+
`[opensteer] failed to read selector registry "${file}": ${message}`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
1829
2100
|
return createEmptyRegistry(this.namespace);
|
|
1830
2101
|
}
|
|
1831
2102
|
}
|
|
@@ -1842,7 +2113,16 @@ var LocalSelectorStorage = class {
|
|
|
1842
2113
|
try {
|
|
1843
2114
|
const raw = import_fs2.default.readFileSync(file, "utf8");
|
|
1844
2115
|
return JSON.parse(raw);
|
|
1845
|
-
} catch {
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
const message = extractErrorMessage(
|
|
2118
|
+
error,
|
|
2119
|
+
"Unable to parse selector file JSON."
|
|
2120
|
+
);
|
|
2121
|
+
if (this.debug) {
|
|
2122
|
+
console.warn(
|
|
2123
|
+
`[opensteer] failed to read selector file "${file}": ${message}`
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
1846
2126
|
return null;
|
|
1847
2127
|
}
|
|
1848
2128
|
}
|
|
@@ -1862,7 +2142,6 @@ var LocalSelectorStorage = class {
|
|
|
1862
2142
|
|
|
1863
2143
|
// src/html/pipeline.ts
|
|
1864
2144
|
var cheerio3 = __toESM(require("cheerio"), 1);
|
|
1865
|
-
var import_crypto = require("crypto");
|
|
1866
2145
|
|
|
1867
2146
|
// src/html/serializer.ts
|
|
1868
2147
|
var cheerio = __toESM(require("cheerio"), 1);
|
|
@@ -2135,9 +2414,6 @@ var ENSURE_NAME_SHIM_SCRIPT = `
|
|
|
2135
2414
|
`;
|
|
2136
2415
|
var OS_FRAME_TOKEN_KEY = "__opensteerFrameToken";
|
|
2137
2416
|
var OS_INSTANCE_TOKEN_KEY = "__opensteerInstanceToken";
|
|
2138
|
-
var OS_COUNTER_OWNER_KEY = "__opensteerCounterOwner";
|
|
2139
|
-
var OS_COUNTER_VALUE_KEY = "__opensteerCounterValue";
|
|
2140
|
-
var OS_COUNTER_NEXT_KEY = "__opensteerCounterNext";
|
|
2141
2417
|
|
|
2142
2418
|
// src/element-path/build.ts
|
|
2143
2419
|
var MAX_ATTRIBUTE_VALUE_LENGTH = 300;
|
|
@@ -3927,567 +4203,178 @@ function cleanForAction(html) {
|
|
|
3927
4203
|
return compactHtml(htmlOut);
|
|
3928
4204
|
}
|
|
3929
4205
|
|
|
3930
|
-
// src/
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
4206
|
+
// src/html/pipeline.ts
|
|
4207
|
+
function applyCleaner(mode, html) {
|
|
4208
|
+
switch (mode) {
|
|
4209
|
+
case "clickable":
|
|
4210
|
+
return cleanForClickable(html);
|
|
4211
|
+
case "scrollable":
|
|
4212
|
+
return cleanForScrollable(html);
|
|
4213
|
+
case "extraction":
|
|
4214
|
+
return cleanForExtraction(html);
|
|
4215
|
+
case "full":
|
|
4216
|
+
return cleanForFull(html);
|
|
4217
|
+
case "action":
|
|
4218
|
+
default:
|
|
4219
|
+
return cleanForAction(html);
|
|
3943
4220
|
}
|
|
3944
|
-
const text = rawText.replace(/\s+/g, " ").trim();
|
|
3945
|
-
return text || null;
|
|
3946
4221
|
}
|
|
3947
|
-
function
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
const
|
|
3954
|
-
|
|
3955
|
-
|
|
4222
|
+
async function assignCounters(page, html, nodePaths, nodeMeta) {
|
|
4223
|
+
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4224
|
+
const counterIndex = /* @__PURE__ */ new Map();
|
|
4225
|
+
let nextCounter = 1;
|
|
4226
|
+
const assignedByNodeId = /* @__PURE__ */ new Map();
|
|
4227
|
+
$("*").each(function() {
|
|
4228
|
+
const el = $(this);
|
|
4229
|
+
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4230
|
+
if (!nodeId) return;
|
|
4231
|
+
const counter = nextCounter++;
|
|
4232
|
+
assignedByNodeId.set(nodeId, counter);
|
|
4233
|
+
const path5 = nodePaths.get(nodeId);
|
|
4234
|
+
el.attr("c", String(counter));
|
|
4235
|
+
el.removeAttr(OS_NODE_ID_ATTR);
|
|
4236
|
+
if (path5) {
|
|
4237
|
+
counterIndex.set(counter, cloneElementPath(path5));
|
|
4238
|
+
}
|
|
4239
|
+
});
|
|
4240
|
+
try {
|
|
4241
|
+
await syncLiveCounters(page, nodeMeta, assignedByNodeId);
|
|
4242
|
+
} catch (error) {
|
|
4243
|
+
await clearLiveCounters(page);
|
|
4244
|
+
throw error;
|
|
3956
4245
|
}
|
|
3957
|
-
|
|
4246
|
+
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4247
|
+
return {
|
|
4248
|
+
html: $.html(),
|
|
4249
|
+
counterIndex
|
|
4250
|
+
};
|
|
3958
4251
|
}
|
|
3959
|
-
function
|
|
3960
|
-
|
|
3961
|
-
if (!
|
|
3962
|
-
const
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
(
|
|
3972
|
-
);
|
|
3973
|
-
if (densityCandidates.length) {
|
|
3974
|
-
return densityCandidates.reduce(
|
|
3975
|
-
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
3976
|
-
).url;
|
|
4252
|
+
async function syncLiveCounters(page, nodeMeta, assignedByNodeId) {
|
|
4253
|
+
await clearLiveCounters(page);
|
|
4254
|
+
if (!assignedByNodeId.size) return;
|
|
4255
|
+
const groupedByFrame = /* @__PURE__ */ new Map();
|
|
4256
|
+
for (const [nodeId, counter] of assignedByNodeId.entries()) {
|
|
4257
|
+
const meta = nodeMeta.get(nodeId);
|
|
4258
|
+
if (!meta?.frameToken) continue;
|
|
4259
|
+
const list = groupedByFrame.get(meta.frameToken) || [];
|
|
4260
|
+
list.push({
|
|
4261
|
+
nodeId,
|
|
4262
|
+
counter
|
|
4263
|
+
});
|
|
4264
|
+
groupedByFrame.set(meta.frameToken, list);
|
|
3977
4265
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
const
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
if (!url) continue;
|
|
3992
|
-
index = skipWhitespace(text, index);
|
|
3993
|
-
const descriptors = [];
|
|
3994
|
-
while (index < text.length && text[index] !== ",") {
|
|
3995
|
-
const descriptorToken = readDescriptorToken(text, index);
|
|
3996
|
-
if (!descriptorToken.value) {
|
|
3997
|
-
index = descriptorToken.nextIndex;
|
|
3998
|
-
continue;
|
|
4266
|
+
if (!groupedByFrame.size) return;
|
|
4267
|
+
const failures = [];
|
|
4268
|
+
const framesByToken = await mapFramesByToken(page);
|
|
4269
|
+
for (const [frameToken, entries] of groupedByFrame.entries()) {
|
|
4270
|
+
const frame = framesByToken.get(frameToken);
|
|
4271
|
+
if (!frame) {
|
|
4272
|
+
for (const entry of entries) {
|
|
4273
|
+
failures.push({
|
|
4274
|
+
nodeId: entry.nodeId,
|
|
4275
|
+
counter: entry.counter,
|
|
4276
|
+
frameToken,
|
|
4277
|
+
reason: "frame_missing"
|
|
4278
|
+
});
|
|
3999
4279
|
}
|
|
4000
|
-
|
|
4001
|
-
index = descriptorToken.nextIndex;
|
|
4002
|
-
index = skipWhitespace(text, index);
|
|
4003
|
-
}
|
|
4004
|
-
if (index < text.length && text[index] === ",") {
|
|
4005
|
-
index += 1;
|
|
4280
|
+
continue;
|
|
4006
4281
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4282
|
+
try {
|
|
4283
|
+
const unresolved = await frame.evaluate(
|
|
4284
|
+
({ entries: entries2, nodeAttr }) => {
|
|
4285
|
+
const index = /* @__PURE__ */ new Map();
|
|
4286
|
+
const unresolved2 = [];
|
|
4287
|
+
const walk = (root) => {
|
|
4288
|
+
const children = Array.from(root.children);
|
|
4289
|
+
for (const child of children) {
|
|
4290
|
+
const nodeId = child.getAttribute(nodeAttr);
|
|
4291
|
+
if (nodeId) {
|
|
4292
|
+
const list = index.get(nodeId) || [];
|
|
4293
|
+
list.push(child);
|
|
4294
|
+
index.set(nodeId, list);
|
|
4295
|
+
}
|
|
4296
|
+
walk(child);
|
|
4297
|
+
if (child.shadowRoot) {
|
|
4298
|
+
walk(child.shadowRoot);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
walk(document);
|
|
4303
|
+
for (const entry of entries2) {
|
|
4304
|
+
const matches = index.get(entry.nodeId) || [];
|
|
4305
|
+
if (matches.length !== 1) {
|
|
4306
|
+
unresolved2.push({
|
|
4307
|
+
nodeId: entry.nodeId,
|
|
4308
|
+
counter: entry.counter,
|
|
4309
|
+
matches: matches.length
|
|
4310
|
+
});
|
|
4311
|
+
continue;
|
|
4312
|
+
}
|
|
4313
|
+
matches[0].setAttribute("c", String(entry.counter));
|
|
4314
|
+
}
|
|
4315
|
+
return unresolved2;
|
|
4316
|
+
},
|
|
4317
|
+
{
|
|
4318
|
+
entries,
|
|
4319
|
+
nodeAttr: OS_NODE_ID_ATTR
|
|
4017
4320
|
}
|
|
4018
|
-
|
|
4321
|
+
);
|
|
4322
|
+
for (const entry of unresolved) {
|
|
4323
|
+
failures.push({
|
|
4324
|
+
nodeId: entry.nodeId,
|
|
4325
|
+
counter: entry.counter,
|
|
4326
|
+
frameToken,
|
|
4327
|
+
reason: "match_count",
|
|
4328
|
+
matches: entry.matches
|
|
4329
|
+
});
|
|
4019
4330
|
}
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4331
|
+
} catch {
|
|
4332
|
+
for (const entry of entries) {
|
|
4333
|
+
failures.push({
|
|
4334
|
+
nodeId: entry.nodeId,
|
|
4335
|
+
counter: entry.counter,
|
|
4336
|
+
frameToken,
|
|
4337
|
+
reason: "frame_unavailable"
|
|
4338
|
+
});
|
|
4026
4339
|
}
|
|
4027
4340
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4341
|
+
}
|
|
4342
|
+
if (failures.length) {
|
|
4343
|
+
const preview = failures.slice(0, 3).map((failure) => {
|
|
4344
|
+
const base = `counter ${failure.counter} (nodeId "${failure.nodeId}") in frame "${failure.frameToken}"`;
|
|
4345
|
+
if (failure.reason === "frame_missing") {
|
|
4346
|
+
return `${base} could not be synchronized because the frame is missing.`;
|
|
4347
|
+
}
|
|
4348
|
+
if (failure.reason === "frame_unavailable") {
|
|
4349
|
+
return `${base} could not be synchronized because frame evaluation failed.`;
|
|
4350
|
+
}
|
|
4351
|
+
return `${base} expected exactly one live node but found ${failure.matches ?? 0}.`;
|
|
4032
4352
|
});
|
|
4353
|
+
const remaining = failures.length > 3 ? ` (+${failures.length - 3} more)` : "";
|
|
4354
|
+
throw new Error(
|
|
4355
|
+
`Failed to synchronize snapshot counters with the live DOM: ${preview.join(" ")}${remaining}`
|
|
4356
|
+
);
|
|
4033
4357
|
}
|
|
4034
|
-
return out;
|
|
4035
4358
|
}
|
|
4036
|
-
function
|
|
4037
|
-
const
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
const text = String(raw || "");
|
|
4042
|
-
const start = skipSeparators(text, 0);
|
|
4043
|
-
if (start >= text.length) return null;
|
|
4044
|
-
const firstToken = readUrlToken(text, start).value.trim();
|
|
4045
|
-
return firstToken || null;
|
|
4046
|
-
}
|
|
4047
|
-
function skipWhitespace(value, index) {
|
|
4048
|
-
let cursor = index;
|
|
4049
|
-
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
4050
|
-
cursor += 1;
|
|
4051
|
-
}
|
|
4052
|
-
return cursor;
|
|
4053
|
-
}
|
|
4054
|
-
function skipSeparators(value, index) {
|
|
4055
|
-
let cursor = skipWhitespace(value, index);
|
|
4056
|
-
while (cursor < value.length && value[cursor] === ",") {
|
|
4057
|
-
cursor += 1;
|
|
4058
|
-
cursor = skipWhitespace(value, cursor);
|
|
4059
|
-
}
|
|
4060
|
-
return cursor;
|
|
4061
|
-
}
|
|
4062
|
-
function readUrlToken(value, index) {
|
|
4063
|
-
let cursor = index;
|
|
4064
|
-
let out = "";
|
|
4065
|
-
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
4066
|
-
while (cursor < value.length) {
|
|
4067
|
-
const char = value[cursor];
|
|
4068
|
-
if (/\s/.test(char)) {
|
|
4069
|
-
break;
|
|
4070
|
-
}
|
|
4071
|
-
if (char === "," && !isDataUrl) {
|
|
4072
|
-
break;
|
|
4073
|
-
}
|
|
4074
|
-
out += char;
|
|
4075
|
-
cursor += 1;
|
|
4076
|
-
}
|
|
4077
|
-
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
4078
|
-
out = out.slice(0, -1);
|
|
4079
|
-
}
|
|
4080
|
-
return {
|
|
4081
|
-
value: out,
|
|
4082
|
-
nextIndex: cursor
|
|
4083
|
-
};
|
|
4084
|
-
}
|
|
4085
|
-
function readDescriptorToken(value, index) {
|
|
4086
|
-
let cursor = skipWhitespace(value, index);
|
|
4087
|
-
let out = "";
|
|
4088
|
-
while (cursor < value.length) {
|
|
4089
|
-
const char = value[cursor];
|
|
4090
|
-
if (char === "," || /\s/.test(char)) {
|
|
4091
|
-
break;
|
|
4092
|
-
}
|
|
4093
|
-
out += char;
|
|
4094
|
-
cursor += 1;
|
|
4095
|
-
}
|
|
4096
|
-
return {
|
|
4097
|
-
value: out.trim(),
|
|
4098
|
-
nextIndex: cursor
|
|
4099
|
-
};
|
|
4100
|
-
}
|
|
4101
|
-
|
|
4102
|
-
// src/html/counter-runtime.ts
|
|
4103
|
-
var CounterResolutionError = class extends Error {
|
|
4104
|
-
code;
|
|
4105
|
-
constructor(code, message) {
|
|
4106
|
-
super(message);
|
|
4107
|
-
this.name = "CounterResolutionError";
|
|
4108
|
-
this.code = code;
|
|
4109
|
-
}
|
|
4110
|
-
};
|
|
4111
|
-
async function ensureLiveCounters(page, nodeMeta, nodeIds) {
|
|
4112
|
-
const out = /* @__PURE__ */ new Map();
|
|
4113
|
-
if (!nodeIds.length) return out;
|
|
4114
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
4115
|
-
for (const nodeId of nodeIds) {
|
|
4116
|
-
const meta = nodeMeta.get(nodeId);
|
|
4117
|
-
if (!meta) {
|
|
4118
|
-
throw new CounterResolutionError(
|
|
4119
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4120
|
-
`Missing metadata for node ${nodeId}. Run snapshot() again.`
|
|
4121
|
-
);
|
|
4122
|
-
}
|
|
4123
|
-
const list = grouped.get(meta.frameToken) || [];
|
|
4124
|
-
list.push({
|
|
4125
|
-
nodeId,
|
|
4126
|
-
instanceToken: meta.instanceToken
|
|
4127
|
-
});
|
|
4128
|
-
grouped.set(meta.frameToken, list);
|
|
4129
|
-
}
|
|
4130
|
-
const framesByToken = await mapFramesByToken(page);
|
|
4131
|
-
let nextCounter = await readGlobalNextCounter(page);
|
|
4132
|
-
const usedCounters = /* @__PURE__ */ new Map();
|
|
4133
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
4134
|
-
const frame = framesByToken.get(frameToken);
|
|
4135
|
-
if (!frame) {
|
|
4136
|
-
throw new CounterResolutionError(
|
|
4137
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
4138
|
-
`Counter frame ${frameToken} is unavailable. Run snapshot() again.`
|
|
4139
|
-
);
|
|
4140
|
-
}
|
|
4141
|
-
const result = await frame.evaluate(
|
|
4142
|
-
({
|
|
4143
|
-
entries: entries2,
|
|
4144
|
-
nodeAttr,
|
|
4145
|
-
instanceTokenKey,
|
|
4146
|
-
counterOwnerKey,
|
|
4147
|
-
counterValueKey,
|
|
4148
|
-
startCounter
|
|
4149
|
-
}) => {
|
|
4150
|
-
const helpers = {
|
|
4151
|
-
pushNode(map, node) {
|
|
4152
|
-
const nodeId = node.getAttribute(nodeAttr);
|
|
4153
|
-
if (!nodeId) return;
|
|
4154
|
-
const list = map.get(nodeId) || [];
|
|
4155
|
-
list.push(node);
|
|
4156
|
-
map.set(nodeId, list);
|
|
4157
|
-
},
|
|
4158
|
-
walk(map, root) {
|
|
4159
|
-
const children = Array.from(root.children);
|
|
4160
|
-
for (const child of children) {
|
|
4161
|
-
helpers.pushNode(map, child);
|
|
4162
|
-
helpers.walk(map, child);
|
|
4163
|
-
if (child.shadowRoot) {
|
|
4164
|
-
helpers.walk(map, child.shadowRoot);
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
},
|
|
4168
|
-
buildNodeIndex() {
|
|
4169
|
-
const map = /* @__PURE__ */ new Map();
|
|
4170
|
-
helpers.walk(map, document);
|
|
4171
|
-
return map;
|
|
4172
|
-
}
|
|
4173
|
-
};
|
|
4174
|
-
const index = helpers.buildNodeIndex();
|
|
4175
|
-
const assigned = [];
|
|
4176
|
-
const failures = [];
|
|
4177
|
-
let next = Math.max(1, Number(startCounter || 1));
|
|
4178
|
-
for (const entry of entries2) {
|
|
4179
|
-
const matches = index.get(entry.nodeId) || [];
|
|
4180
|
-
if (!matches.length) {
|
|
4181
|
-
failures.push({
|
|
4182
|
-
nodeId: entry.nodeId,
|
|
4183
|
-
reason: "missing"
|
|
4184
|
-
});
|
|
4185
|
-
continue;
|
|
4186
|
-
}
|
|
4187
|
-
if (matches.length !== 1) {
|
|
4188
|
-
failures.push({
|
|
4189
|
-
nodeId: entry.nodeId,
|
|
4190
|
-
reason: "ambiguous"
|
|
4191
|
-
});
|
|
4192
|
-
continue;
|
|
4193
|
-
}
|
|
4194
|
-
const target = matches[0];
|
|
4195
|
-
if (target[instanceTokenKey] !== entry.instanceToken) {
|
|
4196
|
-
failures.push({
|
|
4197
|
-
nodeId: entry.nodeId,
|
|
4198
|
-
reason: "instance_mismatch"
|
|
4199
|
-
});
|
|
4200
|
-
continue;
|
|
4201
|
-
}
|
|
4202
|
-
const owned = target[counterOwnerKey] === true;
|
|
4203
|
-
const runtimeCounter = Number(target[counterValueKey] || 0);
|
|
4204
|
-
if (owned && Number.isFinite(runtimeCounter) && runtimeCounter > 0) {
|
|
4205
|
-
target.setAttribute("c", String(runtimeCounter));
|
|
4206
|
-
assigned.push({
|
|
4207
|
-
nodeId: entry.nodeId,
|
|
4208
|
-
counter: runtimeCounter
|
|
4209
|
-
});
|
|
4210
|
-
continue;
|
|
4211
|
-
}
|
|
4212
|
-
const counter = next++;
|
|
4213
|
-
target.setAttribute("c", String(counter));
|
|
4214
|
-
Object.defineProperty(target, counterOwnerKey, {
|
|
4215
|
-
value: true,
|
|
4216
|
-
writable: true,
|
|
4217
|
-
configurable: true
|
|
4218
|
-
});
|
|
4219
|
-
Object.defineProperty(target, counterValueKey, {
|
|
4220
|
-
value: counter,
|
|
4221
|
-
writable: true,
|
|
4222
|
-
configurable: true
|
|
4223
|
-
});
|
|
4224
|
-
assigned.push({ nodeId: entry.nodeId, counter });
|
|
4225
|
-
}
|
|
4226
|
-
return {
|
|
4227
|
-
assigned,
|
|
4228
|
-
failures,
|
|
4229
|
-
nextCounter: next
|
|
4230
|
-
};
|
|
4231
|
-
},
|
|
4232
|
-
{
|
|
4233
|
-
entries,
|
|
4234
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4235
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
4236
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4237
|
-
counterValueKey: OS_COUNTER_VALUE_KEY,
|
|
4238
|
-
startCounter: nextCounter
|
|
4239
|
-
}
|
|
4240
|
-
);
|
|
4241
|
-
if (result.failures.length) {
|
|
4242
|
-
const first = result.failures[0];
|
|
4243
|
-
throw buildCounterFailureError(first.nodeId, first.reason);
|
|
4244
|
-
}
|
|
4245
|
-
nextCounter = result.nextCounter;
|
|
4246
|
-
for (const item of result.assigned) {
|
|
4247
|
-
const existingNode = usedCounters.get(item.counter);
|
|
4248
|
-
if (existingNode && existingNode !== item.nodeId) {
|
|
4249
|
-
throw new CounterResolutionError(
|
|
4250
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
4251
|
-
`Counter ${item.counter} is assigned to multiple nodes (${existingNode}, ${item.nodeId}). Run snapshot() again.`
|
|
4252
|
-
);
|
|
4253
|
-
}
|
|
4254
|
-
usedCounters.set(item.counter, item.nodeId);
|
|
4255
|
-
out.set(item.nodeId, item.counter);
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
await writeGlobalNextCounter(page, nextCounter);
|
|
4259
|
-
return out;
|
|
4260
|
-
}
|
|
4261
|
-
async function resolveCounterElement(page, snapshot, counter) {
|
|
4262
|
-
const binding = readBinding(snapshot, counter);
|
|
4263
|
-
const framesByToken = await mapFramesByToken(page);
|
|
4264
|
-
const frame = framesByToken.get(binding.frameToken);
|
|
4265
|
-
if (!frame) {
|
|
4266
|
-
throw new CounterResolutionError(
|
|
4267
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
4268
|
-
`Counter ${counter} frame is unavailable. Run snapshot() again.`
|
|
4269
|
-
);
|
|
4270
|
-
}
|
|
4271
|
-
const status = await frame.evaluate(
|
|
4272
|
-
({
|
|
4273
|
-
nodeId,
|
|
4274
|
-
instanceToken,
|
|
4275
|
-
counter: counter2,
|
|
4276
|
-
nodeAttr,
|
|
4277
|
-
instanceTokenKey,
|
|
4278
|
-
counterOwnerKey,
|
|
4279
|
-
counterValueKey
|
|
4280
|
-
}) => {
|
|
4281
|
-
const helpers = {
|
|
4282
|
-
walk(map, root) {
|
|
4283
|
-
const children = Array.from(root.children);
|
|
4284
|
-
for (const child of children) {
|
|
4285
|
-
const id = child.getAttribute(nodeAttr);
|
|
4286
|
-
if (id) {
|
|
4287
|
-
const list = map.get(id) || [];
|
|
4288
|
-
list.push(child);
|
|
4289
|
-
map.set(id, list);
|
|
4290
|
-
}
|
|
4291
|
-
helpers.walk(map, child);
|
|
4292
|
-
if (child.shadowRoot) {
|
|
4293
|
-
helpers.walk(map, child.shadowRoot);
|
|
4294
|
-
}
|
|
4295
|
-
}
|
|
4296
|
-
},
|
|
4297
|
-
buildNodeIndex() {
|
|
4298
|
-
const map = /* @__PURE__ */ new Map();
|
|
4299
|
-
helpers.walk(map, document);
|
|
4300
|
-
return map;
|
|
4301
|
-
}
|
|
4302
|
-
};
|
|
4303
|
-
const matches = helpers.buildNodeIndex().get(nodeId) || [];
|
|
4304
|
-
if (!matches.length) return "missing";
|
|
4305
|
-
if (matches.length !== 1) return "ambiguous";
|
|
4306
|
-
const target = matches[0];
|
|
4307
|
-
if (target[instanceTokenKey] !== instanceToken) {
|
|
4308
|
-
return "instance_mismatch";
|
|
4309
|
-
}
|
|
4310
|
-
if (target[counterOwnerKey] !== true) {
|
|
4311
|
-
return "instance_mismatch";
|
|
4312
|
-
}
|
|
4313
|
-
if (Number(target[counterValueKey] || 0) !== counter2) {
|
|
4314
|
-
return "instance_mismatch";
|
|
4315
|
-
}
|
|
4316
|
-
if (target.getAttribute("c") !== String(counter2)) {
|
|
4317
|
-
return "instance_mismatch";
|
|
4318
|
-
}
|
|
4319
|
-
return "ok";
|
|
4320
|
-
},
|
|
4321
|
-
{
|
|
4322
|
-
nodeId: binding.nodeId,
|
|
4323
|
-
instanceToken: binding.instanceToken,
|
|
4324
|
-
counter,
|
|
4325
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4326
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
4327
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4328
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4329
|
-
}
|
|
4330
|
-
);
|
|
4331
|
-
if (status !== "ok") {
|
|
4332
|
-
throw buildCounterFailureError(binding.nodeId, status);
|
|
4333
|
-
}
|
|
4334
|
-
const handle = await frame.evaluateHandle(
|
|
4335
|
-
({ nodeId, nodeAttr }) => {
|
|
4336
|
-
const helpers = {
|
|
4337
|
-
walk(matches, root) {
|
|
4359
|
+
async function clearLiveCounters(page) {
|
|
4360
|
+
for (const frame of page.frames()) {
|
|
4361
|
+
try {
|
|
4362
|
+
await frame.evaluate(() => {
|
|
4363
|
+
const walk = (root) => {
|
|
4338
4364
|
const children = Array.from(root.children);
|
|
4339
4365
|
for (const child of children) {
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
}
|
|
4343
|
-
helpers.walk(matches, child);
|
|
4366
|
+
child.removeAttribute("c");
|
|
4367
|
+
walk(child);
|
|
4344
4368
|
if (child.shadowRoot) {
|
|
4345
|
-
|
|
4346
|
-
}
|
|
4347
|
-
}
|
|
4348
|
-
},
|
|
4349
|
-
findUniqueNode() {
|
|
4350
|
-
const matches = [];
|
|
4351
|
-
helpers.walk(matches, document);
|
|
4352
|
-
if (matches.length !== 1) return null;
|
|
4353
|
-
return matches[0];
|
|
4354
|
-
}
|
|
4355
|
-
};
|
|
4356
|
-
return helpers.findUniqueNode();
|
|
4357
|
-
},
|
|
4358
|
-
{
|
|
4359
|
-
nodeId: binding.nodeId,
|
|
4360
|
-
nodeAttr: OS_NODE_ID_ATTR
|
|
4361
|
-
}
|
|
4362
|
-
);
|
|
4363
|
-
const element = handle.asElement();
|
|
4364
|
-
if (!element) {
|
|
4365
|
-
await handle.dispose();
|
|
4366
|
-
throw new CounterResolutionError(
|
|
4367
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4368
|
-
`Counter ${counter} became stale. Run snapshot() again.`
|
|
4369
|
-
);
|
|
4370
|
-
}
|
|
4371
|
-
return element;
|
|
4372
|
-
}
|
|
4373
|
-
async function resolveCountersBatch(page, snapshot, requests) {
|
|
4374
|
-
const out = {};
|
|
4375
|
-
if (!requests.length) return out;
|
|
4376
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
4377
|
-
for (const request of requests) {
|
|
4378
|
-
const binding = readBinding(snapshot, request.counter);
|
|
4379
|
-
const list = grouped.get(binding.frameToken) || [];
|
|
4380
|
-
list.push({
|
|
4381
|
-
...request,
|
|
4382
|
-
...binding
|
|
4383
|
-
});
|
|
4384
|
-
grouped.set(binding.frameToken, list);
|
|
4385
|
-
}
|
|
4386
|
-
const framesByToken = await mapFramesByToken(page);
|
|
4387
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
4388
|
-
const frame = framesByToken.get(frameToken);
|
|
4389
|
-
if (!frame) {
|
|
4390
|
-
throw new CounterResolutionError(
|
|
4391
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
4392
|
-
`Counter frame ${frameToken} is unavailable. Run snapshot() again.`
|
|
4393
|
-
);
|
|
4394
|
-
}
|
|
4395
|
-
const result = await frame.evaluate(
|
|
4396
|
-
({
|
|
4397
|
-
entries: entries2,
|
|
4398
|
-
nodeAttr,
|
|
4399
|
-
instanceTokenKey,
|
|
4400
|
-
counterOwnerKey,
|
|
4401
|
-
counterValueKey
|
|
4402
|
-
}) => {
|
|
4403
|
-
const values = [];
|
|
4404
|
-
const failures = [];
|
|
4405
|
-
const helpers = {
|
|
4406
|
-
walk(map, root) {
|
|
4407
|
-
const children = Array.from(root.children);
|
|
4408
|
-
for (const child of children) {
|
|
4409
|
-
const id = child.getAttribute(nodeAttr);
|
|
4410
|
-
if (id) {
|
|
4411
|
-
const list = map.get(id) || [];
|
|
4412
|
-
list.push(child);
|
|
4413
|
-
map.set(id, list);
|
|
4414
|
-
}
|
|
4415
|
-
helpers.walk(map, child);
|
|
4416
|
-
if (child.shadowRoot) {
|
|
4417
|
-
helpers.walk(map, child.shadowRoot);
|
|
4418
|
-
}
|
|
4369
|
+
walk(child.shadowRoot);
|
|
4419
4370
|
}
|
|
4420
|
-
},
|
|
4421
|
-
buildNodeIndex() {
|
|
4422
|
-
const map = /* @__PURE__ */ new Map();
|
|
4423
|
-
helpers.walk(map, document);
|
|
4424
|
-
return map;
|
|
4425
|
-
},
|
|
4426
|
-
readRawValue(element, attribute) {
|
|
4427
|
-
if (attribute) {
|
|
4428
|
-
return element.getAttribute(attribute);
|
|
4429
|
-
}
|
|
4430
|
-
return element.textContent;
|
|
4431
|
-
}
|
|
4432
|
-
};
|
|
4433
|
-
const index = helpers.buildNodeIndex();
|
|
4434
|
-
for (const entry of entries2) {
|
|
4435
|
-
const matches = index.get(entry.nodeId) || [];
|
|
4436
|
-
if (!matches.length) {
|
|
4437
|
-
failures.push({
|
|
4438
|
-
nodeId: entry.nodeId,
|
|
4439
|
-
reason: "missing"
|
|
4440
|
-
});
|
|
4441
|
-
continue;
|
|
4442
|
-
}
|
|
4443
|
-
if (matches.length !== 1) {
|
|
4444
|
-
failures.push({
|
|
4445
|
-
nodeId: entry.nodeId,
|
|
4446
|
-
reason: "ambiguous"
|
|
4447
|
-
});
|
|
4448
|
-
continue;
|
|
4449
4371
|
}
|
|
4450
|
-
const target = matches[0];
|
|
4451
|
-
if (target[instanceTokenKey] !== entry.instanceToken || target[counterOwnerKey] !== true || Number(target[counterValueKey] || 0) !== entry.counter || target.getAttribute("c") !== String(entry.counter)) {
|
|
4452
|
-
failures.push({
|
|
4453
|
-
nodeId: entry.nodeId,
|
|
4454
|
-
reason: "instance_mismatch"
|
|
4455
|
-
});
|
|
4456
|
-
continue;
|
|
4457
|
-
}
|
|
4458
|
-
values.push({
|
|
4459
|
-
key: entry.key,
|
|
4460
|
-
value: helpers.readRawValue(target, entry.attribute)
|
|
4461
|
-
});
|
|
4462
|
-
}
|
|
4463
|
-
return {
|
|
4464
|
-
values,
|
|
4465
|
-
failures
|
|
4466
4372
|
};
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4471
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
4472
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4473
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4474
|
-
}
|
|
4475
|
-
);
|
|
4476
|
-
if (result.failures.length) {
|
|
4477
|
-
const first = result.failures[0];
|
|
4478
|
-
throw buildCounterFailureError(first.nodeId, first.reason);
|
|
4479
|
-
}
|
|
4480
|
-
const attributeByKey = new Map(
|
|
4481
|
-
entries.map((entry) => [entry.key, entry.attribute])
|
|
4482
|
-
);
|
|
4483
|
-
for (const item of result.values) {
|
|
4484
|
-
out[item.key] = normalizeExtractedValue(
|
|
4485
|
-
item.value,
|
|
4486
|
-
attributeByKey.get(item.key)
|
|
4487
|
-
);
|
|
4373
|
+
walk(document);
|
|
4374
|
+
});
|
|
4375
|
+
} catch {
|
|
4488
4376
|
}
|
|
4489
4377
|
}
|
|
4490
|
-
return out;
|
|
4491
4378
|
}
|
|
4492
4379
|
async function mapFramesByToken(page) {
|
|
4493
4380
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -4509,193 +4396,18 @@ async function readFrameToken(frame) {
|
|
|
4509
4396
|
return null;
|
|
4510
4397
|
}
|
|
4511
4398
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
if (Number.isFinite(current) && current > 0) {
|
|
4518
|
-
return current;
|
|
4519
|
-
}
|
|
4520
|
-
let max = 0;
|
|
4521
|
-
for (const frame of page.frames()) {
|
|
4522
|
-
try {
|
|
4523
|
-
const frameMax = await frame.evaluate(
|
|
4524
|
-
({ nodeAttr, counterOwnerKey, counterValueKey }) => {
|
|
4525
|
-
let localMax = 0;
|
|
4526
|
-
const helpers = {
|
|
4527
|
-
walk(root) {
|
|
4528
|
-
const children = Array.from(
|
|
4529
|
-
root.children
|
|
4530
|
-
);
|
|
4531
|
-
for (const child of children) {
|
|
4532
|
-
const candidate = child;
|
|
4533
|
-
const hasNodeId = child.hasAttribute(nodeAttr);
|
|
4534
|
-
const owned = candidate[counterOwnerKey] === true;
|
|
4535
|
-
if (hasNodeId && owned) {
|
|
4536
|
-
const value = Number(
|
|
4537
|
-
candidate[counterValueKey] || 0
|
|
4538
|
-
);
|
|
4539
|
-
if (Number.isFinite(value) && value > localMax) {
|
|
4540
|
-
localMax = value;
|
|
4541
|
-
}
|
|
4542
|
-
}
|
|
4543
|
-
helpers.walk(child);
|
|
4544
|
-
if (child.shadowRoot) {
|
|
4545
|
-
helpers.walk(child.shadowRoot);
|
|
4546
|
-
}
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
};
|
|
4550
|
-
helpers.walk(document);
|
|
4551
|
-
return localMax;
|
|
4552
|
-
},
|
|
4553
|
-
{
|
|
4554
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4555
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4556
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4557
|
-
}
|
|
4558
|
-
);
|
|
4559
|
-
if (frameMax > max) {
|
|
4560
|
-
max = frameMax;
|
|
4561
|
-
}
|
|
4562
|
-
} catch {
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
const next = max + 1;
|
|
4566
|
-
await writeGlobalNextCounter(page, next);
|
|
4567
|
-
return next;
|
|
4568
|
-
}
|
|
4569
|
-
async function writeGlobalNextCounter(page, nextCounter) {
|
|
4570
|
-
await page.mainFrame().evaluate(
|
|
4571
|
-
({ counterNextKey, nextCounter: nextCounter2 }) => {
|
|
4572
|
-
const win = window;
|
|
4573
|
-
win[counterNextKey] = nextCounter2;
|
|
4574
|
-
},
|
|
4575
|
-
{
|
|
4576
|
-
counterNextKey: OS_COUNTER_NEXT_KEY,
|
|
4577
|
-
nextCounter
|
|
4578
|
-
}
|
|
4579
|
-
).catch(() => void 0);
|
|
4399
|
+
function stripNodeIds(html) {
|
|
4400
|
+
if (!html.includes(OS_NODE_ID_ATTR)) return html;
|
|
4401
|
+
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4402
|
+
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4403
|
+
return $.html();
|
|
4580
4404
|
}
|
|
4581
|
-
function
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
);
|
|
4587
|
-
}
|
|
4588
|
-
const binding = snapshot.counterBindings.get(counter);
|
|
4589
|
-
if (!binding) {
|
|
4590
|
-
throw new CounterResolutionError(
|
|
4591
|
-
"ERR_COUNTER_NOT_FOUND",
|
|
4592
|
-
`Counter ${counter} was not found in the current snapshot. Run snapshot() again.`
|
|
4593
|
-
);
|
|
4594
|
-
}
|
|
4595
|
-
if (binding.sessionId !== snapshot.snapshotSessionId) {
|
|
4596
|
-
throw new CounterResolutionError(
|
|
4597
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4598
|
-
`Counter ${counter} is stale for this snapshot session. Run snapshot() again.`
|
|
4599
|
-
);
|
|
4600
|
-
}
|
|
4601
|
-
return binding;
|
|
4602
|
-
}
|
|
4603
|
-
function buildCounterFailureError(nodeId, reason) {
|
|
4604
|
-
if (reason === "ambiguous") {
|
|
4605
|
-
return new CounterResolutionError(
|
|
4606
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
4607
|
-
`Counter target is ambiguous for node ${nodeId}. Run snapshot() again.`
|
|
4608
|
-
);
|
|
4609
|
-
}
|
|
4610
|
-
return new CounterResolutionError(
|
|
4611
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4612
|
-
`Counter target is stale or missing for node ${nodeId}. Run snapshot() again.`
|
|
4613
|
-
);
|
|
4614
|
-
}
|
|
4615
|
-
|
|
4616
|
-
// src/html/pipeline.ts
|
|
4617
|
-
function applyCleaner(mode, html) {
|
|
4618
|
-
switch (mode) {
|
|
4619
|
-
case "clickable":
|
|
4620
|
-
return cleanForClickable(html);
|
|
4621
|
-
case "scrollable":
|
|
4622
|
-
return cleanForScrollable(html);
|
|
4623
|
-
case "extraction":
|
|
4624
|
-
return cleanForExtraction(html);
|
|
4625
|
-
case "full":
|
|
4626
|
-
return cleanForFull(html);
|
|
4627
|
-
case "action":
|
|
4628
|
-
default:
|
|
4629
|
-
return cleanForAction(html);
|
|
4630
|
-
}
|
|
4631
|
-
}
|
|
4632
|
-
async function assignCounters(page, html, nodePaths, nodeMeta, snapshotSessionId) {
|
|
4633
|
-
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4634
|
-
const counterIndex = /* @__PURE__ */ new Map();
|
|
4635
|
-
const counterBindings = /* @__PURE__ */ new Map();
|
|
4636
|
-
const orderedNodeIds = [];
|
|
4637
|
-
$("*").each(function() {
|
|
4638
|
-
const el = $(this);
|
|
4639
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4640
|
-
if (!nodeId) return;
|
|
4641
|
-
orderedNodeIds.push(nodeId);
|
|
4642
|
-
});
|
|
4643
|
-
const countersByNodeId = await ensureLiveCounters(
|
|
4644
|
-
page,
|
|
4645
|
-
nodeMeta,
|
|
4646
|
-
orderedNodeIds
|
|
4647
|
-
);
|
|
4648
|
-
$("*").each(function() {
|
|
4649
|
-
const el = $(this);
|
|
4650
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4651
|
-
if (!nodeId) return;
|
|
4652
|
-
const path5 = nodePaths.get(nodeId);
|
|
4653
|
-
const meta = nodeMeta.get(nodeId);
|
|
4654
|
-
const counter = countersByNodeId.get(nodeId);
|
|
4655
|
-
if (counter == null || !Number.isFinite(counter)) {
|
|
4656
|
-
throw new Error(
|
|
4657
|
-
`Counter assignment failed for node ${nodeId}. Run snapshot() again.`
|
|
4658
|
-
);
|
|
4659
|
-
}
|
|
4660
|
-
if (counterBindings.has(counter) && counterBindings.get(counter)?.nodeId !== nodeId) {
|
|
4661
|
-
throw new Error(
|
|
4662
|
-
`Counter ${counter} was assigned to multiple nodes. Run snapshot() again.`
|
|
4663
|
-
);
|
|
4664
|
-
}
|
|
4665
|
-
el.attr("c", String(counter));
|
|
4666
|
-
el.removeAttr(OS_NODE_ID_ATTR);
|
|
4667
|
-
if (path5) {
|
|
4668
|
-
counterIndex.set(counter, cloneElementPath(path5));
|
|
4669
|
-
}
|
|
4670
|
-
if (meta) {
|
|
4671
|
-
counterBindings.set(counter, {
|
|
4672
|
-
sessionId: snapshotSessionId,
|
|
4673
|
-
frameToken: meta.frameToken,
|
|
4674
|
-
nodeId,
|
|
4675
|
-
instanceToken: meta.instanceToken
|
|
4676
|
-
});
|
|
4677
|
-
}
|
|
4678
|
-
});
|
|
4679
|
-
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4680
|
-
return {
|
|
4681
|
-
html: $.html(),
|
|
4682
|
-
counterIndex,
|
|
4683
|
-
counterBindings
|
|
4684
|
-
};
|
|
4685
|
-
}
|
|
4686
|
-
function stripNodeIds(html) {
|
|
4687
|
-
if (!html.includes(OS_NODE_ID_ATTR)) return html;
|
|
4688
|
-
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4689
|
-
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4690
|
-
return $.html();
|
|
4691
|
-
}
|
|
4692
|
-
async function prepareSnapshot(page, options = {}) {
|
|
4693
|
-
const snapshotSessionId = (0, import_crypto.randomUUID)();
|
|
4694
|
-
const mode = options.mode ?? "action";
|
|
4695
|
-
const withCounters = options.withCounters ?? true;
|
|
4696
|
-
const shouldMarkInteractive = options.markInteractive ?? true;
|
|
4697
|
-
if (shouldMarkInteractive) {
|
|
4698
|
-
await markInteractiveElements(page);
|
|
4405
|
+
async function prepareSnapshot(page, options = {}) {
|
|
4406
|
+
const mode = options.mode ?? "action";
|
|
4407
|
+
const withCounters = options.withCounters ?? true;
|
|
4408
|
+
const shouldMarkInteractive = options.markInteractive ?? true;
|
|
4409
|
+
if (shouldMarkInteractive) {
|
|
4410
|
+
await markInteractiveElements(page);
|
|
4699
4411
|
}
|
|
4700
4412
|
const serialized = await serializePageHTML(page);
|
|
4701
4413
|
const rawHtml = serialized.html;
|
|
@@ -4703,18 +4415,15 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
4703
4415
|
const reducedHtml = applyCleaner(mode, processedHtml);
|
|
4704
4416
|
let cleanedHtml = reducedHtml;
|
|
4705
4417
|
let counterIndex = null;
|
|
4706
|
-
let counterBindings = null;
|
|
4707
4418
|
if (withCounters) {
|
|
4708
4419
|
const counted = await assignCounters(
|
|
4709
4420
|
page,
|
|
4710
4421
|
reducedHtml,
|
|
4711
4422
|
serialized.nodePaths,
|
|
4712
|
-
serialized.nodeMeta
|
|
4713
|
-
snapshotSessionId
|
|
4423
|
+
serialized.nodeMeta
|
|
4714
4424
|
);
|
|
4715
4425
|
cleanedHtml = counted.html;
|
|
4716
4426
|
counterIndex = counted.counterIndex;
|
|
4717
|
-
counterBindings = counted.counterBindings;
|
|
4718
4427
|
} else {
|
|
4719
4428
|
cleanedHtml = stripNodeIds(cleanedHtml);
|
|
4720
4429
|
}
|
|
@@ -4723,325 +4432,724 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
4723
4432
|
cleanedHtml = $unwrap("body").html()?.trim() || cleanedHtml;
|
|
4724
4433
|
}
|
|
4725
4434
|
return {
|
|
4726
|
-
snapshotSessionId,
|
|
4727
4435
|
mode,
|
|
4728
4436
|
url: page.url(),
|
|
4729
4437
|
rawHtml,
|
|
4730
4438
|
processedHtml,
|
|
4731
4439
|
reducedHtml,
|
|
4732
4440
|
cleanedHtml,
|
|
4733
|
-
counterIndex
|
|
4734
|
-
|
|
4441
|
+
counterIndex
|
|
4442
|
+
};
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
// src/element-path/errors.ts
|
|
4446
|
+
var ElementPathError = class extends Error {
|
|
4447
|
+
code;
|
|
4448
|
+
constructor(code, message) {
|
|
4449
|
+
super(message);
|
|
4450
|
+
this.name = "ElementPathError";
|
|
4451
|
+
this.code = code;
|
|
4452
|
+
}
|
|
4453
|
+
};
|
|
4454
|
+
|
|
4455
|
+
// src/element-path/resolver.ts
|
|
4456
|
+
async function resolveElementPath(page, rawPath) {
|
|
4457
|
+
const path5 = sanitizeElementPath(rawPath);
|
|
4458
|
+
let frame = page.mainFrame();
|
|
4459
|
+
let rootHandle = null;
|
|
4460
|
+
for (const hop of path5.context) {
|
|
4461
|
+
const host = await resolveDomPath(frame, hop.host, rootHandle);
|
|
4462
|
+
if (!host) {
|
|
4463
|
+
await disposeHandle(rootHandle);
|
|
4464
|
+
throw new ElementPathError(
|
|
4465
|
+
"ERR_PATH_CONTEXT_HOST_NOT_FOUND",
|
|
4466
|
+
"Unable to resolve context host from stored match selectors."
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
if (hop.kind === "iframe") {
|
|
4470
|
+
const nextFrame = await host.element.contentFrame();
|
|
4471
|
+
await host.element.dispose();
|
|
4472
|
+
await disposeHandle(rootHandle);
|
|
4473
|
+
rootHandle = null;
|
|
4474
|
+
if (!nextFrame) {
|
|
4475
|
+
throw new ElementPathError(
|
|
4476
|
+
"ERR_PATH_IFRAME_UNAVAILABLE",
|
|
4477
|
+
"Iframe is unavailable or inaccessible for this path."
|
|
4478
|
+
);
|
|
4479
|
+
}
|
|
4480
|
+
frame = nextFrame;
|
|
4481
|
+
continue;
|
|
4482
|
+
}
|
|
4483
|
+
const shadowRoot = await host.element.evaluateHandle(
|
|
4484
|
+
(element) => element.shadowRoot
|
|
4485
|
+
);
|
|
4486
|
+
await host.element.dispose();
|
|
4487
|
+
const isMissing = await shadowRoot.evaluate((value) => value == null);
|
|
4488
|
+
if (isMissing) {
|
|
4489
|
+
await shadowRoot.dispose();
|
|
4490
|
+
await disposeHandle(rootHandle);
|
|
4491
|
+
throw new ElementPathError(
|
|
4492
|
+
"ERR_PATH_SHADOW_ROOT_UNAVAILABLE",
|
|
4493
|
+
"Shadow root is unavailable for this path."
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
await disposeHandle(rootHandle);
|
|
4497
|
+
rootHandle = shadowRoot;
|
|
4498
|
+
}
|
|
4499
|
+
const target = await resolveDomPath(frame, path5.nodes, rootHandle);
|
|
4500
|
+
if (!target) {
|
|
4501
|
+
const diagnostics = await collectCandidateDiagnostics(
|
|
4502
|
+
frame,
|
|
4503
|
+
path5.nodes,
|
|
4504
|
+
rootHandle
|
|
4505
|
+
);
|
|
4506
|
+
await disposeHandle(rootHandle);
|
|
4507
|
+
throw new ElementPathError(
|
|
4508
|
+
"ERR_PATH_TARGET_NOT_FOUND",
|
|
4509
|
+
buildTargetNotFoundMessage(path5.nodes, diagnostics)
|
|
4510
|
+
);
|
|
4511
|
+
}
|
|
4512
|
+
await disposeHandle(rootHandle);
|
|
4513
|
+
if (isPathDebugEnabled()) {
|
|
4514
|
+
debugPath("resolved", {
|
|
4515
|
+
selector: target.selector,
|
|
4516
|
+
mode: target.mode,
|
|
4517
|
+
count: target.count,
|
|
4518
|
+
targetDepth: path5.nodes.length
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
return {
|
|
4522
|
+
element: target.element,
|
|
4523
|
+
usedSelector: target.selector || buildPathSelectorHint(path5)
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
async function resolveDomPath(frame, domPath, rootHandle) {
|
|
4527
|
+
const candidates = buildPathCandidates(domPath);
|
|
4528
|
+
if (!candidates.length) return null;
|
|
4529
|
+
if (isPathDebugEnabled()) {
|
|
4530
|
+
debugPath("trying selectors", { candidates });
|
|
4531
|
+
}
|
|
4532
|
+
const selected = rootHandle ? await rootHandle.evaluate(selectInRoot, candidates) : await frame.evaluate(selectInDocument, candidates);
|
|
4533
|
+
if (!selected || !selected.selector) return null;
|
|
4534
|
+
const handle = rootHandle ? await rootHandle.evaluateHandle((root, selector) => {
|
|
4535
|
+
if (!(root instanceof ShadowRoot)) return null;
|
|
4536
|
+
return root.querySelector(selector);
|
|
4537
|
+
}, selected.selector) : await frame.evaluateHandle(
|
|
4538
|
+
(selector) => document.querySelector(selector),
|
|
4539
|
+
selected.selector
|
|
4540
|
+
);
|
|
4541
|
+
const element = handle.asElement();
|
|
4542
|
+
if (!element) {
|
|
4543
|
+
await handle.dispose();
|
|
4544
|
+
return null;
|
|
4545
|
+
}
|
|
4546
|
+
return {
|
|
4547
|
+
element,
|
|
4548
|
+
selector: selected.selector,
|
|
4549
|
+
mode: selected.mode,
|
|
4550
|
+
count: selected.count
|
|
4551
|
+
};
|
|
4552
|
+
}
|
|
4553
|
+
async function collectCandidateDiagnostics(frame, domPath, rootHandle) {
|
|
4554
|
+
const candidates = buildPathCandidates(domPath);
|
|
4555
|
+
if (!candidates.length) return [];
|
|
4556
|
+
const diagnostics = rootHandle ? await rootHandle.evaluate(countInRoot, candidates) : await frame.evaluate(countInDocument, candidates);
|
|
4557
|
+
return Array.isArray(diagnostics) ? diagnostics.map((item) => ({
|
|
4558
|
+
selector: String(item?.selector || ""),
|
|
4559
|
+
count: Number(item?.count || 0)
|
|
4560
|
+
})).filter((item) => item.selector) : [];
|
|
4561
|
+
}
|
|
4562
|
+
function buildTargetNotFoundMessage(domPath, diagnostics) {
|
|
4563
|
+
const depth = Array.isArray(domPath) ? domPath.length : 0;
|
|
4564
|
+
const sample = diagnostics.slice(0, 4).map((item) => `"${item.selector}" => ${item.count}`).join(", ");
|
|
4565
|
+
const base = "Element path resolution failed (ERR_PATH_TARGET_NOT_FOUND): no selector candidate matched the current DOM.";
|
|
4566
|
+
if (!sample)
|
|
4567
|
+
return `${base} Tried ${Math.max(diagnostics.length, 0)} candidates.`;
|
|
4568
|
+
return `${base} Target depth ${depth}. Candidate counts: ${sample}.`;
|
|
4569
|
+
}
|
|
4570
|
+
function selectInDocument(selectors) {
|
|
4571
|
+
let fallback = null;
|
|
4572
|
+
for (const selector of selectors) {
|
|
4573
|
+
if (!selector) continue;
|
|
4574
|
+
let count = 0;
|
|
4575
|
+
try {
|
|
4576
|
+
count = document.querySelectorAll(selector).length;
|
|
4577
|
+
} catch {
|
|
4578
|
+
count = 0;
|
|
4579
|
+
}
|
|
4580
|
+
if (count === 1) {
|
|
4581
|
+
return {
|
|
4582
|
+
selector,
|
|
4583
|
+
count,
|
|
4584
|
+
mode: "unique"
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
if (count > 1 && !fallback) {
|
|
4588
|
+
fallback = {
|
|
4589
|
+
selector,
|
|
4590
|
+
count,
|
|
4591
|
+
mode: "fallback"
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
return fallback;
|
|
4596
|
+
}
|
|
4597
|
+
function selectInRoot(root, selectors) {
|
|
4598
|
+
if (!(root instanceof ShadowRoot)) return null;
|
|
4599
|
+
let fallback = null;
|
|
4600
|
+
for (const selector of selectors) {
|
|
4601
|
+
if (!selector) continue;
|
|
4602
|
+
let count = 0;
|
|
4603
|
+
try {
|
|
4604
|
+
count = root.querySelectorAll(selector).length;
|
|
4605
|
+
} catch {
|
|
4606
|
+
count = 0;
|
|
4607
|
+
}
|
|
4608
|
+
if (count === 1) {
|
|
4609
|
+
return {
|
|
4610
|
+
selector,
|
|
4611
|
+
count,
|
|
4612
|
+
mode: "unique"
|
|
4613
|
+
};
|
|
4614
|
+
}
|
|
4615
|
+
if (count > 1 && !fallback) {
|
|
4616
|
+
fallback = {
|
|
4617
|
+
selector,
|
|
4618
|
+
count,
|
|
4619
|
+
mode: "fallback"
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
return fallback;
|
|
4624
|
+
}
|
|
4625
|
+
function countInDocument(selectors) {
|
|
4626
|
+
const out = [];
|
|
4627
|
+
for (const selector of selectors) {
|
|
4628
|
+
if (!selector) continue;
|
|
4629
|
+
let count = 0;
|
|
4630
|
+
try {
|
|
4631
|
+
count = document.querySelectorAll(selector).length;
|
|
4632
|
+
} catch {
|
|
4633
|
+
count = 0;
|
|
4634
|
+
}
|
|
4635
|
+
out.push({ selector, count });
|
|
4636
|
+
}
|
|
4637
|
+
return out;
|
|
4638
|
+
}
|
|
4639
|
+
function countInRoot(root, selectors) {
|
|
4640
|
+
if (!(root instanceof ShadowRoot)) return [];
|
|
4641
|
+
const out = [];
|
|
4642
|
+
for (const selector of selectors) {
|
|
4643
|
+
if (!selector) continue;
|
|
4644
|
+
let count = 0;
|
|
4645
|
+
try {
|
|
4646
|
+
count = root.querySelectorAll(selector).length;
|
|
4647
|
+
} catch {
|
|
4648
|
+
count = 0;
|
|
4649
|
+
}
|
|
4650
|
+
out.push({ selector, count });
|
|
4651
|
+
}
|
|
4652
|
+
return out;
|
|
4653
|
+
}
|
|
4654
|
+
function isPathDebugEnabled() {
|
|
4655
|
+
const value = process.env.OPENSTEER_DEBUG_PATH || process.env.OPENSTEER_DEBUG || process.env.DEBUG_SELECTORS;
|
|
4656
|
+
if (!value) return false;
|
|
4657
|
+
const normalized = value.trim().toLowerCase();
|
|
4658
|
+
return normalized === "1" || normalized === "true";
|
|
4659
|
+
}
|
|
4660
|
+
function debugPath(message, data) {
|
|
4661
|
+
if (!isPathDebugEnabled()) return;
|
|
4662
|
+
if (data !== void 0) {
|
|
4663
|
+
console.log(`[opensteer:path] ${message}`, data);
|
|
4664
|
+
} else {
|
|
4665
|
+
console.log(`[opensteer:path] ${message}`);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
async function disposeHandle(handle) {
|
|
4669
|
+
if (!handle) return;
|
|
4670
|
+
try {
|
|
4671
|
+
await handle.dispose();
|
|
4672
|
+
} catch {
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
// src/actions/actionability-probe.ts
|
|
4677
|
+
async function probeActionabilityState(element) {
|
|
4678
|
+
try {
|
|
4679
|
+
return await element.evaluate((target) => {
|
|
4680
|
+
if (!(target instanceof Element)) {
|
|
4681
|
+
return {
|
|
4682
|
+
connected: false,
|
|
4683
|
+
visible: null,
|
|
4684
|
+
enabled: null,
|
|
4685
|
+
editable: null,
|
|
4686
|
+
blocker: null
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
const connected = target.isConnected;
|
|
4690
|
+
if (!connected) {
|
|
4691
|
+
return {
|
|
4692
|
+
connected: false,
|
|
4693
|
+
visible: null,
|
|
4694
|
+
enabled: null,
|
|
4695
|
+
editable: null,
|
|
4696
|
+
blocker: null
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
const style = window.getComputedStyle(target);
|
|
4700
|
+
const rect = target.getBoundingClientRect();
|
|
4701
|
+
const hasBox = rect.width > 0 && rect.height > 0;
|
|
4702
|
+
const opacity = Number.parseFloat(style.opacity || "1");
|
|
4703
|
+
const isVisible = hasBox && style.display !== "none" && style.visibility !== "hidden" && style.visibility !== "collapse" && (!Number.isFinite(opacity) || opacity > 0);
|
|
4704
|
+
let enabled = null;
|
|
4705
|
+
if (target instanceof HTMLButtonElement || target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement || target instanceof HTMLOptionElement || target instanceof HTMLOptGroupElement || target instanceof HTMLFieldSetElement) {
|
|
4706
|
+
enabled = !target.disabled;
|
|
4707
|
+
}
|
|
4708
|
+
let editable = null;
|
|
4709
|
+
if (target instanceof HTMLInputElement) {
|
|
4710
|
+
editable = !target.readOnly && !target.disabled;
|
|
4711
|
+
} else if (target instanceof HTMLTextAreaElement) {
|
|
4712
|
+
editable = !target.readOnly && !target.disabled;
|
|
4713
|
+
} else if (target instanceof HTMLSelectElement) {
|
|
4714
|
+
editable = !target.disabled;
|
|
4715
|
+
} else if (target instanceof HTMLElement && target.isContentEditable) {
|
|
4716
|
+
editable = true;
|
|
4717
|
+
}
|
|
4718
|
+
let blocker = null;
|
|
4719
|
+
if (hasBox && window.innerWidth > 0 && window.innerHeight > 0) {
|
|
4720
|
+
const x = Math.min(
|
|
4721
|
+
Math.max(rect.left + rect.width / 2, 0),
|
|
4722
|
+
window.innerWidth - 1
|
|
4723
|
+
);
|
|
4724
|
+
const y = Math.min(
|
|
4725
|
+
Math.max(rect.top + rect.height / 2, 0),
|
|
4726
|
+
window.innerHeight - 1
|
|
4727
|
+
);
|
|
4728
|
+
const top = document.elementFromPoint(x, y);
|
|
4729
|
+
if (top && top !== target && !target.contains(top)) {
|
|
4730
|
+
const classes = String(top.className || "").split(/\s+/).map((value) => value.trim()).filter(Boolean).slice(0, 5);
|
|
4731
|
+
blocker = {
|
|
4732
|
+
tag: top.tagName.toLowerCase(),
|
|
4733
|
+
id: top.id || null,
|
|
4734
|
+
classes,
|
|
4735
|
+
role: top.getAttribute("role"),
|
|
4736
|
+
text: (top.textContent || "").trim().slice(0, 80) || null
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
return {
|
|
4741
|
+
connected,
|
|
4742
|
+
visible: isVisible,
|
|
4743
|
+
enabled,
|
|
4744
|
+
editable,
|
|
4745
|
+
blocker
|
|
4746
|
+
};
|
|
4747
|
+
});
|
|
4748
|
+
} catch {
|
|
4749
|
+
return null;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
// src/extract-value-normalization.ts
|
|
4754
|
+
var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
|
|
4755
|
+
function normalizeExtractedValue(raw, attribute) {
|
|
4756
|
+
if (raw == null) return null;
|
|
4757
|
+
const rawText = String(raw);
|
|
4758
|
+
if (!rawText.trim()) return null;
|
|
4759
|
+
const normalizedAttribute = String(attribute || "").trim().toLowerCase();
|
|
4760
|
+
if (URL_LIST_ATTRIBUTES.has(normalizedAttribute)) {
|
|
4761
|
+
const singleValue = pickSingleListAttributeValue(
|
|
4762
|
+
normalizedAttribute,
|
|
4763
|
+
rawText
|
|
4764
|
+
).trim();
|
|
4765
|
+
return singleValue || null;
|
|
4766
|
+
}
|
|
4767
|
+
const text = rawText.replace(/\s+/g, " ").trim();
|
|
4768
|
+
return text || null;
|
|
4769
|
+
}
|
|
4770
|
+
function pickSingleListAttributeValue(attribute, raw) {
|
|
4771
|
+
if (attribute === "ping") {
|
|
4772
|
+
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
4773
|
+
return firstUrl.trim();
|
|
4774
|
+
}
|
|
4775
|
+
if (attribute === "srcset" || attribute === "imagesrcset") {
|
|
4776
|
+
const picked = pickBestSrcsetCandidate(raw);
|
|
4777
|
+
if (picked) return picked;
|
|
4778
|
+
return pickFirstSrcsetToken(raw) || "";
|
|
4779
|
+
}
|
|
4780
|
+
return raw.trim();
|
|
4781
|
+
}
|
|
4782
|
+
function pickBestSrcsetCandidate(raw) {
|
|
4783
|
+
const candidates = parseSrcsetCandidates(raw);
|
|
4784
|
+
if (!candidates.length) return null;
|
|
4785
|
+
const widthCandidates = candidates.filter(
|
|
4786
|
+
(candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
|
|
4787
|
+
);
|
|
4788
|
+
if (widthCandidates.length) {
|
|
4789
|
+
return widthCandidates.reduce(
|
|
4790
|
+
(best, candidate) => candidate.width > best.width ? candidate : best
|
|
4791
|
+
).url;
|
|
4792
|
+
}
|
|
4793
|
+
const densityCandidates = candidates.filter(
|
|
4794
|
+
(candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
|
|
4795
|
+
);
|
|
4796
|
+
if (densityCandidates.length) {
|
|
4797
|
+
return densityCandidates.reduce(
|
|
4798
|
+
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
4799
|
+
).url;
|
|
4800
|
+
}
|
|
4801
|
+
return candidates[0]?.url || null;
|
|
4802
|
+
}
|
|
4803
|
+
function parseSrcsetCandidates(raw) {
|
|
4804
|
+
const text = String(raw || "").trim();
|
|
4805
|
+
if (!text) return [];
|
|
4806
|
+
const out = [];
|
|
4807
|
+
let index = 0;
|
|
4808
|
+
while (index < text.length) {
|
|
4809
|
+
index = skipSeparators(text, index);
|
|
4810
|
+
if (index >= text.length) break;
|
|
4811
|
+
const urlToken = readUrlToken(text, index);
|
|
4812
|
+
index = urlToken.nextIndex;
|
|
4813
|
+
const url = urlToken.value.trim();
|
|
4814
|
+
if (!url) continue;
|
|
4815
|
+
index = skipWhitespace(text, index);
|
|
4816
|
+
const descriptors = [];
|
|
4817
|
+
while (index < text.length && text[index] !== ",") {
|
|
4818
|
+
const descriptorToken = readDescriptorToken(text, index);
|
|
4819
|
+
if (!descriptorToken.value) {
|
|
4820
|
+
index = descriptorToken.nextIndex;
|
|
4821
|
+
continue;
|
|
4822
|
+
}
|
|
4823
|
+
descriptors.push(descriptorToken.value);
|
|
4824
|
+
index = descriptorToken.nextIndex;
|
|
4825
|
+
index = skipWhitespace(text, index);
|
|
4826
|
+
}
|
|
4827
|
+
if (index < text.length && text[index] === ",") {
|
|
4828
|
+
index += 1;
|
|
4829
|
+
}
|
|
4830
|
+
let width = null;
|
|
4831
|
+
let density = null;
|
|
4832
|
+
for (const descriptor of descriptors) {
|
|
4833
|
+
const token = descriptor.trim().toLowerCase();
|
|
4834
|
+
if (!token) continue;
|
|
4835
|
+
const widthMatch = token.match(/^(\d+)w$/);
|
|
4836
|
+
if (widthMatch) {
|
|
4837
|
+
const parsed = Number.parseInt(widthMatch[1], 10);
|
|
4838
|
+
if (Number.isFinite(parsed)) {
|
|
4839
|
+
width = parsed;
|
|
4840
|
+
}
|
|
4841
|
+
continue;
|
|
4842
|
+
}
|
|
4843
|
+
const densityMatch = token.match(/^(\d*\.?\d+)x$/);
|
|
4844
|
+
if (densityMatch) {
|
|
4845
|
+
const parsed = Number.parseFloat(densityMatch[1]);
|
|
4846
|
+
if (Number.isFinite(parsed)) {
|
|
4847
|
+
density = parsed;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
out.push({
|
|
4852
|
+
url,
|
|
4853
|
+
width,
|
|
4854
|
+
density
|
|
4855
|
+
});
|
|
4856
|
+
}
|
|
4857
|
+
return out;
|
|
4858
|
+
}
|
|
4859
|
+
function pickFirstSrcsetToken(raw) {
|
|
4860
|
+
const candidate = parseSrcsetCandidates(raw)[0];
|
|
4861
|
+
if (candidate?.url) {
|
|
4862
|
+
return candidate.url;
|
|
4863
|
+
}
|
|
4864
|
+
const text = String(raw || "");
|
|
4865
|
+
const start = skipSeparators(text, 0);
|
|
4866
|
+
if (start >= text.length) return null;
|
|
4867
|
+
const firstToken = readUrlToken(text, start).value.trim();
|
|
4868
|
+
return firstToken || null;
|
|
4869
|
+
}
|
|
4870
|
+
function skipWhitespace(value, index) {
|
|
4871
|
+
let cursor = index;
|
|
4872
|
+
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
4873
|
+
cursor += 1;
|
|
4874
|
+
}
|
|
4875
|
+
return cursor;
|
|
4876
|
+
}
|
|
4877
|
+
function skipSeparators(value, index) {
|
|
4878
|
+
let cursor = skipWhitespace(value, index);
|
|
4879
|
+
while (cursor < value.length && value[cursor] === ",") {
|
|
4880
|
+
cursor += 1;
|
|
4881
|
+
cursor = skipWhitespace(value, cursor);
|
|
4882
|
+
}
|
|
4883
|
+
return cursor;
|
|
4884
|
+
}
|
|
4885
|
+
function readUrlToken(value, index) {
|
|
4886
|
+
let cursor = index;
|
|
4887
|
+
let out = "";
|
|
4888
|
+
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
4889
|
+
while (cursor < value.length) {
|
|
4890
|
+
const char = value[cursor];
|
|
4891
|
+
if (/\s/.test(char)) {
|
|
4892
|
+
break;
|
|
4893
|
+
}
|
|
4894
|
+
if (char === "," && !isDataUrl) {
|
|
4895
|
+
break;
|
|
4896
|
+
}
|
|
4897
|
+
out += char;
|
|
4898
|
+
cursor += 1;
|
|
4899
|
+
}
|
|
4900
|
+
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
4901
|
+
out = out.slice(0, -1);
|
|
4902
|
+
}
|
|
4903
|
+
return {
|
|
4904
|
+
value: out,
|
|
4905
|
+
nextIndex: cursor
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
function readDescriptorToken(value, index) {
|
|
4909
|
+
let cursor = skipWhitespace(value, index);
|
|
4910
|
+
let out = "";
|
|
4911
|
+
while (cursor < value.length) {
|
|
4912
|
+
const char = value[cursor];
|
|
4913
|
+
if (char === "," || /\s/.test(char)) {
|
|
4914
|
+
break;
|
|
4915
|
+
}
|
|
4916
|
+
out += char;
|
|
4917
|
+
cursor += 1;
|
|
4918
|
+
}
|
|
4919
|
+
return {
|
|
4920
|
+
value: out.trim(),
|
|
4921
|
+
nextIndex: cursor
|
|
4735
4922
|
};
|
|
4736
4923
|
}
|
|
4737
4924
|
|
|
4738
|
-
// src/
|
|
4739
|
-
var
|
|
4925
|
+
// src/html/counter-runtime.ts
|
|
4926
|
+
var CounterResolutionError = class extends Error {
|
|
4740
4927
|
code;
|
|
4741
4928
|
constructor(code, message) {
|
|
4742
4929
|
super(message);
|
|
4743
|
-
this.name = "
|
|
4930
|
+
this.name = "CounterResolutionError";
|
|
4744
4931
|
this.code = code;
|
|
4745
4932
|
}
|
|
4746
4933
|
};
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
let frame = page.mainFrame();
|
|
4752
|
-
let rootHandle = null;
|
|
4753
|
-
for (const hop of path5.context) {
|
|
4754
|
-
const host = await resolveDomPath(frame, hop.host, rootHandle);
|
|
4755
|
-
if (!host) {
|
|
4756
|
-
await disposeHandle(rootHandle);
|
|
4757
|
-
throw new ElementPathError(
|
|
4758
|
-
"ERR_PATH_CONTEXT_HOST_NOT_FOUND",
|
|
4759
|
-
"Unable to resolve context host from stored match selectors."
|
|
4760
|
-
);
|
|
4761
|
-
}
|
|
4762
|
-
if (hop.kind === "iframe") {
|
|
4763
|
-
const nextFrame = await host.element.contentFrame();
|
|
4764
|
-
await host.element.dispose();
|
|
4765
|
-
await disposeHandle(rootHandle);
|
|
4766
|
-
rootHandle = null;
|
|
4767
|
-
if (!nextFrame) {
|
|
4768
|
-
throw new ElementPathError(
|
|
4769
|
-
"ERR_PATH_IFRAME_UNAVAILABLE",
|
|
4770
|
-
"Iframe is unavailable or inaccessible for this path."
|
|
4771
|
-
);
|
|
4772
|
-
}
|
|
4773
|
-
frame = nextFrame;
|
|
4774
|
-
continue;
|
|
4775
|
-
}
|
|
4776
|
-
const shadowRoot = await host.element.evaluateHandle(
|
|
4777
|
-
(element) => element.shadowRoot
|
|
4778
|
-
);
|
|
4779
|
-
await host.element.dispose();
|
|
4780
|
-
const isMissing = await shadowRoot.evaluate((value) => value == null);
|
|
4781
|
-
if (isMissing) {
|
|
4782
|
-
await shadowRoot.dispose();
|
|
4783
|
-
await disposeHandle(rootHandle);
|
|
4784
|
-
throw new ElementPathError(
|
|
4785
|
-
"ERR_PATH_SHADOW_ROOT_UNAVAILABLE",
|
|
4786
|
-
"Shadow root is unavailable for this path."
|
|
4787
|
-
);
|
|
4788
|
-
}
|
|
4789
|
-
await disposeHandle(rootHandle);
|
|
4790
|
-
rootHandle = shadowRoot;
|
|
4791
|
-
}
|
|
4792
|
-
const target = await resolveDomPath(frame, path5.nodes, rootHandle);
|
|
4793
|
-
if (!target) {
|
|
4794
|
-
const diagnostics = await collectCandidateDiagnostics(
|
|
4795
|
-
frame,
|
|
4796
|
-
path5.nodes,
|
|
4797
|
-
rootHandle
|
|
4798
|
-
);
|
|
4799
|
-
await disposeHandle(rootHandle);
|
|
4800
|
-
throw new ElementPathError(
|
|
4801
|
-
"ERR_PATH_TARGET_NOT_FOUND",
|
|
4802
|
-
buildTargetNotFoundMessage(path5.nodes, diagnostics)
|
|
4803
|
-
);
|
|
4934
|
+
async function resolveCounterElement(page, counter) {
|
|
4935
|
+
const normalized = normalizeCounter(counter);
|
|
4936
|
+
if (normalized == null) {
|
|
4937
|
+
throw buildCounterNotFoundError(counter);
|
|
4804
4938
|
}
|
|
4805
|
-
await
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
mode: target.mode,
|
|
4810
|
-
count: target.count,
|
|
4811
|
-
targetDepth: path5.nodes.length
|
|
4812
|
-
});
|
|
4939
|
+
const scan = await scanCounterOccurrences(page, [normalized]);
|
|
4940
|
+
const entry = scan.get(normalized);
|
|
4941
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
4942
|
+
throw buildCounterNotFoundError(counter);
|
|
4813
4943
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
usedSelector: target.selector || buildPathSelectorHint(path5)
|
|
4817
|
-
};
|
|
4818
|
-
}
|
|
4819
|
-
async function resolveDomPath(frame, domPath, rootHandle) {
|
|
4820
|
-
const candidates = buildPathCandidates(domPath);
|
|
4821
|
-
if (!candidates.length) return null;
|
|
4822
|
-
if (isPathDebugEnabled()) {
|
|
4823
|
-
debugPath("trying selectors", { candidates });
|
|
4944
|
+
if (entry.count > 1) {
|
|
4945
|
+
throw buildCounterAmbiguousError(counter);
|
|
4824
4946
|
}
|
|
4825
|
-
const
|
|
4826
|
-
if (!selected || !selected.selector) return null;
|
|
4827
|
-
const handle = rootHandle ? await rootHandle.evaluateHandle((root, selector) => {
|
|
4828
|
-
if (!(root instanceof ShadowRoot)) return null;
|
|
4829
|
-
return root.querySelector(selector);
|
|
4830
|
-
}, selected.selector) : await frame.evaluateHandle(
|
|
4831
|
-
(selector) => document.querySelector(selector),
|
|
4832
|
-
selected.selector
|
|
4833
|
-
);
|
|
4947
|
+
const handle = await resolveUniqueHandleInFrame(entry.frame, normalized);
|
|
4834
4948
|
const element = handle.asElement();
|
|
4835
4949
|
if (!element) {
|
|
4836
4950
|
await handle.dispose();
|
|
4837
|
-
|
|
4951
|
+
throw buildCounterNotFoundError(counter);
|
|
4838
4952
|
}
|
|
4839
|
-
return
|
|
4840
|
-
element,
|
|
4841
|
-
selector: selected.selector,
|
|
4842
|
-
mode: selected.mode,
|
|
4843
|
-
count: selected.count
|
|
4844
|
-
};
|
|
4845
|
-
}
|
|
4846
|
-
async function collectCandidateDiagnostics(frame, domPath, rootHandle) {
|
|
4847
|
-
const candidates = buildPathCandidates(domPath);
|
|
4848
|
-
if (!candidates.length) return [];
|
|
4849
|
-
const diagnostics = rootHandle ? await rootHandle.evaluate(countInRoot, candidates) : await frame.evaluate(countInDocument, candidates);
|
|
4850
|
-
return Array.isArray(diagnostics) ? diagnostics.map((item) => ({
|
|
4851
|
-
selector: String(item?.selector || ""),
|
|
4852
|
-
count: Number(item?.count || 0)
|
|
4853
|
-
})).filter((item) => item.selector) : [];
|
|
4854
|
-
}
|
|
4855
|
-
function buildTargetNotFoundMessage(domPath, diagnostics) {
|
|
4856
|
-
const depth = Array.isArray(domPath) ? domPath.length : 0;
|
|
4857
|
-
const sample = diagnostics.slice(0, 4).map((item) => `"${item.selector}" => ${item.count}`).join(", ");
|
|
4858
|
-
const base = "Element path resolution failed (ERR_PATH_TARGET_NOT_FOUND): no selector candidate matched the current DOM.";
|
|
4859
|
-
if (!sample)
|
|
4860
|
-
return `${base} Tried ${Math.max(diagnostics.length, 0)} candidates.`;
|
|
4861
|
-
return `${base} Target depth ${depth}. Candidate counts: ${sample}.`;
|
|
4953
|
+
return element;
|
|
4862
4954
|
}
|
|
4863
|
-
function
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
}
|
|
4873
|
-
if (count === 1) {
|
|
4874
|
-
return {
|
|
4875
|
-
selector,
|
|
4876
|
-
count,
|
|
4877
|
-
mode: "unique"
|
|
4878
|
-
};
|
|
4879
|
-
}
|
|
4880
|
-
if (count > 1 && !fallback) {
|
|
4881
|
-
fallback = {
|
|
4882
|
-
selector,
|
|
4883
|
-
count,
|
|
4884
|
-
mode: "fallback"
|
|
4885
|
-
};
|
|
4955
|
+
async function resolveCountersBatch(page, requests) {
|
|
4956
|
+
const out = {};
|
|
4957
|
+
if (!requests.length) return out;
|
|
4958
|
+
const counters = dedupeCounters(requests);
|
|
4959
|
+
const scan = await scanCounterOccurrences(page, counters);
|
|
4960
|
+
for (const counter of counters) {
|
|
4961
|
+
const entry = scan.get(counter);
|
|
4962
|
+
if (entry.count > 1) {
|
|
4963
|
+
throw buildCounterAmbiguousError(counter);
|
|
4886
4964
|
}
|
|
4887
4965
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
if (!selector) continue;
|
|
4895
|
-
let count = 0;
|
|
4896
|
-
try {
|
|
4897
|
-
count = root.querySelectorAll(selector).length;
|
|
4898
|
-
} catch {
|
|
4899
|
-
count = 0;
|
|
4966
|
+
const valueCache = /* @__PURE__ */ new Map();
|
|
4967
|
+
for (const request of requests) {
|
|
4968
|
+
const normalized = normalizeCounter(request.counter);
|
|
4969
|
+
if (normalized == null) {
|
|
4970
|
+
out[request.key] = null;
|
|
4971
|
+
continue;
|
|
4900
4972
|
}
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
mode: "unique"
|
|
4906
|
-
};
|
|
4973
|
+
const entry = scan.get(normalized);
|
|
4974
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
4975
|
+
out[request.key] = null;
|
|
4976
|
+
continue;
|
|
4907
4977
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
mode: "fallback"
|
|
4913
|
-
};
|
|
4978
|
+
const cacheKey = `${normalized}:${request.attribute || ""}`;
|
|
4979
|
+
if (valueCache.has(cacheKey)) {
|
|
4980
|
+
out[request.key] = valueCache.get(cacheKey);
|
|
4981
|
+
continue;
|
|
4914
4982
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
let count = 0;
|
|
4923
|
-
try {
|
|
4924
|
-
count = document.querySelectorAll(selector).length;
|
|
4925
|
-
} catch {
|
|
4926
|
-
count = 0;
|
|
4983
|
+
const read = await readCounterValueInFrame(
|
|
4984
|
+
entry.frame,
|
|
4985
|
+
normalized,
|
|
4986
|
+
request.attribute
|
|
4987
|
+
);
|
|
4988
|
+
if (read.status === "ambiguous") {
|
|
4989
|
+
throw buildCounterAmbiguousError(normalized);
|
|
4927
4990
|
}
|
|
4928
|
-
|
|
4991
|
+
if (read.status === "missing") {
|
|
4992
|
+
valueCache.set(cacheKey, null);
|
|
4993
|
+
out[request.key] = null;
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
const normalizedValue = normalizeExtractedValue(
|
|
4997
|
+
read.value ?? null,
|
|
4998
|
+
request.attribute
|
|
4999
|
+
);
|
|
5000
|
+
valueCache.set(cacheKey, normalizedValue);
|
|
5001
|
+
out[request.key] = normalizedValue;
|
|
4929
5002
|
}
|
|
4930
5003
|
return out;
|
|
4931
5004
|
}
|
|
4932
|
-
function
|
|
4933
|
-
|
|
5005
|
+
function dedupeCounters(requests) {
|
|
5006
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4934
5007
|
const out = [];
|
|
4935
|
-
for (const
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
} catch {
|
|
4941
|
-
count = 0;
|
|
4942
|
-
}
|
|
4943
|
-
out.push({ selector, count });
|
|
5008
|
+
for (const request of requests) {
|
|
5009
|
+
const normalized = normalizeCounter(request.counter);
|
|
5010
|
+
if (normalized == null || seen.has(normalized)) continue;
|
|
5011
|
+
seen.add(normalized);
|
|
5012
|
+
out.push(normalized);
|
|
4944
5013
|
}
|
|
4945
5014
|
return out;
|
|
4946
5015
|
}
|
|
4947
|
-
function
|
|
4948
|
-
|
|
4949
|
-
if (!
|
|
4950
|
-
|
|
4951
|
-
return
|
|
4952
|
-
}
|
|
4953
|
-
function debugPath(message, data) {
|
|
4954
|
-
if (!isPathDebugEnabled()) return;
|
|
4955
|
-
if (data !== void 0) {
|
|
4956
|
-
console.log(`[opensteer:path] ${message}`, data);
|
|
4957
|
-
} else {
|
|
4958
|
-
console.log(`[opensteer:path] ${message}`);
|
|
4959
|
-
}
|
|
5016
|
+
function normalizeCounter(counter) {
|
|
5017
|
+
if (!Number.isFinite(counter)) return null;
|
|
5018
|
+
if (!Number.isInteger(counter)) return null;
|
|
5019
|
+
if (counter <= 0) return null;
|
|
5020
|
+
return counter;
|
|
4960
5021
|
}
|
|
4961
|
-
async function
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
5022
|
+
async function scanCounterOccurrences(page, counters) {
|
|
5023
|
+
const out = /* @__PURE__ */ new Map();
|
|
5024
|
+
for (const counter of counters) {
|
|
5025
|
+
out.set(counter, {
|
|
5026
|
+
count: 0,
|
|
5027
|
+
frame: null
|
|
5028
|
+
});
|
|
4966
5029
|
}
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
blocker: null
|
|
5030
|
+
if (!counters.length) return out;
|
|
5031
|
+
for (const frame of page.frames()) {
|
|
5032
|
+
let frameCounts;
|
|
5033
|
+
try {
|
|
5034
|
+
frameCounts = await frame.evaluate((candidates) => {
|
|
5035
|
+
const keys = new Set(candidates.map((value) => String(value)));
|
|
5036
|
+
const counts = {};
|
|
5037
|
+
for (const key of keys) {
|
|
5038
|
+
counts[key] = 0;
|
|
5039
|
+
}
|
|
5040
|
+
const walk = (root) => {
|
|
5041
|
+
const children = Array.from(root.children);
|
|
5042
|
+
for (const child of children) {
|
|
5043
|
+
const value = child.getAttribute("c");
|
|
5044
|
+
if (value && keys.has(value)) {
|
|
5045
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
5046
|
+
}
|
|
5047
|
+
walk(child);
|
|
5048
|
+
if (child.shadowRoot) {
|
|
5049
|
+
walk(child.shadowRoot);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
4990
5052
|
};
|
|
5053
|
+
walk(document);
|
|
5054
|
+
return counts;
|
|
5055
|
+
}, counters);
|
|
5056
|
+
} catch {
|
|
5057
|
+
continue;
|
|
5058
|
+
}
|
|
5059
|
+
for (const [rawCounter, rawCount] of Object.entries(frameCounts)) {
|
|
5060
|
+
const counter = Number.parseInt(rawCounter, 10);
|
|
5061
|
+
if (!Number.isFinite(counter)) continue;
|
|
5062
|
+
const count = Number(rawCount || 0);
|
|
5063
|
+
if (!Number.isFinite(count) || count <= 0) continue;
|
|
5064
|
+
const entry = out.get(counter);
|
|
5065
|
+
entry.count += count;
|
|
5066
|
+
if (!entry.frame) {
|
|
5067
|
+
entry.frame = frame;
|
|
4991
5068
|
}
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
editable = true;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
return out;
|
|
5072
|
+
}
|
|
5073
|
+
async function resolveUniqueHandleInFrame(frame, counter) {
|
|
5074
|
+
return frame.evaluateHandle((targetCounter) => {
|
|
5075
|
+
const matches = [];
|
|
5076
|
+
const walk = (root) => {
|
|
5077
|
+
const children = Array.from(root.children);
|
|
5078
|
+
for (const child of children) {
|
|
5079
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
5080
|
+
matches.push(child);
|
|
5081
|
+
}
|
|
5082
|
+
walk(child);
|
|
5083
|
+
if (child.shadowRoot) {
|
|
5084
|
+
walk(child.shadowRoot);
|
|
5085
|
+
}
|
|
5010
5086
|
}
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5087
|
+
};
|
|
5088
|
+
walk(document);
|
|
5089
|
+
if (matches.length !== 1) {
|
|
5090
|
+
return null;
|
|
5091
|
+
}
|
|
5092
|
+
return matches[0];
|
|
5093
|
+
}, String(counter));
|
|
5094
|
+
}
|
|
5095
|
+
async function readCounterValueInFrame(frame, counter, attribute) {
|
|
5096
|
+
try {
|
|
5097
|
+
return await frame.evaluate(
|
|
5098
|
+
({ targetCounter, attribute: attribute2 }) => {
|
|
5099
|
+
const matches = [];
|
|
5100
|
+
const walk = (root) => {
|
|
5101
|
+
const children = Array.from(root.children);
|
|
5102
|
+
for (const child of children) {
|
|
5103
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
5104
|
+
matches.push(child);
|
|
5105
|
+
}
|
|
5106
|
+
walk(child);
|
|
5107
|
+
if (child.shadowRoot) {
|
|
5108
|
+
walk(child.shadowRoot);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
};
|
|
5112
|
+
walk(document);
|
|
5113
|
+
if (!matches.length) {
|
|
5114
|
+
return {
|
|
5115
|
+
status: "missing"
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
if (matches.length > 1) {
|
|
5119
|
+
return {
|
|
5120
|
+
status: "ambiguous"
|
|
5030
5121
|
};
|
|
5031
5122
|
}
|
|
5123
|
+
const target = matches[0];
|
|
5124
|
+
const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
|
|
5125
|
+
return {
|
|
5126
|
+
status: "ok",
|
|
5127
|
+
value
|
|
5128
|
+
};
|
|
5129
|
+
},
|
|
5130
|
+
{
|
|
5131
|
+
targetCounter: String(counter),
|
|
5132
|
+
attribute
|
|
5032
5133
|
}
|
|
5033
|
-
|
|
5034
|
-
connected,
|
|
5035
|
-
visible: isVisible,
|
|
5036
|
-
enabled,
|
|
5037
|
-
editable,
|
|
5038
|
-
blocker
|
|
5039
|
-
};
|
|
5040
|
-
});
|
|
5134
|
+
);
|
|
5041
5135
|
} catch {
|
|
5042
|
-
return
|
|
5136
|
+
return {
|
|
5137
|
+
status: "missing"
|
|
5138
|
+
};
|
|
5043
5139
|
}
|
|
5044
5140
|
}
|
|
5141
|
+
function buildCounterNotFoundError(counter) {
|
|
5142
|
+
return new CounterResolutionError(
|
|
5143
|
+
"ERR_COUNTER_NOT_FOUND",
|
|
5144
|
+
`Counter ${counter} was not found in the live DOM.`
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
function buildCounterAmbiguousError(counter) {
|
|
5148
|
+
return new CounterResolutionError(
|
|
5149
|
+
"ERR_COUNTER_AMBIGUOUS",
|
|
5150
|
+
`Counter ${counter} matches multiple live elements.`
|
|
5151
|
+
);
|
|
5152
|
+
}
|
|
5045
5153
|
|
|
5046
5154
|
// src/actions/failure-classifier.ts
|
|
5047
5155
|
var ACTION_FAILURE_CODES = [
|
|
@@ -5092,7 +5200,7 @@ function defaultActionFailureMessage(action) {
|
|
|
5092
5200
|
function classifyActionFailure(input) {
|
|
5093
5201
|
const typed = classifyTypedError(input.error);
|
|
5094
5202
|
if (typed) return typed;
|
|
5095
|
-
const message =
|
|
5203
|
+
const message = extractErrorMessage2(input.error, input.fallbackMessage);
|
|
5096
5204
|
const fromCallLog = classifyFromPlaywrightMessage(message, input.probe);
|
|
5097
5205
|
if (fromCallLog) return fromCallLog;
|
|
5098
5206
|
const fromProbe = classifyFromProbe(input.probe);
|
|
@@ -5101,7 +5209,7 @@ function classifyActionFailure(input) {
|
|
|
5101
5209
|
if (fromHeuristic) return fromHeuristic;
|
|
5102
5210
|
return buildFailure({
|
|
5103
5211
|
code: "UNKNOWN",
|
|
5104
|
-
message: ensureMessage(input.fallbackMessage
|
|
5212
|
+
message: ensureMessage(message, input.fallbackMessage),
|
|
5105
5213
|
classificationSource: "unknown"
|
|
5106
5214
|
});
|
|
5107
5215
|
}
|
|
@@ -5157,13 +5265,6 @@ function classifyTypedError(error) {
|
|
|
5157
5265
|
classificationSource: "typed_error"
|
|
5158
5266
|
});
|
|
5159
5267
|
}
|
|
5160
|
-
if (error.code === "ERR_COUNTER_FRAME_UNAVAILABLE") {
|
|
5161
|
-
return buildFailure({
|
|
5162
|
-
code: "TARGET_UNAVAILABLE",
|
|
5163
|
-
message: error.message,
|
|
5164
|
-
classificationSource: "typed_error"
|
|
5165
|
-
});
|
|
5166
|
-
}
|
|
5167
5268
|
if (error.code === "ERR_COUNTER_AMBIGUOUS") {
|
|
5168
5269
|
return buildFailure({
|
|
5169
5270
|
code: "TARGET_AMBIGUOUS",
|
|
@@ -5171,13 +5272,6 @@ function classifyTypedError(error) {
|
|
|
5171
5272
|
classificationSource: "typed_error"
|
|
5172
5273
|
});
|
|
5173
5274
|
}
|
|
5174
|
-
if (error.code === "ERR_COUNTER_STALE_OR_NOT_FOUND") {
|
|
5175
|
-
return buildFailure({
|
|
5176
|
-
code: "TARGET_STALE",
|
|
5177
|
-
message: error.message,
|
|
5178
|
-
classificationSource: "typed_error"
|
|
5179
|
-
});
|
|
5180
|
-
}
|
|
5181
5275
|
}
|
|
5182
5276
|
return null;
|
|
5183
5277
|
}
|
|
@@ -5361,13 +5455,22 @@ function defaultRetryableForCode(code) {
|
|
|
5361
5455
|
return true;
|
|
5362
5456
|
}
|
|
5363
5457
|
}
|
|
5364
|
-
function
|
|
5458
|
+
function extractErrorMessage2(error, fallbackMessage) {
|
|
5365
5459
|
if (error instanceof Error && error.message.trim()) {
|
|
5366
5460
|
return error.message;
|
|
5367
5461
|
}
|
|
5368
5462
|
if (typeof error === "string" && error.trim()) {
|
|
5369
5463
|
return error.trim();
|
|
5370
5464
|
}
|
|
5465
|
+
if (error && typeof error === "object" && !Array.isArray(error)) {
|
|
5466
|
+
const record = error;
|
|
5467
|
+
if (typeof record.message === "string" && record.message.trim()) {
|
|
5468
|
+
return record.message.trim();
|
|
5469
|
+
}
|
|
5470
|
+
if (typeof record.error === "string" && record.error.trim()) {
|
|
5471
|
+
return record.error.trim();
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5371
5474
|
return ensureMessage(fallbackMessage, "Action failed.");
|
|
5372
5475
|
}
|
|
5373
5476
|
function ensureMessage(value, fallback) {
|
|
@@ -7677,7 +7780,8 @@ function withTokenQuery(wsUrl, token) {
|
|
|
7677
7780
|
// src/cloud/local-cache-sync.ts
|
|
7678
7781
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
7679
7782
|
var import_path5 = __toESM(require("path"), 1);
|
|
7680
|
-
function collectLocalSelectorCacheEntries(storage) {
|
|
7783
|
+
function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
7784
|
+
const debug = options.debug === true;
|
|
7681
7785
|
const namespace = storage.getNamespace();
|
|
7682
7786
|
const namespaceDir = storage.getNamespaceDir();
|
|
7683
7787
|
if (!import_fs3.default.existsSync(namespaceDir)) return [];
|
|
@@ -7686,7 +7790,7 @@ function collectLocalSelectorCacheEntries(storage) {
|
|
|
7686
7790
|
for (const fileName of fileNames) {
|
|
7687
7791
|
if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
|
|
7688
7792
|
const filePath = import_path5.default.join(namespaceDir, fileName);
|
|
7689
|
-
const selector = readSelectorFile(filePath);
|
|
7793
|
+
const selector = readSelectorFile(filePath, debug);
|
|
7690
7794
|
if (!selector) continue;
|
|
7691
7795
|
const descriptionHash = normalizeDescriptionHash(selector.id);
|
|
7692
7796
|
const method = normalizeMethod(selector.method);
|
|
@@ -7711,11 +7815,20 @@ function collectLocalSelectorCacheEntries(storage) {
|
|
|
7711
7815
|
}
|
|
7712
7816
|
return dedupeNewest(entries);
|
|
7713
7817
|
}
|
|
7714
|
-
function readSelectorFile(filePath) {
|
|
7818
|
+
function readSelectorFile(filePath, debug) {
|
|
7715
7819
|
try {
|
|
7716
7820
|
const raw = import_fs3.default.readFileSync(filePath, "utf8");
|
|
7717
7821
|
return JSON.parse(raw);
|
|
7718
|
-
} catch {
|
|
7822
|
+
} catch (error) {
|
|
7823
|
+
const message = extractErrorMessage(
|
|
7824
|
+
error,
|
|
7825
|
+
"Unable to parse selector cache file JSON."
|
|
7826
|
+
);
|
|
7827
|
+
if (debug) {
|
|
7828
|
+
console.warn(
|
|
7829
|
+
`[opensteer] failed to read local selector cache file "${filePath}": ${message}`
|
|
7830
|
+
);
|
|
7831
|
+
}
|
|
7719
7832
|
return null;
|
|
7720
7833
|
}
|
|
7721
7834
|
}
|
|
@@ -9923,7 +10036,9 @@ var Opensteer = class _Opensteer {
|
|
|
9923
10036
|
this.aiExtract = this.createLazyExtractCallback(model);
|
|
9924
10037
|
const rootDir = resolved.storage?.rootDir || process.cwd();
|
|
9925
10038
|
this.namespace = resolveNamespace(resolved, rootDir);
|
|
9926
|
-
this.storage = new LocalSelectorStorage(rootDir, this.namespace
|
|
10039
|
+
this.storage = new LocalSelectorStorage(rootDir, this.namespace, {
|
|
10040
|
+
debug: Boolean(resolved.debug)
|
|
10041
|
+
});
|
|
9927
10042
|
this.pool = new BrowserPool(resolved.browser || {});
|
|
9928
10043
|
if (cloudSelection.cloud) {
|
|
9929
10044
|
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
@@ -9942,6 +10057,14 @@ var Opensteer = class _Opensteer {
|
|
|
9942
10057
|
this.cloud = null;
|
|
9943
10058
|
}
|
|
9944
10059
|
}
|
|
10060
|
+
logDebugError(context, error) {
|
|
10061
|
+
if (!this.config.debug) return;
|
|
10062
|
+
const normalized = normalizeError(error, "Unknown error.");
|
|
10063
|
+
const codeSuffix = normalized.code && normalized.code.trim() ? ` [${normalized.code.trim()}]` : "";
|
|
10064
|
+
console.warn(
|
|
10065
|
+
`[opensteer] ${context}: ${normalized.message}${codeSuffix}`
|
|
10066
|
+
);
|
|
10067
|
+
}
|
|
9945
10068
|
createLazyResolveCallback(model) {
|
|
9946
10069
|
let resolverPromise = null;
|
|
9947
10070
|
return async (...args) => {
|
|
@@ -10032,7 +10155,8 @@ var Opensteer = class _Opensteer {
|
|
|
10032
10155
|
let tabs;
|
|
10033
10156
|
try {
|
|
10034
10157
|
tabs = await this.invokeCloudAction("tabs", {});
|
|
10035
|
-
} catch {
|
|
10158
|
+
} catch (error) {
|
|
10159
|
+
this.logDebugError("cloud page reference sync (tabs lookup) failed", error);
|
|
10036
10160
|
return;
|
|
10037
10161
|
}
|
|
10038
10162
|
if (!tabs.length) {
|
|
@@ -10170,12 +10294,7 @@ var Opensteer = class _Opensteer {
|
|
|
10170
10294
|
try {
|
|
10171
10295
|
await this.syncLocalSelectorCacheToCloud();
|
|
10172
10296
|
} catch (error) {
|
|
10173
|
-
|
|
10174
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
10175
|
-
console.warn(
|
|
10176
|
-
`[opensteer] cloud selector cache sync failed: ${message}`
|
|
10177
|
-
);
|
|
10178
|
-
}
|
|
10297
|
+
this.logDebugError("cloud selector cache sync failed", error);
|
|
10179
10298
|
}
|
|
10180
10299
|
localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
|
|
10181
10300
|
this.cloud.localRunId = localRunId;
|
|
@@ -10207,7 +10326,12 @@ var Opensteer = class _Opensteer {
|
|
|
10207
10326
|
this.cloud.actionClient = actionClient;
|
|
10208
10327
|
this.cloud.sessionId = sessionId;
|
|
10209
10328
|
this.cloud.cloudSessionUrl = session3.cloudSessionUrl;
|
|
10210
|
-
await this.syncCloudPageRef().catch(() =>
|
|
10329
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
10330
|
+
this.logDebugError(
|
|
10331
|
+
"cloud page reference sync after launch failed",
|
|
10332
|
+
error
|
|
10333
|
+
);
|
|
10334
|
+
});
|
|
10211
10335
|
this.announceCloudSession({
|
|
10212
10336
|
sessionId: session3.sessionId,
|
|
10213
10337
|
workspaceId: session3.cloudSession.workspaceId,
|
|
@@ -10294,7 +10418,9 @@ var Opensteer = class _Opensteer {
|
|
|
10294
10418
|
}
|
|
10295
10419
|
async syncLocalSelectorCacheToCloud() {
|
|
10296
10420
|
if (!this.cloud) return;
|
|
10297
|
-
const entries = collectLocalSelectorCacheEntries(this.storage
|
|
10421
|
+
const entries = collectLocalSelectorCacheEntries(this.storage, {
|
|
10422
|
+
debug: Boolean(this.config.debug)
|
|
10423
|
+
});
|
|
10298
10424
|
if (!entries.length) return;
|
|
10299
10425
|
await this.cloud.sessionClient.importSelectorCache({
|
|
10300
10426
|
entries
|
|
@@ -10303,9 +10429,12 @@ var Opensteer = class _Opensteer {
|
|
|
10303
10429
|
async goto(url, options) {
|
|
10304
10430
|
if (this.cloud) {
|
|
10305
10431
|
await this.invokeCloudActionAndResetCache("goto", { url, options });
|
|
10306
|
-
await this.syncCloudPageRef({ expectedUrl: url }).catch(
|
|
10307
|
-
(
|
|
10308
|
-
|
|
10432
|
+
await this.syncCloudPageRef({ expectedUrl: url }).catch((error) => {
|
|
10433
|
+
this.logDebugError(
|
|
10434
|
+
"cloud page reference sync after goto failed",
|
|
10435
|
+
error
|
|
10436
|
+
);
|
|
10437
|
+
});
|
|
10309
10438
|
return;
|
|
10310
10439
|
}
|
|
10311
10440
|
const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
|
|
@@ -10406,7 +10535,7 @@ var Opensteer = class _Opensteer {
|
|
|
10406
10535
|
let persistPath = null;
|
|
10407
10536
|
try {
|
|
10408
10537
|
if (storageKey && resolution.shouldPersist) {
|
|
10409
|
-
persistPath = await this.
|
|
10538
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10410
10539
|
handle,
|
|
10411
10540
|
"hover",
|
|
10412
10541
|
resolution.counter
|
|
@@ -10505,7 +10634,7 @@ var Opensteer = class _Opensteer {
|
|
|
10505
10634
|
let persistPath = null;
|
|
10506
10635
|
try {
|
|
10507
10636
|
if (storageKey && resolution.shouldPersist) {
|
|
10508
|
-
persistPath = await this.
|
|
10637
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10509
10638
|
handle,
|
|
10510
10639
|
"input",
|
|
10511
10640
|
resolution.counter
|
|
@@ -10608,7 +10737,7 @@ var Opensteer = class _Opensteer {
|
|
|
10608
10737
|
let persistPath = null;
|
|
10609
10738
|
try {
|
|
10610
10739
|
if (storageKey && resolution.shouldPersist) {
|
|
10611
|
-
persistPath = await this.
|
|
10740
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10612
10741
|
handle,
|
|
10613
10742
|
"select",
|
|
10614
10743
|
resolution.counter
|
|
@@ -10718,7 +10847,7 @@ var Opensteer = class _Opensteer {
|
|
|
10718
10847
|
let persistPath = null;
|
|
10719
10848
|
try {
|
|
10720
10849
|
if (storageKey && resolution.shouldPersist) {
|
|
10721
|
-
persistPath = await this.
|
|
10850
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10722
10851
|
handle,
|
|
10723
10852
|
"scroll",
|
|
10724
10853
|
resolution.counter
|
|
@@ -10817,7 +10946,12 @@ var Opensteer = class _Opensteer {
|
|
|
10817
10946
|
}
|
|
10818
10947
|
);
|
|
10819
10948
|
await this.syncCloudPageRef({ expectedUrl: result.url }).catch(
|
|
10820
|
-
() =>
|
|
10949
|
+
(error) => {
|
|
10950
|
+
this.logDebugError(
|
|
10951
|
+
"cloud page reference sync after newTab failed",
|
|
10952
|
+
error
|
|
10953
|
+
);
|
|
10954
|
+
}
|
|
10821
10955
|
);
|
|
10822
10956
|
return result;
|
|
10823
10957
|
}
|
|
@@ -10829,7 +10963,12 @@ var Opensteer = class _Opensteer {
|
|
|
10829
10963
|
async switchTab(index) {
|
|
10830
10964
|
if (this.cloud) {
|
|
10831
10965
|
await this.invokeCloudActionAndResetCache("switchTab", { index });
|
|
10832
|
-
await this.syncCloudPageRef().catch(() =>
|
|
10966
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
10967
|
+
this.logDebugError(
|
|
10968
|
+
"cloud page reference sync after switchTab failed",
|
|
10969
|
+
error
|
|
10970
|
+
);
|
|
10971
|
+
});
|
|
10833
10972
|
return;
|
|
10834
10973
|
}
|
|
10835
10974
|
const page = await switchTab(this.context, index);
|
|
@@ -10839,7 +10978,12 @@ var Opensteer = class _Opensteer {
|
|
|
10839
10978
|
async closeTab(index) {
|
|
10840
10979
|
if (this.cloud) {
|
|
10841
10980
|
await this.invokeCloudActionAndResetCache("closeTab", { index });
|
|
10842
|
-
await this.syncCloudPageRef().catch(() =>
|
|
10981
|
+
await this.syncCloudPageRef().catch((error) => {
|
|
10982
|
+
this.logDebugError(
|
|
10983
|
+
"cloud page reference sync after closeTab failed",
|
|
10984
|
+
error
|
|
10985
|
+
);
|
|
10986
|
+
});
|
|
10843
10987
|
return;
|
|
10844
10988
|
}
|
|
10845
10989
|
const newPage = await closeTab(this.context, this.page, index);
|
|
@@ -10999,22 +11143,28 @@ var Opensteer = class _Opensteer {
|
|
|
10999
11143
|
const handle = await this.resolveCounterHandle(resolution.counter);
|
|
11000
11144
|
try {
|
|
11001
11145
|
if (storageKey && resolution.shouldPersist) {
|
|
11002
|
-
const persistPath = await this.
|
|
11146
|
+
const persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11003
11147
|
handle,
|
|
11004
11148
|
method,
|
|
11005
11149
|
resolution.counter
|
|
11006
11150
|
);
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11151
|
+
if (persistPath) {
|
|
11152
|
+
this.persistPath(
|
|
11153
|
+
storageKey,
|
|
11154
|
+
method,
|
|
11155
|
+
options.description,
|
|
11156
|
+
persistPath
|
|
11157
|
+
);
|
|
11158
|
+
}
|
|
11013
11159
|
}
|
|
11014
11160
|
return await counterFn(handle);
|
|
11015
11161
|
} catch (err) {
|
|
11016
|
-
|
|
11017
|
-
|
|
11162
|
+
if (err instanceof Error) {
|
|
11163
|
+
throw err;
|
|
11164
|
+
}
|
|
11165
|
+
throw new Error(
|
|
11166
|
+
`${method} failed. ${extractErrorMessage(err, "Unknown error.")}`
|
|
11167
|
+
);
|
|
11018
11168
|
} finally {
|
|
11019
11169
|
await handle.dispose();
|
|
11020
11170
|
}
|
|
@@ -11043,7 +11193,7 @@ var Opensteer = class _Opensteer {
|
|
|
11043
11193
|
let persistPath = null;
|
|
11044
11194
|
try {
|
|
11045
11195
|
if (storageKey && resolution.shouldPersist) {
|
|
11046
|
-
persistPath = await this.
|
|
11196
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11047
11197
|
handle,
|
|
11048
11198
|
"uploadFile",
|
|
11049
11199
|
resolution.counter
|
|
@@ -11141,6 +11291,9 @@ var Opensteer = class _Opensteer {
|
|
|
11141
11291
|
if (this.cloud) {
|
|
11142
11292
|
return await this.invokeCloudAction("extract", options);
|
|
11143
11293
|
}
|
|
11294
|
+
if (options.schema !== void 0) {
|
|
11295
|
+
assertValidExtractSchemaRoot(options.schema);
|
|
11296
|
+
}
|
|
11144
11297
|
const storageKey = this.resolveStorageKey(options.description);
|
|
11145
11298
|
const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
|
|
11146
11299
|
const stored = storageKey ? this.storage.readSelector(storageKey) : null;
|
|
@@ -11149,7 +11302,7 @@ var Opensteer = class _Opensteer {
|
|
|
11149
11302
|
try {
|
|
11150
11303
|
payload = normalizePersistedExtractPayload(stored.path);
|
|
11151
11304
|
} catch (err) {
|
|
11152
|
-
const message = err
|
|
11305
|
+
const message = extractErrorMessage(err, "Unknown error.");
|
|
11153
11306
|
const selectorFile = storageKey ? this.storage.getSelectorPath(storageKey) : "unknown selector file";
|
|
11154
11307
|
throw new Error(
|
|
11155
11308
|
`Cached extraction selector is invalid for the current schema at "${selectorFile}". Delete the cached selector and rerun extraction. ${message}`
|
|
@@ -11166,7 +11319,16 @@ var Opensteer = class _Opensteer {
|
|
|
11166
11319
|
fields.push(...schemaFields);
|
|
11167
11320
|
}
|
|
11168
11321
|
if (!fields.length) {
|
|
11169
|
-
|
|
11322
|
+
let planResult;
|
|
11323
|
+
try {
|
|
11324
|
+
planResult = await this.parseAiExtractPlan(options);
|
|
11325
|
+
} catch (error) {
|
|
11326
|
+
const message = extractErrorMessage(error, "Unknown error.");
|
|
11327
|
+
const contextMessage = options.schema ? "Schema extraction did not resolve deterministic field targets, so Opensteer attempted AI extraction planning." : "Opensteer attempted AI extraction planning.";
|
|
11328
|
+
throw new Error(`${contextMessage} ${message}`, {
|
|
11329
|
+
cause: error
|
|
11330
|
+
});
|
|
11331
|
+
}
|
|
11170
11332
|
if (planResult.fields.length) {
|
|
11171
11333
|
fields.push(...planResult.fields);
|
|
11172
11334
|
} else if (planResult.data !== void 0) {
|
|
@@ -11307,7 +11469,7 @@ var Opensteer = class _Opensteer {
|
|
|
11307
11469
|
let persistPath = null;
|
|
11308
11470
|
try {
|
|
11309
11471
|
if (storageKey && resolution.shouldPersist) {
|
|
11310
|
-
persistPath = await this.
|
|
11472
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11311
11473
|
handle,
|
|
11312
11474
|
"click",
|
|
11313
11475
|
resolution.counter
|
|
@@ -11403,17 +11565,6 @@ var Opensteer = class _Opensteer {
|
|
|
11403
11565
|
}
|
|
11404
11566
|
}
|
|
11405
11567
|
if (options.element != null) {
|
|
11406
|
-
const pathFromElement = await this.tryBuildPathFromCounter(
|
|
11407
|
-
options.element
|
|
11408
|
-
);
|
|
11409
|
-
if (pathFromElement) {
|
|
11410
|
-
return {
|
|
11411
|
-
path: pathFromElement,
|
|
11412
|
-
counter: null,
|
|
11413
|
-
shouldPersist: Boolean(storageKey),
|
|
11414
|
-
source: "element"
|
|
11415
|
-
};
|
|
11416
|
-
}
|
|
11417
11568
|
return {
|
|
11418
11569
|
path: null,
|
|
11419
11570
|
counter: options.element,
|
|
@@ -11441,17 +11592,6 @@ var Opensteer = class _Opensteer {
|
|
|
11441
11592
|
options.description
|
|
11442
11593
|
);
|
|
11443
11594
|
if (resolved?.counter != null) {
|
|
11444
|
-
const pathFromAiCounter = await this.tryBuildPathFromCounter(
|
|
11445
|
-
resolved.counter
|
|
11446
|
-
);
|
|
11447
|
-
if (pathFromAiCounter) {
|
|
11448
|
-
return {
|
|
11449
|
-
path: pathFromAiCounter,
|
|
11450
|
-
counter: null,
|
|
11451
|
-
shouldPersist: Boolean(storageKey),
|
|
11452
|
-
source: "ai"
|
|
11453
|
-
};
|
|
11454
|
-
}
|
|
11455
11595
|
return {
|
|
11456
11596
|
path: null,
|
|
11457
11597
|
counter: resolved.counter,
|
|
@@ -11533,23 +11673,22 @@ var Opensteer = class _Opensteer {
|
|
|
11533
11673
|
try {
|
|
11534
11674
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11535
11675
|
if (builtPath) {
|
|
11536
|
-
|
|
11676
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11677
|
+
handle,
|
|
11678
|
+
builtPath
|
|
11679
|
+
);
|
|
11680
|
+
return this.withIndexedIframeContext(
|
|
11681
|
+
withFrameContext,
|
|
11682
|
+
indexedPath
|
|
11683
|
+
);
|
|
11537
11684
|
}
|
|
11538
11685
|
return indexedPath;
|
|
11539
11686
|
} finally {
|
|
11540
11687
|
await handle.dispose();
|
|
11541
11688
|
}
|
|
11542
11689
|
}
|
|
11543
|
-
async tryBuildPathFromCounter(counter) {
|
|
11544
|
-
try {
|
|
11545
|
-
return await this.buildPathFromElement(counter);
|
|
11546
|
-
} catch {
|
|
11547
|
-
return null;
|
|
11548
|
-
}
|
|
11549
|
-
}
|
|
11550
11690
|
async resolveCounterHandle(element) {
|
|
11551
|
-
|
|
11552
|
-
return resolveCounterElement(this.page, snapshot, element);
|
|
11691
|
+
return resolveCounterElement(this.page, element);
|
|
11553
11692
|
}
|
|
11554
11693
|
async resolveCounterHandleForAction(action, description, element) {
|
|
11555
11694
|
try {
|
|
@@ -11573,8 +11712,12 @@ var Opensteer = class _Opensteer {
|
|
|
11573
11712
|
const indexedPath = await this.readPathFromCounterIndex(counter);
|
|
11574
11713
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11575
11714
|
if (builtPath) {
|
|
11715
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11716
|
+
handle,
|
|
11717
|
+
builtPath
|
|
11718
|
+
);
|
|
11576
11719
|
const normalized = this.withIndexedIframeContext(
|
|
11577
|
-
|
|
11720
|
+
withFrameContext,
|
|
11578
11721
|
indexedPath
|
|
11579
11722
|
);
|
|
11580
11723
|
if (normalized.nodes.length) return normalized;
|
|
@@ -11584,15 +11727,34 @@ var Opensteer = class _Opensteer {
|
|
|
11584
11727
|
`Unable to build element path from counter ${counter} during ${action}.`
|
|
11585
11728
|
);
|
|
11586
11729
|
}
|
|
11730
|
+
async tryBuildPathFromResolvedHandle(handle, action, counter) {
|
|
11731
|
+
try {
|
|
11732
|
+
return await this.buildPathFromResolvedHandle(handle, action, counter);
|
|
11733
|
+
} catch (error) {
|
|
11734
|
+
this.logDebugError(
|
|
11735
|
+
`path persistence skipped for ${action} counter ${counter}`,
|
|
11736
|
+
error
|
|
11737
|
+
);
|
|
11738
|
+
return null;
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11587
11741
|
withIndexedIframeContext(builtPath, indexedPath) {
|
|
11588
11742
|
const normalizedBuilt = this.normalizePath(builtPath);
|
|
11589
11743
|
if (!indexedPath) return normalizedBuilt;
|
|
11590
11744
|
const iframePrefix = collectIframeContextPrefix(indexedPath);
|
|
11591
11745
|
if (!iframePrefix.length) return normalizedBuilt;
|
|
11746
|
+
const builtContext = cloneContextHops(normalizedBuilt.context);
|
|
11747
|
+
const overlap = measureContextOverlap(iframePrefix, builtContext);
|
|
11748
|
+
const missingPrefix = cloneContextHops(
|
|
11749
|
+
iframePrefix.slice(0, iframePrefix.length - overlap)
|
|
11750
|
+
);
|
|
11751
|
+
if (!missingPrefix.length) {
|
|
11752
|
+
return normalizedBuilt;
|
|
11753
|
+
}
|
|
11592
11754
|
const merged = {
|
|
11593
11755
|
context: [
|
|
11594
|
-
...
|
|
11595
|
-
...
|
|
11756
|
+
...missingPrefix,
|
|
11757
|
+
...builtContext
|
|
11596
11758
|
],
|
|
11597
11759
|
nodes: cloneElementPath(normalizedBuilt).nodes
|
|
11598
11760
|
};
|
|
@@ -11602,9 +11764,48 @@ var Opensteer = class _Opensteer {
|
|
|
11602
11764
|
if (fallback.nodes.length) return fallback;
|
|
11603
11765
|
return normalizedBuilt;
|
|
11604
11766
|
}
|
|
11767
|
+
async withHandleIframeContext(handle, path5) {
|
|
11768
|
+
const ownFrame = await handle.ownerFrame();
|
|
11769
|
+
if (!ownFrame) {
|
|
11770
|
+
return this.normalizePath(path5);
|
|
11771
|
+
}
|
|
11772
|
+
let frame = ownFrame;
|
|
11773
|
+
let prefix2 = [];
|
|
11774
|
+
while (frame && frame !== this.page.mainFrame()) {
|
|
11775
|
+
const parent = frame.parentFrame();
|
|
11776
|
+
if (!parent) break;
|
|
11777
|
+
const frameElement = await frame.frameElement().catch(() => null);
|
|
11778
|
+
if (!frameElement) break;
|
|
11779
|
+
try {
|
|
11780
|
+
const frameElementPath = await buildElementPathFromHandle(frameElement);
|
|
11781
|
+
if (frameElementPath?.nodes.length) {
|
|
11782
|
+
const segment = [
|
|
11783
|
+
...cloneContextHops(frameElementPath.context),
|
|
11784
|
+
{
|
|
11785
|
+
kind: "iframe",
|
|
11786
|
+
host: cloneElementPath(frameElementPath).nodes
|
|
11787
|
+
}
|
|
11788
|
+
];
|
|
11789
|
+
prefix2 = [...segment, ...prefix2];
|
|
11790
|
+
}
|
|
11791
|
+
} finally {
|
|
11792
|
+
await frameElement.dispose().catch(() => void 0);
|
|
11793
|
+
}
|
|
11794
|
+
frame = parent;
|
|
11795
|
+
}
|
|
11796
|
+
if (!prefix2.length) {
|
|
11797
|
+
return this.normalizePath(path5);
|
|
11798
|
+
}
|
|
11799
|
+
return this.normalizePath({
|
|
11800
|
+
context: [...prefix2, ...cloneContextHops(path5.context)],
|
|
11801
|
+
nodes: cloneElementPath(path5).nodes
|
|
11802
|
+
});
|
|
11803
|
+
}
|
|
11605
11804
|
async readPathFromCounterIndex(counter) {
|
|
11606
|
-
|
|
11607
|
-
|
|
11805
|
+
if (!this.snapshotCache || this.snapshotCache.url !== this.page.url() || !this.snapshotCache.counterIndex) {
|
|
11806
|
+
return null;
|
|
11807
|
+
}
|
|
11808
|
+
const indexed = this.snapshotCache.counterIndex.get(counter);
|
|
11608
11809
|
if (!indexed) return null;
|
|
11609
11810
|
const normalized = this.normalizePath(indexed);
|
|
11610
11811
|
if (!normalized.nodes.length) return null;
|
|
@@ -11615,15 +11816,6 @@ var Opensteer = class _Opensteer {
|
|
|
11615
11816
|
if (!path5) return null;
|
|
11616
11817
|
return this.normalizePath(path5);
|
|
11617
11818
|
}
|
|
11618
|
-
async ensureSnapshotWithCounters() {
|
|
11619
|
-
if (!this.snapshotCache || !this.snapshotCache.counterBindings || this.snapshotCache.url !== this.page.url()) {
|
|
11620
|
-
await this.snapshot({
|
|
11621
|
-
mode: "full",
|
|
11622
|
-
withCounters: true
|
|
11623
|
-
});
|
|
11624
|
-
}
|
|
11625
|
-
return this.snapshotCache;
|
|
11626
|
-
}
|
|
11627
11819
|
persistPath(id, method, description, path5) {
|
|
11628
11820
|
const now = Date.now();
|
|
11629
11821
|
const safeFile = this.storage.getSelectorFileName(id);
|
|
@@ -11828,12 +12020,6 @@ var Opensteer = class _Opensteer {
|
|
|
11828
12020
|
};
|
|
11829
12021
|
}
|
|
11830
12022
|
async buildFieldTargetsFromSchema(schema) {
|
|
11831
|
-
if (!schema || typeof schema !== "object") {
|
|
11832
|
-
return [];
|
|
11833
|
-
}
|
|
11834
|
-
if (Array.isArray(schema)) {
|
|
11835
|
-
return [];
|
|
11836
|
-
}
|
|
11837
12023
|
const fields = [];
|
|
11838
12024
|
await this.collectFieldTargetsFromSchemaObject(
|
|
11839
12025
|
schema,
|
|
@@ -11879,17 +12065,6 @@ var Opensteer = class _Opensteer {
|
|
|
11879
12065
|
return;
|
|
11880
12066
|
}
|
|
11881
12067
|
if (normalized.element != null) {
|
|
11882
|
-
const path5 = await this.tryBuildPathFromCounter(
|
|
11883
|
-
normalized.element
|
|
11884
|
-
);
|
|
11885
|
-
if (path5) {
|
|
11886
|
-
fields.push({
|
|
11887
|
-
key: fieldKey,
|
|
11888
|
-
path: path5,
|
|
11889
|
-
attribute: normalized.attribute
|
|
11890
|
-
});
|
|
11891
|
-
return;
|
|
11892
|
-
}
|
|
11893
12068
|
fields.push({
|
|
11894
12069
|
key: fieldKey,
|
|
11895
12070
|
counter: normalized.element,
|
|
@@ -11907,6 +12082,10 @@ var Opensteer = class _Opensteer {
|
|
|
11907
12082
|
path: path5,
|
|
11908
12083
|
attribute: normalized.attribute
|
|
11909
12084
|
});
|
|
12085
|
+
} else {
|
|
12086
|
+
throw new Error(
|
|
12087
|
+
`Extraction schema field "${fieldKey}" uses selector "${normalized.selector}", but no matching element path could be built from the current page snapshot.`
|
|
12088
|
+
);
|
|
11910
12089
|
}
|
|
11911
12090
|
return;
|
|
11912
12091
|
}
|
|
@@ -11930,15 +12109,6 @@ var Opensteer = class _Opensteer {
|
|
|
11930
12109
|
continue;
|
|
11931
12110
|
}
|
|
11932
12111
|
if (fieldPlan.element != null) {
|
|
11933
|
-
const path6 = await this.tryBuildPathFromCounter(fieldPlan.element);
|
|
11934
|
-
if (path6) {
|
|
11935
|
-
fields.push({
|
|
11936
|
-
key,
|
|
11937
|
-
path: path6,
|
|
11938
|
-
attribute: fieldPlan.attribute
|
|
11939
|
-
});
|
|
11940
|
-
continue;
|
|
11941
|
-
}
|
|
11942
12112
|
fields.push({
|
|
11943
12113
|
key,
|
|
11944
12114
|
counter: fieldPlan.element,
|
|
@@ -11993,12 +12163,7 @@ var Opensteer = class _Opensteer {
|
|
|
11993
12163
|
}
|
|
11994
12164
|
}
|
|
11995
12165
|
if (counterRequests.length) {
|
|
11996
|
-
const
|
|
11997
|
-
const counterValues = await resolveCountersBatch(
|
|
11998
|
-
this.page,
|
|
11999
|
-
snapshot,
|
|
12000
|
-
counterRequests
|
|
12001
|
-
);
|
|
12166
|
+
const counterValues = await resolveCountersBatch(this.page, counterRequests);
|
|
12002
12167
|
Object.assign(result, counterValues);
|
|
12003
12168
|
}
|
|
12004
12169
|
if (pathFields.length) {
|
|
@@ -12028,7 +12193,7 @@ var Opensteer = class _Opensteer {
|
|
|
12028
12193
|
const path5 = await this.buildPathFromElement(field.counter);
|
|
12029
12194
|
if (!path5) {
|
|
12030
12195
|
throw new Error(
|
|
12031
|
-
`Unable to
|
|
12196
|
+
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
12032
12197
|
);
|
|
12033
12198
|
}
|
|
12034
12199
|
resolved.push({
|
|
@@ -12050,7 +12215,7 @@ var Opensteer = class _Opensteer {
|
|
|
12050
12215
|
}
|
|
12051
12216
|
resolveStorageKey(description) {
|
|
12052
12217
|
if (!description) return null;
|
|
12053
|
-
return (0,
|
|
12218
|
+
return (0, import_crypto.createHash)("sha256").update(description).digest("hex").slice(0, 16);
|
|
12054
12219
|
}
|
|
12055
12220
|
normalizePath(path5) {
|
|
12056
12221
|
return sanitizeElementPath(path5);
|
|
@@ -12074,6 +12239,33 @@ function collectIframeContextPrefix(path5) {
|
|
|
12074
12239
|
if (lastIframeIndex < 0) return [];
|
|
12075
12240
|
return cloneContextHops(context.slice(0, lastIframeIndex + 1));
|
|
12076
12241
|
}
|
|
12242
|
+
function measureContextOverlap(indexedPrefix, builtContext) {
|
|
12243
|
+
const maxOverlap = Math.min(indexedPrefix.length, builtContext.length);
|
|
12244
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
12245
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, true)) {
|
|
12246
|
+
return size;
|
|
12247
|
+
}
|
|
12248
|
+
}
|
|
12249
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
12250
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, false)) {
|
|
12251
|
+
return size;
|
|
12252
|
+
}
|
|
12253
|
+
}
|
|
12254
|
+
return 0;
|
|
12255
|
+
}
|
|
12256
|
+
function matchesContextPrefix(indexedPrefix, builtContext, size, strictHost) {
|
|
12257
|
+
for (let idx = 0; idx < size; idx += 1) {
|
|
12258
|
+
const left = indexedPrefix[indexedPrefix.length - size + idx];
|
|
12259
|
+
const right = builtContext[idx];
|
|
12260
|
+
if (left.kind !== right.kind) {
|
|
12261
|
+
return false;
|
|
12262
|
+
}
|
|
12263
|
+
if (strictHost && JSON.stringify(left.host) !== JSON.stringify(right.host)) {
|
|
12264
|
+
return false;
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
return true;
|
|
12268
|
+
}
|
|
12077
12269
|
function normalizeSchemaValue(value) {
|
|
12078
12270
|
if (!value) return null;
|
|
12079
12271
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -12095,7 +12287,7 @@ function normalizeExtractSource(source) {
|
|
|
12095
12287
|
}
|
|
12096
12288
|
function computeSchemaHash(schema) {
|
|
12097
12289
|
const stable = stableStringify(schema);
|
|
12098
|
-
return (0,
|
|
12290
|
+
return (0, import_crypto.createHash)("sha256").update(stable).digest("hex");
|
|
12099
12291
|
}
|
|
12100
12292
|
function buildPathMap(fields) {
|
|
12101
12293
|
const out = {};
|
|
@@ -12257,13 +12449,28 @@ function countNonNullLeaves(value) {
|
|
|
12257
12449
|
function isPrimitiveLike(value) {
|
|
12258
12450
|
return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
12259
12451
|
}
|
|
12452
|
+
function assertValidExtractSchemaRoot(schema) {
|
|
12453
|
+
if (!schema || typeof schema !== "object") {
|
|
12454
|
+
throw new Error(
|
|
12455
|
+
"Invalid extraction schema: expected a JSON object at the top level."
|
|
12456
|
+
);
|
|
12457
|
+
}
|
|
12458
|
+
if (Array.isArray(schema)) {
|
|
12459
|
+
throw new Error(
|
|
12460
|
+
'Invalid extraction schema: top-level arrays are not supported. Wrap array fields in an object (for example {"items":[...]}).'
|
|
12461
|
+
);
|
|
12462
|
+
}
|
|
12463
|
+
}
|
|
12260
12464
|
function parseAiExtractResponse(response) {
|
|
12261
12465
|
if (typeof response === "string") {
|
|
12262
12466
|
const trimmed = stripCodeFence2(response);
|
|
12263
12467
|
try {
|
|
12264
12468
|
return JSON.parse(trimmed);
|
|
12265
12469
|
} catch {
|
|
12266
|
-
|
|
12470
|
+
const preview = summarizeForError(trimmed);
|
|
12471
|
+
throw new Error(
|
|
12472
|
+
`LLM extraction returned a non-JSON response.${preview ? ` Preview: "${preview}"` : ""}`
|
|
12473
|
+
);
|
|
12267
12474
|
}
|
|
12268
12475
|
}
|
|
12269
12476
|
if (response && typeof response === "object") {
|
|
@@ -12288,6 +12495,12 @@ function stripCodeFence2(input) {
|
|
|
12288
12495
|
if (lastFence === -1) return withoutHeader.trim();
|
|
12289
12496
|
return withoutHeader.slice(0, lastFence).trim();
|
|
12290
12497
|
}
|
|
12498
|
+
function summarizeForError(input, maxLength = 180) {
|
|
12499
|
+
const compact = input.replace(/\s+/g, " ").trim();
|
|
12500
|
+
if (!compact) return "";
|
|
12501
|
+
if (compact.length <= maxLength) return compact;
|
|
12502
|
+
return `${compact.slice(0, maxLength)}...`;
|
|
12503
|
+
}
|
|
12291
12504
|
function getScrollDelta2(options) {
|
|
12292
12505
|
const amount = typeof options.amount === "number" ? options.amount : 600;
|
|
12293
12506
|
const absoluteAmount = Math.abs(amount);
|
|
@@ -12310,7 +12523,7 @@ function isInternalOrBlankPageUrl(url) {
|
|
|
12310
12523
|
}
|
|
12311
12524
|
function buildLocalRunId(namespace) {
|
|
12312
12525
|
const normalized = namespace.trim() || "default";
|
|
12313
|
-
return `${normalized}-${Date.now().toString(36)}-${(0,
|
|
12526
|
+
return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
|
|
12314
12527
|
}
|
|
12315
12528
|
|
|
12316
12529
|
// src/cli/paths.ts
|
|
@@ -12627,7 +12840,16 @@ function enqueueRequest(request, socket) {
|
|
|
12627
12840
|
void handleRequest(request, socket);
|
|
12628
12841
|
return;
|
|
12629
12842
|
}
|
|
12630
|
-
requestQueue = requestQueue.then(() => handleRequest(request, socket)).catch(() => {
|
|
12843
|
+
requestQueue = requestQueue.then(() => handleRequest(request, socket)).catch((error) => {
|
|
12844
|
+
sendResponse(
|
|
12845
|
+
socket,
|
|
12846
|
+
buildErrorResponse(
|
|
12847
|
+
request.id,
|
|
12848
|
+
error,
|
|
12849
|
+
"Unexpected server error while handling request.",
|
|
12850
|
+
"CLI_INTERNAL_ERROR"
|
|
12851
|
+
)
|
|
12852
|
+
);
|
|
12631
12853
|
});
|
|
12632
12854
|
}
|
|
12633
12855
|
async function handleRequest(request, socket) {
|
|
@@ -12636,7 +12858,11 @@ async function handleRequest(request, socket) {
|
|
|
12636
12858
|
sendResponse(socket, {
|
|
12637
12859
|
id,
|
|
12638
12860
|
ok: false,
|
|
12639
|
-
error: `Session '${session}' is shutting down
|
|
12861
|
+
error: `Session '${session}' is shutting down.`,
|
|
12862
|
+
errorInfo: {
|
|
12863
|
+
message: `Session '${session}' is shutting down.`,
|
|
12864
|
+
code: "SESSION_SHUTTING_DOWN"
|
|
12865
|
+
}
|
|
12640
12866
|
});
|
|
12641
12867
|
return;
|
|
12642
12868
|
}
|
|
@@ -12644,7 +12870,11 @@ async function handleRequest(request, socket) {
|
|
|
12644
12870
|
sendResponse(socket, {
|
|
12645
12871
|
id,
|
|
12646
12872
|
ok: false,
|
|
12647
|
-
error: `Session '${session}' is shutting down. Retry your command
|
|
12873
|
+
error: `Session '${session}' is shutting down. Retry your command.`,
|
|
12874
|
+
errorInfo: {
|
|
12875
|
+
message: `Session '${session}' is shutting down. Retry your command.`,
|
|
12876
|
+
code: "SESSION_SHUTTING_DOWN"
|
|
12877
|
+
}
|
|
12648
12878
|
});
|
|
12649
12879
|
return;
|
|
12650
12880
|
}
|
|
@@ -12660,7 +12890,16 @@ async function handleRequest(request, socket) {
|
|
|
12660
12890
|
sendResponse(socket, {
|
|
12661
12891
|
id,
|
|
12662
12892
|
ok: false,
|
|
12663
|
-
error: `Session '${session}' is already bound to selector namespace '${selectorNamespace}'. Requested '${requestedName}' does not match. Use the same --name for this session or start a different --session
|
|
12893
|
+
error: `Session '${session}' is already bound to selector namespace '${selectorNamespace}'. Requested '${requestedName}' does not match. Use the same --name for this session or start a different --session.`,
|
|
12894
|
+
errorInfo: {
|
|
12895
|
+
message: `Session '${session}' is already bound to selector namespace '${selectorNamespace}'. Requested '${requestedName}' does not match. Use the same --name for this session or start a different --session.`,
|
|
12896
|
+
code: "SESSION_NAMESPACE_MISMATCH",
|
|
12897
|
+
details: {
|
|
12898
|
+
session,
|
|
12899
|
+
activeNamespace: selectorNamespace,
|
|
12900
|
+
requestedNamespace: requestedName
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12664
12903
|
});
|
|
12665
12904
|
return;
|
|
12666
12905
|
}
|
|
@@ -12718,11 +12957,10 @@ async function handleRequest(request, socket) {
|
|
|
12718
12957
|
}
|
|
12719
12958
|
});
|
|
12720
12959
|
} catch (err) {
|
|
12721
|
-
sendResponse(
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
});
|
|
12960
|
+
sendResponse(
|
|
12961
|
+
socket,
|
|
12962
|
+
buildErrorResponse(id, err, "Failed to open browser session.")
|
|
12963
|
+
);
|
|
12726
12964
|
}
|
|
12727
12965
|
return;
|
|
12728
12966
|
}
|
|
@@ -12738,11 +12976,10 @@ async function handleRequest(request, socket) {
|
|
|
12738
12976
|
result: { sessionClosed: true }
|
|
12739
12977
|
});
|
|
12740
12978
|
} catch (err) {
|
|
12741
|
-
sendResponse(
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
});
|
|
12979
|
+
sendResponse(
|
|
12980
|
+
socket,
|
|
12981
|
+
buildErrorResponse(id, err, "Failed to close browser session.")
|
|
12982
|
+
);
|
|
12746
12983
|
}
|
|
12747
12984
|
beginShutdown();
|
|
12748
12985
|
return;
|
|
@@ -12755,7 +12992,14 @@ async function handleRequest(request, socket) {
|
|
|
12755
12992
|
sendResponse(socket, {
|
|
12756
12993
|
id,
|
|
12757
12994
|
ok: false,
|
|
12758
|
-
error: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions
|
|
12995
|
+
error: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions.`,
|
|
12996
|
+
errorInfo: {
|
|
12997
|
+
message: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions.`,
|
|
12998
|
+
code: "SESSION_NOT_OPEN",
|
|
12999
|
+
details: {
|
|
13000
|
+
session
|
|
13001
|
+
}
|
|
13002
|
+
}
|
|
12759
13003
|
});
|
|
12760
13004
|
return;
|
|
12761
13005
|
}
|
|
@@ -12764,7 +13008,14 @@ async function handleRequest(request, socket) {
|
|
|
12764
13008
|
sendResponse(socket, {
|
|
12765
13009
|
id,
|
|
12766
13010
|
ok: false,
|
|
12767
|
-
error: `Unknown command: ${command}
|
|
13011
|
+
error: `Unknown command: ${command}`,
|
|
13012
|
+
errorInfo: {
|
|
13013
|
+
message: `Unknown command: ${command}`,
|
|
13014
|
+
code: "UNKNOWN_COMMAND",
|
|
13015
|
+
details: {
|
|
13016
|
+
command
|
|
13017
|
+
}
|
|
13018
|
+
}
|
|
12768
13019
|
});
|
|
12769
13020
|
return;
|
|
12770
13021
|
}
|
|
@@ -12772,11 +13023,12 @@ async function handleRequest(request, socket) {
|
|
|
12772
13023
|
const result = await handler(instance, args);
|
|
12773
13024
|
sendResponse(socket, { id, ok: true, result });
|
|
12774
13025
|
} catch (err) {
|
|
12775
|
-
sendResponse(
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
13026
|
+
sendResponse(
|
|
13027
|
+
socket,
|
|
13028
|
+
buildErrorResponse(id, err, `Command "${command}" failed.`, void 0, {
|
|
13029
|
+
command
|
|
13030
|
+
})
|
|
13031
|
+
);
|
|
12780
13032
|
}
|
|
12781
13033
|
}
|
|
12782
13034
|
if ((0, import_fs4.existsSync)(socketPath)) {
|
|
@@ -12797,7 +13049,11 @@ var server = (0, import_net.createServer)((socket) => {
|
|
|
12797
13049
|
sendResponse(socket, {
|
|
12798
13050
|
id: 0,
|
|
12799
13051
|
ok: false,
|
|
12800
|
-
error: "Invalid JSON request"
|
|
13052
|
+
error: "Invalid JSON request",
|
|
13053
|
+
errorInfo: {
|
|
13054
|
+
message: "Invalid JSON request",
|
|
13055
|
+
code: "INVALID_JSON_REQUEST"
|
|
13056
|
+
}
|
|
12801
13057
|
});
|
|
12802
13058
|
}
|
|
12803
13059
|
}
|
|
@@ -12836,3 +13092,25 @@ async function shutdown() {
|
|
|
12836
13092
|
}
|
|
12837
13093
|
process.on("SIGTERM", shutdown);
|
|
12838
13094
|
process.on("SIGINT", shutdown);
|
|
13095
|
+
function buildErrorResponse(id, error, fallbackMessage, fallbackCode, details) {
|
|
13096
|
+
const normalized = normalizeError(error, fallbackMessage);
|
|
13097
|
+
let mergedDetails;
|
|
13098
|
+
if (normalized.details || details) {
|
|
13099
|
+
mergedDetails = {
|
|
13100
|
+
...normalized.details || {},
|
|
13101
|
+
...details || {}
|
|
13102
|
+
};
|
|
13103
|
+
}
|
|
13104
|
+
return {
|
|
13105
|
+
id,
|
|
13106
|
+
ok: false,
|
|
13107
|
+
error: normalized.message,
|
|
13108
|
+
errorInfo: {
|
|
13109
|
+
message: normalized.message,
|
|
13110
|
+
...normalized.code || fallbackCode ? { code: normalized.code || fallbackCode } : {},
|
|
13111
|
+
...normalized.name ? { name: normalized.name } : {},
|
|
13112
|
+
...mergedDetails ? { details: mergedDetails } : {},
|
|
13113
|
+
...normalized.cause ? { cause: normalized.cause } : {}
|
|
13114
|
+
}
|
|
13115
|
+
};
|
|
13116
|
+
}
|