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.
@@ -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) {
@@ -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
- constructor(rootDir, namespace) {
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
  }
@@ -5092,7 +5372,7 @@ function defaultActionFailureMessage(action) {
5092
5372
  function classifyActionFailure(input) {
5093
5373
  const typed = classifyTypedError(input.error);
5094
5374
  if (typed) return typed;
5095
- const message = extractErrorMessage(input.error, input.fallbackMessage);
5375
+ const message = extractErrorMessage2(input.error, input.fallbackMessage);
5096
5376
  const fromCallLog = classifyFromPlaywrightMessage(message, input.probe);
5097
5377
  if (fromCallLog) return fromCallLog;
5098
5378
  const fromProbe = classifyFromProbe(input.probe);
@@ -5101,7 +5381,7 @@ function classifyActionFailure(input) {
5101
5381
  if (fromHeuristic) return fromHeuristic;
5102
5382
  return buildFailure({
5103
5383
  code: "UNKNOWN",
5104
- message: ensureMessage(input.fallbackMessage, "Action failed."),
5384
+ message: ensureMessage(message, input.fallbackMessage),
5105
5385
  classificationSource: "unknown"
5106
5386
  });
5107
5387
  }
@@ -5361,13 +5641,22 @@ function defaultRetryableForCode(code) {
5361
5641
  return true;
5362
5642
  }
5363
5643
  }
5364
- function extractErrorMessage(error, fallbackMessage) {
5644
+ function extractErrorMessage2(error, fallbackMessage) {
5365
5645
  if (error instanceof Error && error.message.trim()) {
5366
5646
  return error.message;
5367
5647
  }
5368
5648
  if (typeof error === "string" && error.trim()) {
5369
5649
  return error.trim();
5370
5650
  }
5651
+ if (error && typeof error === "object" && !Array.isArray(error)) {
5652
+ const record = error;
5653
+ if (typeof record.message === "string" && record.message.trim()) {
5654
+ return record.message.trim();
5655
+ }
5656
+ if (typeof record.error === "string" && record.error.trim()) {
5657
+ return record.error.trim();
5658
+ }
5659
+ }
5371
5660
  return ensureMessage(fallbackMessage, "Action failed.");
5372
5661
  }
5373
5662
  function ensureMessage(value, fallback) {
@@ -7677,7 +7966,8 @@ function withTokenQuery(wsUrl, token) {
7677
7966
  // src/cloud/local-cache-sync.ts
7678
7967
  var import_fs3 = __toESM(require("fs"), 1);
7679
7968
  var import_path5 = __toESM(require("path"), 1);
7680
- function collectLocalSelectorCacheEntries(storage) {
7969
+ function collectLocalSelectorCacheEntries(storage, options = {}) {
7970
+ const debug = options.debug === true;
7681
7971
  const namespace = storage.getNamespace();
7682
7972
  const namespaceDir = storage.getNamespaceDir();
7683
7973
  if (!import_fs3.default.existsSync(namespaceDir)) return [];
@@ -7686,7 +7976,7 @@ function collectLocalSelectorCacheEntries(storage) {
7686
7976
  for (const fileName of fileNames) {
7687
7977
  if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
7688
7978
  const filePath = import_path5.default.join(namespaceDir, fileName);
7689
- const selector = readSelectorFile(filePath);
7979
+ const selector = readSelectorFile(filePath, debug);
7690
7980
  if (!selector) continue;
7691
7981
  const descriptionHash = normalizeDescriptionHash(selector.id);
7692
7982
  const method = normalizeMethod(selector.method);
@@ -7711,11 +8001,20 @@ function collectLocalSelectorCacheEntries(storage) {
7711
8001
  }
7712
8002
  return dedupeNewest(entries);
7713
8003
  }
7714
- function readSelectorFile(filePath) {
8004
+ function readSelectorFile(filePath, debug) {
7715
8005
  try {
7716
8006
  const raw = import_fs3.default.readFileSync(filePath, "utf8");
7717
8007
  return JSON.parse(raw);
7718
- } catch {
8008
+ } catch (error) {
8009
+ const message = extractErrorMessage(
8010
+ error,
8011
+ "Unable to parse selector cache file JSON."
8012
+ );
8013
+ if (debug) {
8014
+ console.warn(
8015
+ `[opensteer] failed to read local selector cache file "${filePath}": ${message}`
8016
+ );
8017
+ }
7719
8018
  return null;
7720
8019
  }
7721
8020
  }
@@ -9923,7 +10222,9 @@ var Opensteer = class _Opensteer {
9923
10222
  this.aiExtract = this.createLazyExtractCallback(model);
9924
10223
  const rootDir = resolved.storage?.rootDir || process.cwd();
9925
10224
  this.namespace = resolveNamespace(resolved, rootDir);
9926
- this.storage = new LocalSelectorStorage(rootDir, this.namespace);
10225
+ this.storage = new LocalSelectorStorage(rootDir, this.namespace, {
10226
+ debug: Boolean(resolved.debug)
10227
+ });
9927
10228
  this.pool = new BrowserPool(resolved.browser || {});
9928
10229
  if (cloudSelection.cloud) {
9929
10230
  const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
@@ -9942,6 +10243,14 @@ var Opensteer = class _Opensteer {
9942
10243
  this.cloud = null;
9943
10244
  }
9944
10245
  }
10246
+ logDebugError(context, error) {
10247
+ if (!this.config.debug) return;
10248
+ const normalized = normalizeError(error, "Unknown error.");
10249
+ const codeSuffix = normalized.code && normalized.code.trim() ? ` [${normalized.code.trim()}]` : "";
10250
+ console.warn(
10251
+ `[opensteer] ${context}: ${normalized.message}${codeSuffix}`
10252
+ );
10253
+ }
9945
10254
  createLazyResolveCallback(model) {
9946
10255
  let resolverPromise = null;
9947
10256
  return async (...args) => {
@@ -10032,7 +10341,8 @@ var Opensteer = class _Opensteer {
10032
10341
  let tabs;
10033
10342
  try {
10034
10343
  tabs = await this.invokeCloudAction("tabs", {});
10035
- } catch {
10344
+ } catch (error) {
10345
+ this.logDebugError("cloud page reference sync (tabs lookup) failed", error);
10036
10346
  return;
10037
10347
  }
10038
10348
  if (!tabs.length) {
@@ -10170,12 +10480,7 @@ var Opensteer = class _Opensteer {
10170
10480
  try {
10171
10481
  await this.syncLocalSelectorCacheToCloud();
10172
10482
  } catch (error) {
10173
- if (this.config.debug) {
10174
- const message = error instanceof Error ? error.message : String(error);
10175
- console.warn(
10176
- `[opensteer] cloud selector cache sync failed: ${message}`
10177
- );
10178
- }
10483
+ this.logDebugError("cloud selector cache sync failed", error);
10179
10484
  }
10180
10485
  localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
10181
10486
  this.cloud.localRunId = localRunId;
@@ -10207,7 +10512,12 @@ var Opensteer = class _Opensteer {
10207
10512
  this.cloud.actionClient = actionClient;
10208
10513
  this.cloud.sessionId = sessionId;
10209
10514
  this.cloud.cloudSessionUrl = session3.cloudSessionUrl;
10210
- await this.syncCloudPageRef().catch(() => void 0);
10515
+ await this.syncCloudPageRef().catch((error) => {
10516
+ this.logDebugError(
10517
+ "cloud page reference sync after launch failed",
10518
+ error
10519
+ );
10520
+ });
10211
10521
  this.announceCloudSession({
10212
10522
  sessionId: session3.sessionId,
10213
10523
  workspaceId: session3.cloudSession.workspaceId,
@@ -10294,7 +10604,9 @@ var Opensteer = class _Opensteer {
10294
10604
  }
10295
10605
  async syncLocalSelectorCacheToCloud() {
10296
10606
  if (!this.cloud) return;
10297
- const entries = collectLocalSelectorCacheEntries(this.storage);
10607
+ const entries = collectLocalSelectorCacheEntries(this.storage, {
10608
+ debug: Boolean(this.config.debug)
10609
+ });
10298
10610
  if (!entries.length) return;
10299
10611
  await this.cloud.sessionClient.importSelectorCache({
10300
10612
  entries
@@ -10303,9 +10615,12 @@ var Opensteer = class _Opensteer {
10303
10615
  async goto(url, options) {
10304
10616
  if (this.cloud) {
10305
10617
  await this.invokeCloudActionAndResetCache("goto", { url, options });
10306
- await this.syncCloudPageRef({ expectedUrl: url }).catch(
10307
- () => void 0
10308
- );
10618
+ await this.syncCloudPageRef({ expectedUrl: url }).catch((error) => {
10619
+ this.logDebugError(
10620
+ "cloud page reference sync after goto failed",
10621
+ error
10622
+ );
10623
+ });
10309
10624
  return;
10310
10625
  }
10311
10626
  const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
@@ -10817,7 +11132,12 @@ var Opensteer = class _Opensteer {
10817
11132
  }
10818
11133
  );
10819
11134
  await this.syncCloudPageRef({ expectedUrl: result.url }).catch(
10820
- () => void 0
11135
+ (error) => {
11136
+ this.logDebugError(
11137
+ "cloud page reference sync after newTab failed",
11138
+ error
11139
+ );
11140
+ }
10821
11141
  );
10822
11142
  return result;
10823
11143
  }
@@ -10829,7 +11149,12 @@ var Opensteer = class _Opensteer {
10829
11149
  async switchTab(index) {
10830
11150
  if (this.cloud) {
10831
11151
  await this.invokeCloudActionAndResetCache("switchTab", { index });
10832
- await this.syncCloudPageRef().catch(() => void 0);
11152
+ await this.syncCloudPageRef().catch((error) => {
11153
+ this.logDebugError(
11154
+ "cloud page reference sync after switchTab failed",
11155
+ error
11156
+ );
11157
+ });
10833
11158
  return;
10834
11159
  }
10835
11160
  const page = await switchTab(this.context, index);
@@ -10839,7 +11164,12 @@ var Opensteer = class _Opensteer {
10839
11164
  async closeTab(index) {
10840
11165
  if (this.cloud) {
10841
11166
  await this.invokeCloudActionAndResetCache("closeTab", { index });
10842
- await this.syncCloudPageRef().catch(() => void 0);
11167
+ await this.syncCloudPageRef().catch((error) => {
11168
+ this.logDebugError(
11169
+ "cloud page reference sync after closeTab failed",
11170
+ error
11171
+ );
11172
+ });
10843
11173
  return;
10844
11174
  }
10845
11175
  const newPage = await closeTab(this.context, this.page, index);
@@ -11013,8 +11343,12 @@ var Opensteer = class _Opensteer {
11013
11343
  }
11014
11344
  return await counterFn(handle);
11015
11345
  } catch (err) {
11016
- const message = err instanceof Error ? err.message : `${method} failed.`;
11017
- throw new Error(message);
11346
+ if (err instanceof Error) {
11347
+ throw err;
11348
+ }
11349
+ throw new Error(
11350
+ `${method} failed. ${extractErrorMessage(err, "Unknown error.")}`
11351
+ );
11018
11352
  } finally {
11019
11353
  await handle.dispose();
11020
11354
  }
@@ -11141,6 +11475,9 @@ var Opensteer = class _Opensteer {
11141
11475
  if (this.cloud) {
11142
11476
  return await this.invokeCloudAction("extract", options);
11143
11477
  }
11478
+ if (options.schema !== void 0) {
11479
+ assertValidExtractSchemaRoot(options.schema);
11480
+ }
11144
11481
  const storageKey = this.resolveStorageKey(options.description);
11145
11482
  const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
11146
11483
  const stored = storageKey ? this.storage.readSelector(storageKey) : null;
@@ -11149,7 +11486,7 @@ var Opensteer = class _Opensteer {
11149
11486
  try {
11150
11487
  payload = normalizePersistedExtractPayload(stored.path);
11151
11488
  } catch (err) {
11152
- const message = err instanceof Error ? err.message : "Unknown error";
11489
+ const message = extractErrorMessage(err, "Unknown error.");
11153
11490
  const selectorFile = storageKey ? this.storage.getSelectorPath(storageKey) : "unknown selector file";
11154
11491
  throw new Error(
11155
11492
  `Cached extraction selector is invalid for the current schema at "${selectorFile}". Delete the cached selector and rerun extraction. ${message}`
@@ -11166,7 +11503,16 @@ var Opensteer = class _Opensteer {
11166
11503
  fields.push(...schemaFields);
11167
11504
  }
11168
11505
  if (!fields.length) {
11169
- const planResult = await this.parseAiExtractPlan(options);
11506
+ let planResult;
11507
+ try {
11508
+ planResult = await this.parseAiExtractPlan(options);
11509
+ } catch (error) {
11510
+ const message = extractErrorMessage(error, "Unknown error.");
11511
+ const contextMessage = options.schema ? "Schema extraction did not resolve deterministic field targets, so Opensteer attempted AI extraction planning." : "Opensteer attempted AI extraction planning.";
11512
+ throw new Error(`${contextMessage} ${message}`, {
11513
+ cause: error
11514
+ });
11515
+ }
11170
11516
  if (planResult.fields.length) {
11171
11517
  fields.push(...planResult.fields);
11172
11518
  } else if (planResult.data !== void 0) {
@@ -11828,12 +12174,6 @@ var Opensteer = class _Opensteer {
11828
12174
  };
11829
12175
  }
11830
12176
  async buildFieldTargetsFromSchema(schema) {
11831
- if (!schema || typeof schema !== "object") {
11832
- return [];
11833
- }
11834
- if (Array.isArray(schema)) {
11835
- return [];
11836
- }
11837
12177
  const fields = [];
11838
12178
  await this.collectFieldTargetsFromSchemaObject(
11839
12179
  schema,
@@ -11907,6 +12247,10 @@ var Opensteer = class _Opensteer {
11907
12247
  path: path5,
11908
12248
  attribute: normalized.attribute
11909
12249
  });
12250
+ } else {
12251
+ throw new Error(
12252
+ `Extraction schema field "${fieldKey}" uses selector "${normalized.selector}", but no matching element path could be built from the current page snapshot.`
12253
+ );
11910
12254
  }
11911
12255
  return;
11912
12256
  }
@@ -12257,13 +12601,28 @@ function countNonNullLeaves(value) {
12257
12601
  function isPrimitiveLike(value) {
12258
12602
  return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
12259
12603
  }
12604
+ function assertValidExtractSchemaRoot(schema) {
12605
+ if (!schema || typeof schema !== "object") {
12606
+ throw new Error(
12607
+ "Invalid extraction schema: expected a JSON object at the top level."
12608
+ );
12609
+ }
12610
+ if (Array.isArray(schema)) {
12611
+ throw new Error(
12612
+ 'Invalid extraction schema: top-level arrays are not supported. Wrap array fields in an object (for example {"items":[...]}).'
12613
+ );
12614
+ }
12615
+ }
12260
12616
  function parseAiExtractResponse(response) {
12261
12617
  if (typeof response === "string") {
12262
12618
  const trimmed = stripCodeFence2(response);
12263
12619
  try {
12264
12620
  return JSON.parse(trimmed);
12265
12621
  } catch {
12266
- throw new Error("LLM extraction returned a non-JSON string.");
12622
+ const preview = summarizeForError(trimmed);
12623
+ throw new Error(
12624
+ `LLM extraction returned a non-JSON response.${preview ? ` Preview: "${preview}"` : ""}`
12625
+ );
12267
12626
  }
12268
12627
  }
12269
12628
  if (response && typeof response === "object") {
@@ -12288,6 +12647,12 @@ function stripCodeFence2(input) {
12288
12647
  if (lastFence === -1) return withoutHeader.trim();
12289
12648
  return withoutHeader.slice(0, lastFence).trim();
12290
12649
  }
12650
+ function summarizeForError(input, maxLength = 180) {
12651
+ const compact = input.replace(/\s+/g, " ").trim();
12652
+ if (!compact) return "";
12653
+ if (compact.length <= maxLength) return compact;
12654
+ return `${compact.slice(0, maxLength)}...`;
12655
+ }
12291
12656
  function getScrollDelta2(options) {
12292
12657
  const amount = typeof options.amount === "number" ? options.amount : 600;
12293
12658
  const absoluteAmount = Math.abs(amount);
@@ -12627,7 +12992,16 @@ function enqueueRequest(request, socket) {
12627
12992
  void handleRequest(request, socket);
12628
12993
  return;
12629
12994
  }
12630
- requestQueue = requestQueue.then(() => handleRequest(request, socket)).catch(() => {
12995
+ requestQueue = requestQueue.then(() => handleRequest(request, socket)).catch((error) => {
12996
+ sendResponse(
12997
+ socket,
12998
+ buildErrorResponse(
12999
+ request.id,
13000
+ error,
13001
+ "Unexpected server error while handling request.",
13002
+ "CLI_INTERNAL_ERROR"
13003
+ )
13004
+ );
12631
13005
  });
12632
13006
  }
12633
13007
  async function handleRequest(request, socket) {
@@ -12636,7 +13010,11 @@ async function handleRequest(request, socket) {
12636
13010
  sendResponse(socket, {
12637
13011
  id,
12638
13012
  ok: false,
12639
- error: `Session '${session}' is shutting down.`
13013
+ error: `Session '${session}' is shutting down.`,
13014
+ errorInfo: {
13015
+ message: `Session '${session}' is shutting down.`,
13016
+ code: "SESSION_SHUTTING_DOWN"
13017
+ }
12640
13018
  });
12641
13019
  return;
12642
13020
  }
@@ -12644,7 +13022,11 @@ async function handleRequest(request, socket) {
12644
13022
  sendResponse(socket, {
12645
13023
  id,
12646
13024
  ok: false,
12647
- error: `Session '${session}' is shutting down. Retry your command.`
13025
+ error: `Session '${session}' is shutting down. Retry your command.`,
13026
+ errorInfo: {
13027
+ message: `Session '${session}' is shutting down. Retry your command.`,
13028
+ code: "SESSION_SHUTTING_DOWN"
13029
+ }
12648
13030
  });
12649
13031
  return;
12650
13032
  }
@@ -12660,7 +13042,16 @@ async function handleRequest(request, socket) {
12660
13042
  sendResponse(socket, {
12661
13043
  id,
12662
13044
  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.`
13045
+ 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.`,
13046
+ errorInfo: {
13047
+ 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.`,
13048
+ code: "SESSION_NAMESPACE_MISMATCH",
13049
+ details: {
13050
+ session,
13051
+ activeNamespace: selectorNamespace,
13052
+ requestedNamespace: requestedName
13053
+ }
13054
+ }
12664
13055
  });
12665
13056
  return;
12666
13057
  }
@@ -12718,11 +13109,10 @@ async function handleRequest(request, socket) {
12718
13109
  }
12719
13110
  });
12720
13111
  } catch (err) {
12721
- sendResponse(socket, {
12722
- id,
12723
- ok: false,
12724
- error: err instanceof Error ? err.message : String(err)
12725
- });
13112
+ sendResponse(
13113
+ socket,
13114
+ buildErrorResponse(id, err, "Failed to open browser session.")
13115
+ );
12726
13116
  }
12727
13117
  return;
12728
13118
  }
@@ -12738,11 +13128,10 @@ async function handleRequest(request, socket) {
12738
13128
  result: { sessionClosed: true }
12739
13129
  });
12740
13130
  } catch (err) {
12741
- sendResponse(socket, {
12742
- id,
12743
- ok: false,
12744
- error: err instanceof Error ? err.message : String(err)
12745
- });
13131
+ sendResponse(
13132
+ socket,
13133
+ buildErrorResponse(id, err, "Failed to close browser session.")
13134
+ );
12746
13135
  }
12747
13136
  beginShutdown();
12748
13137
  return;
@@ -12755,7 +13144,14 @@ async function handleRequest(request, socket) {
12755
13144
  sendResponse(socket, {
12756
13145
  id,
12757
13146
  ok: false,
12758
- error: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions.`
13147
+ error: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions.`,
13148
+ errorInfo: {
13149
+ message: `No browser session in session '${session}'. Call 'opensteer open --session ${session}' first, or use 'opensteer sessions' to list active sessions.`,
13150
+ code: "SESSION_NOT_OPEN",
13151
+ details: {
13152
+ session
13153
+ }
13154
+ }
12759
13155
  });
12760
13156
  return;
12761
13157
  }
@@ -12764,7 +13160,14 @@ async function handleRequest(request, socket) {
12764
13160
  sendResponse(socket, {
12765
13161
  id,
12766
13162
  ok: false,
12767
- error: `Unknown command: ${command}`
13163
+ error: `Unknown command: ${command}`,
13164
+ errorInfo: {
13165
+ message: `Unknown command: ${command}`,
13166
+ code: "UNKNOWN_COMMAND",
13167
+ details: {
13168
+ command
13169
+ }
13170
+ }
12768
13171
  });
12769
13172
  return;
12770
13173
  }
@@ -12772,11 +13175,12 @@ async function handleRequest(request, socket) {
12772
13175
  const result = await handler(instance, args);
12773
13176
  sendResponse(socket, { id, ok: true, result });
12774
13177
  } catch (err) {
12775
- sendResponse(socket, {
12776
- id,
12777
- ok: false,
12778
- error: err instanceof Error ? err.message : String(err)
12779
- });
13178
+ sendResponse(
13179
+ socket,
13180
+ buildErrorResponse(id, err, `Command "${command}" failed.`, void 0, {
13181
+ command
13182
+ })
13183
+ );
12780
13184
  }
12781
13185
  }
12782
13186
  if ((0, import_fs4.existsSync)(socketPath)) {
@@ -12797,7 +13201,11 @@ var server = (0, import_net.createServer)((socket) => {
12797
13201
  sendResponse(socket, {
12798
13202
  id: 0,
12799
13203
  ok: false,
12800
- error: "Invalid JSON request"
13204
+ error: "Invalid JSON request",
13205
+ errorInfo: {
13206
+ message: "Invalid JSON request",
13207
+ code: "INVALID_JSON_REQUEST"
13208
+ }
12801
13209
  });
12802
13210
  }
12803
13211
  }
@@ -12836,3 +13244,25 @@ async function shutdown() {
12836
13244
  }
12837
13245
  process.on("SIGTERM", shutdown);
12838
13246
  process.on("SIGINT", shutdown);
13247
+ function buildErrorResponse(id, error, fallbackMessage, fallbackCode, details) {
13248
+ const normalized = normalizeError(error, fallbackMessage);
13249
+ let mergedDetails;
13250
+ if (normalized.details || details) {
13251
+ mergedDetails = {
13252
+ ...normalized.details || {},
13253
+ ...details || {}
13254
+ };
13255
+ }
13256
+ return {
13257
+ id,
13258
+ ok: false,
13259
+ error: normalized.message,
13260
+ errorInfo: {
13261
+ message: normalized.message,
13262
+ ...normalized.code || fallbackCode ? { code: normalized.code || fallbackCode } : {},
13263
+ ...normalized.name ? { name: normalized.name } : {},
13264
+ ...mergedDetails ? { details: mergedDetails } : {},
13265
+ ...normalized.cause ? { cause: normalized.cause } : {}
13266
+ }
13267
+ };
13268
+ }