opensteer 0.4.13 → 0.4.14

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