opensteer 0.9.5 → 0.9.6

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/cli/bin.cjs CHANGED
@@ -2598,10 +2598,8 @@ var init_cdp_dom_snapshot = __esm({
2598
2598
  });
2599
2599
 
2600
2600
  // ../browser-core/src/cdp-visual-stability.ts
2601
- var DEFAULT_VISUAL_STABILITY_SETTLE_MS;
2602
2601
  var init_cdp_visual_stability = __esm({
2603
2602
  "../browser-core/src/cdp-visual-stability.ts"() {
2604
- DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
2605
2603
  }
2606
2604
  });
2607
2605
 
@@ -10413,7 +10411,8 @@ function normalizeOpensteerEngineName(value, source = "engine") {
10413
10411
  if (normalized === "playwright" || normalized === "abp") {
10414
10412
  return normalized;
10415
10413
  }
10416
- throw new Error(
10414
+ throw new OpensteerProtocolError(
10415
+ "invalid-argument",
10417
10416
  `${source} must be one of ${OPENSTEER_ENGINE_NAMES.join(", ")}; received "${value}".`
10418
10417
  );
10419
10418
  }
@@ -10422,7 +10421,8 @@ function assertSupportedEngineOptions(input) {
10422
10421
  return;
10423
10422
  }
10424
10423
  if (typeof input.browser === "object" && input.browser !== null && input.browser.mode === "attach") {
10425
- throw new Error(
10424
+ throw new OpensteerProtocolError(
10425
+ "invalid-argument",
10426
10426
  'ABP engine does not support browser.mode="attach". Use the Playwright engine for attach flows.'
10427
10427
  );
10428
10428
  }
@@ -10430,7 +10430,8 @@ function assertSupportedEngineOptions(input) {
10430
10430
  if (unsupportedContextOptionNames.length === 0) {
10431
10431
  return;
10432
10432
  }
10433
- throw new Error(
10433
+ throw new OpensteerProtocolError(
10434
+ "invalid-argument",
10434
10435
  `ABP engine does not support ${unsupportedContextOptionNames.join(", ")}. Supported ABP context options: context.viewport.`
10435
10436
  );
10436
10437
  }
@@ -10488,6 +10489,7 @@ function stripWindowSizeArgs(args) {
10488
10489
  var OPENSTEER_ENGINE_NAMES, DEFAULT_OPENSTEER_ENGINE;
10489
10490
  var init_engine_selection = __esm({
10490
10491
  "../runtime-core/src/internal/engine-selection.ts"() {
10492
+ init_src2();
10491
10493
  OPENSTEER_ENGINE_NAMES = ["playwright", "abp"];
10492
10494
  DEFAULT_OPENSTEER_ENGINE = "playwright";
10493
10495
  }
@@ -11472,7 +11474,8 @@ function assertProviderSupportsEngine(provider, engine) {
11472
11474
  return;
11473
11475
  }
11474
11476
  if (provider === "cloud") {
11475
- throw new Error(
11477
+ throw new OpensteerProtocolError(
11478
+ "invalid-argument",
11476
11479
  "ABP is not supported for provider=cloud. Cloud provider currently requires Playwright."
11477
11480
  );
11478
11481
  }
@@ -11482,7 +11485,8 @@ function normalizeOpensteerProviderMode(value, source = "OPENSTEER_PROVIDER") {
11482
11485
  if (normalized === OPENSTEER_PROVIDER_MODES[0] || normalized === OPENSTEER_PROVIDER_MODES[1]) {
11483
11486
  return normalized;
11484
11487
  }
11485
- throw new Error(
11488
+ throw new OpensteerProtocolError(
11489
+ "invalid-argument",
11486
11490
  `${source} must be one of ${OPENSTEER_PROVIDER_MODES.join(", ")}; received "${value}".`
11487
11491
  );
11488
11492
  }
@@ -11507,6 +11511,7 @@ function resolveOpensteerProvider(input = {}) {
11507
11511
  var OPENSTEER_PROVIDER_MODES;
11508
11512
  var init_config = __esm({
11509
11513
  "src/provider/config.ts"() {
11514
+ init_src2();
11510
11515
  OPENSTEER_PROVIDER_MODES = ["local", "cloud"];
11511
11516
  }
11512
11517
  });
@@ -11968,12 +11973,60 @@ function wrapCloudFetchError(error, input) {
11968
11973
  wrapped.name = error.name;
11969
11974
  return wrapped;
11970
11975
  }
11971
- var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudClient;
11976
+ async function createCloudRequestError(response, input) {
11977
+ const payload = await readCloudErrorPayload(response);
11978
+ return new OpensteerCloudRequestError({
11979
+ statusCode: response.status,
11980
+ method: input.method,
11981
+ pathname: input.pathname,
11982
+ url: input.url,
11983
+ message: payload?.error ?? `${input.method} ${input.pathname} failed with ${String(response.status)}.`,
11984
+ ...payload?.code === void 0 ? {} : { code: payload.code },
11985
+ ...payload?.details === void 0 ? {} : { details: payload.details }
11986
+ });
11987
+ }
11988
+ async function readCloudErrorPayload(response) {
11989
+ try {
11990
+ return asCloudErrorPayload(await response.json());
11991
+ } catch {
11992
+ return void 0;
11993
+ }
11994
+ }
11995
+ function asCloudErrorPayload(value) {
11996
+ if (value === null || typeof value !== "object") {
11997
+ return void 0;
11998
+ }
11999
+ const payload = value;
12000
+ return {
12001
+ ...typeof payload.error === "string" ? { error: payload.error } : {},
12002
+ ...typeof payload.code === "string" ? { code: payload.code } : {},
12003
+ ..."details" in payload ? { details: payload.details } : {}
12004
+ };
12005
+ }
12006
+ var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudRequestError, OpensteerCloudClient;
11972
12007
  var init_client = __esm({
11973
12008
  "src/cloud/client.ts"() {
11974
12009
  init_profile_sync();
11975
12010
  CLOUD_CLOSE_TIMEOUT_MS = 6e4;
11976
12011
  CLOUD_CLOSE_POLL_INTERVAL_MS = 250;
12012
+ OpensteerCloudRequestError = class extends Error {
12013
+ statusCode;
12014
+ code;
12015
+ details;
12016
+ method;
12017
+ pathname;
12018
+ url;
12019
+ constructor(args) {
12020
+ super(args.message);
12021
+ this.name = "OpensteerCloudRequestError";
12022
+ this.statusCode = args.statusCode;
12023
+ this.code = args.code;
12024
+ this.details = args.details;
12025
+ this.method = args.method;
12026
+ this.pathname = args.pathname;
12027
+ this.url = args.url;
12028
+ }
12029
+ };
11977
12030
  OpensteerCloudClient = class {
11978
12031
  constructor(config) {
11979
12032
  this.config = config;
@@ -12149,7 +12202,11 @@ var init_client = __esm({
12149
12202
  });
12150
12203
  }
12151
12204
  if (!response.ok) {
12152
- throw new Error(`${init.method} ${pathname} failed with ${String(response.status)}.`);
12205
+ throw await createCloudRequestError(response, {
12206
+ method: init.method,
12207
+ pathname,
12208
+ url
12209
+ });
12153
12210
  }
12154
12211
  return response;
12155
12212
  }
@@ -12220,7 +12277,7 @@ var init_package = __esm({
12220
12277
  "../runtime-core/package.json"() {
12221
12278
  package_default2 = {
12222
12279
  name: "@opensteer/runtime-core",
12223
- version: "0.2.4",
12280
+ version: "0.2.5",
12224
12281
  description: "Shared semantic runtime for Opensteer local and cloud execution.",
12225
12282
  license: "MIT",
12226
12283
  type: "module",
@@ -12372,7 +12429,7 @@ var init_errors3 = __esm({
12372
12429
  function defaultPolicy() {
12373
12430
  return DEFAULT_POLICY;
12374
12431
  }
12375
- var DEFAULT_TIMEOUTS, DEFAULT_SETTLE_DELAYS, defaultSnapshotSettleObserver, DOM_ACTION_VISUAL_STABILITY_PROFILES, DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE, NAVIGATION_VISUAL_STABILITY_PROFILE, NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, defaultDomActionSettleObserver, defaultNavigationSettleObserver, DEFAULT_SETTLE_OBSERVERS, defaultTimeoutPolicy, defaultSettlePolicy, defaultRetryPolicy, defaultFallbackPolicy, DEFAULT_POLICY;
12432
+ var DEFAULT_TIMEOUTS, DEFAULT_SETTLE_DELAYS, DOM_ACTION_VISUAL_STABILITY_PROFILES, DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE, NAVIGATION_VISUAL_STABILITY_PROFILE, NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, defaultDomActionSettleObserver, defaultNavigationSettleObserver, DEFAULT_SETTLE_OBSERVERS, defaultTimeoutPolicy, defaultSettlePolicy, defaultRetryPolicy, defaultFallbackPolicy, DEFAULT_POLICY;
12376
12433
  var init_defaults = __esm({
12377
12434
  "../runtime-core/src/policy/defaults.ts"() {
12378
12435
  init_src();
@@ -12401,21 +12458,6 @@ var init_defaults = __esm({
12401
12458
  "dom-action": 100,
12402
12459
  snapshot: 0
12403
12460
  };
12404
- defaultSnapshotSettleObserver = {
12405
- async settle(input) {
12406
- if (input.trigger !== "snapshot") {
12407
- return false;
12408
- }
12409
- await input.engine.waitForVisualStability({
12410
- pageRef: input.pageRef,
12411
- ...input.remainingMs === void 0 ? {} : { timeoutMs: input.remainingMs },
12412
- settleMs: DEFAULT_VISUAL_STABILITY_SETTLE_MS,
12413
- scope: "visible-frames"
12414
- });
12415
- return true;
12416
- }
12417
- };
12418
- Object.freeze(defaultSnapshotSettleObserver);
12419
12461
  DOM_ACTION_VISUAL_STABILITY_PROFILES = {
12420
12462
  "dom.click": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
12421
12463
  "dom.input": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
@@ -12494,7 +12536,6 @@ var init_defaults = __esm({
12494
12536
  };
12495
12537
  Object.freeze(defaultNavigationSettleObserver);
12496
12538
  DEFAULT_SETTLE_OBSERVERS = Object.freeze([
12497
- defaultSnapshotSettleObserver,
12498
12539
  defaultDomActionSettleObserver,
12499
12540
  defaultNavigationSettleObserver
12500
12541
  ]);
@@ -12982,6 +13023,9 @@ function buildClauseSelector(node, clause) {
12982
13023
  if (!clause || typeof clause !== "object") {
12983
13024
  return "";
12984
13025
  }
13026
+ if (clause.kind === "text") {
13027
+ return "";
13028
+ }
12985
13029
  if (clause.kind === "position") {
12986
13030
  if (clause.axis === "nthOfType") {
12987
13031
  return `:nth-of-type(${Math.max(1, Number(node.position?.nthOfType || 1))})`;
@@ -13097,7 +13141,7 @@ function resolveExtractedValueInContext(normalizedValue, options) {
13097
13141
  function stripPositionClauses(nodes) {
13098
13142
  return (nodes || []).map((node) => ({
13099
13143
  ...node,
13100
- match: (node.match || []).filter((clause) => clause.kind !== "position")
13144
+ match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
13101
13145
  }));
13102
13146
  }
13103
13147
  function dedupeSelectors(selectors) {
@@ -13685,9 +13729,17 @@ function resolveDomPathInScope(index, domPath, scope) {
13685
13729
  if (!candidates.length) {
13686
13730
  return null;
13687
13731
  }
13732
+ const lastNode = domPath[domPath.length - 1];
13733
+ const textClauses = lastNode?.match.filter((c) => c.kind === "text") ?? [];
13688
13734
  let fallback = null;
13689
13735
  for (const selector of candidates) {
13690
- const matches = querySelectorAllInScope(index, selector, scope);
13736
+ let matches = querySelectorAllInScope(index, selector, scope);
13737
+ if (textClauses.length > 0 && matches.length > 1) {
13738
+ const filtered = matches.filter((node) => matchesTextClauses(node, textClauses));
13739
+ if (filtered.length > 0) {
13740
+ matches = filtered;
13741
+ }
13742
+ }
13691
13743
  if (matches.length === 1) {
13692
13744
  return {
13693
13745
  node: matches[0],
@@ -13707,6 +13759,10 @@ function resolveDomPathInScope(index, domPath, scope) {
13707
13759
  }
13708
13760
  return fallback;
13709
13761
  }
13762
+ function matchesTextClauses(node, clauses) {
13763
+ const text = (node.textContent ?? "").replace(/\s+/g, " ").trim();
13764
+ return clauses.every((clause) => text.includes(clause.value));
13765
+ }
13710
13766
  function queryAllDomPathInScope(index, domPath, scope) {
13711
13767
  const selectors = buildPathCandidates(domPath);
13712
13768
  for (const selector of selectors) {
@@ -13865,7 +13921,13 @@ function clonePathNode(node) {
13865
13921
  };
13866
13922
  }
13867
13923
  function cloneMatchClause(clause) {
13868
- return clause.kind === "position" ? { kind: "position", axis: clause.axis } : {
13924
+ if (clause.kind === "position") {
13925
+ return { kind: "position", axis: clause.axis };
13926
+ }
13927
+ if (clause.kind === "text") {
13928
+ return { kind: "text", value: clause.value };
13929
+ }
13930
+ return {
13869
13931
  kind: "attr",
13870
13932
  key: clause.key,
13871
13933
  ...clause.op === void 0 ? {} : { op: clause.op },
@@ -13920,6 +13982,13 @@ function normalizeMatch(rawMatch, attrs, position, tag) {
13920
13982
  op,
13921
13983
  ...value === void 0 ? {} : { value }
13922
13984
  });
13985
+ continue;
13986
+ }
13987
+ if (record.kind === "text") {
13988
+ const textValue = typeof record.value === "string" ? record.value.trim() : "";
13989
+ if (textValue) {
13990
+ push({ kind: "text", value: textValue.slice(0, 80) });
13991
+ }
13923
13992
  }
13924
13993
  }
13925
13994
  }
@@ -14334,7 +14403,7 @@ var init_executor = __esm({
14334
14403
  init_action_boundary2();
14335
14404
  init_policy();
14336
14405
  init_bridge();
14337
- MAX_DOM_ACTION_ATTEMPTS = 3;
14406
+ MAX_DOM_ACTION_ATTEMPTS = 2;
14338
14407
  DEFAULT_SCROLL_OPTIONS = {
14339
14408
  block: "center",
14340
14409
  inline: "center"
@@ -14821,11 +14890,12 @@ var init_executor = __esm({
14821
14890
  throw this.createActionabilityError(
14822
14891
  operation,
14823
14892
  "obscured",
14824
- `hit test resolved ${hit.nodeRef} outside the target subtree rooted at ${resolved.nodeRef}`,
14893
+ `target is obscured by ${assessment.blockingDescription ?? "another element"} at the click point`,
14825
14894
  {
14826
14895
  ...details,
14827
14896
  hitRelation: assessment.relation,
14828
14897
  ...assessment.ambiguous === void 0 ? {} : { hitAmbiguous: assessment.ambiguous },
14898
+ ...assessment.blockingDescription === void 0 ? {} : { blockingDescription: assessment.blockingDescription },
14829
14899
  ...assessment.canonicalTarget === void 0 ? {} : {
14830
14900
  canonicalNodeRef: assessment.canonicalTarget.nodeRef,
14831
14901
  canonicalDocumentRef: assessment.canonicalTarget.documentRef,
@@ -14839,8 +14909,7 @@ var init_executor = __esm({
14839
14909
  hitMissingFromSnapshot: !resolved.snapshot.nodes.some(
14840
14910
  (node) => node.nodeRef === hit.nodeRef
14841
14911
  )
14842
- },
14843
- true
14912
+ }
14844
14913
  );
14845
14914
  }
14846
14915
  async resolveActionablePointerTarget(session, operation, resolved) {
@@ -15031,7 +15100,12 @@ var init_runtime = __esm({
15031
15100
  });
15032
15101
  }
15033
15102
  async buildPath(input) {
15034
- return sanitizeReplayElementPath(await this.requireBridge().buildReplayPath(input.locator));
15103
+ return sanitizeReplayElementPath(
15104
+ await this.requireBridge().buildReplayPath(
15105
+ input.locator,
15106
+ input.enableTextMatch ? { enableTextMatch: true } : void 0
15107
+ )
15108
+ );
15035
15109
  }
15036
15110
  async resolveTarget(input) {
15037
15111
  return this.withSnapshotSession((session) => this.resolveTargetWithSession(session, input));
@@ -15251,7 +15325,7 @@ var init_runtime = __esm({
15251
15325
  if (resolvedByLocator) {
15252
15326
  const { snapshot, node } = resolvedByLocator;
15253
15327
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15254
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15328
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch: true });
15255
15329
  return this.createResolvedTarget("live", snapshot, node, anchor, {
15256
15330
  ...target.persist === void 0 ? {} : { persist: target.persist },
15257
15331
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15269,11 +15343,12 @@ var init_runtime = __esm({
15269
15343
  const { snapshot, node } = resolved;
15270
15344
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15271
15345
  const writeDescriptor = descriptorWriter ?? ((input) => this.descriptors.write(input));
15272
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15346
+ const enableTextMatch = method !== "extract";
15347
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch });
15273
15348
  const descriptor = target.persist === void 0 ? void 0 : await writeDescriptor({
15274
15349
  method,
15275
15350
  persist: target.persist,
15276
- path: replayPath ?? await this.buildPathForNode(snapshot, node),
15351
+ path: replayPath ?? await this.buildPathForNode(snapshot, node, { enableTextMatch }),
15277
15352
  sourceUrl: snapshot.url
15278
15353
  });
15279
15354
  return this.createResolvedTarget("selector", snapshot, node, anchor, {
@@ -15373,7 +15448,7 @@ var init_runtime = __esm({
15373
15448
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
15374
15449
  );
15375
15450
  }
15376
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node);
15451
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
15377
15452
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
15378
15453
  ...persist === void 0 ? {} : { persist },
15379
15454
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15544,19 +15619,20 @@ var init_runtime = __esm({
15544
15619
  }
15545
15620
  return this.bridge;
15546
15621
  }
15547
- async buildPathForNode(snapshot, node) {
15622
+ async buildPathForNode(snapshot, node, options) {
15548
15623
  if (node.nodeRef === void 0) {
15549
15624
  throw new Error(
15550
15625
  `snapshot node ${String(node.snapshotNodeId)} does not expose a live node reference`
15551
15626
  );
15552
15627
  }
15553
15628
  return this.buildPath({
15554
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef)
15629
+ locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
15630
+ ...options?.enableTextMatch ? { enableTextMatch: true } : {}
15555
15631
  });
15556
15632
  }
15557
- async tryBuildPathFromNode(snapshot, node) {
15633
+ async tryBuildPathFromNode(snapshot, node, options) {
15558
15634
  try {
15559
- return await this.buildPathForNode(snapshot, node);
15635
+ return await this.buildPathForNode(snapshot, node, options);
15560
15636
  } catch {
15561
15637
  return void 0;
15562
15638
  }
@@ -17126,6 +17202,9 @@ function relaxPathForSingleSample(path24, mode) {
17126
17202
  }
17127
17203
  return !isLast;
17128
17204
  }
17205
+ if (clause.kind === "text") {
17206
+ return false;
17207
+ }
17129
17208
  const key = String(clause.key || "").trim().toLowerCase();
17130
17209
  if (!key || !shouldKeepAttrForSingleSample(key)) {
17131
17210
  return false;
@@ -17228,9 +17307,11 @@ function buildNodeStructure(node) {
17228
17307
  }
17229
17308
  structuralAttrs[key] = value;
17230
17309
  }
17231
- const matchClauses = (node.match || []).map(
17232
- (clause) => clause.kind === "position" ? `position:${clause.axis}` : `attr:${String(clause.key || "").trim().toLowerCase()}`
17233
- ).sort();
17310
+ const matchClauses = (node.match || []).map((clause) => {
17311
+ if (clause.kind === "position") return `position:${clause.axis}`;
17312
+ if (clause.kind === "text") return `text:${clause.value}`;
17313
+ return `attr:${String(clause.key || "").trim().toLowerCase()}`;
17314
+ }).sort();
17234
17315
  return {
17235
17316
  tag,
17236
17317
  attrs: structuralAttrs,
@@ -17636,6 +17717,9 @@ function mergeMatchByMajority(matchLists, attrs, threshold, positionFlags = {
17636
17717
  });
17637
17718
  continue;
17638
17719
  }
17720
+ if (clause.kind === "text") {
17721
+ continue;
17722
+ }
17639
17723
  if (clause.axis === "nthOfType") {
17640
17724
  if (positionFlags.hasNthOfType) {
17641
17725
  merged.push({ kind: "position", axis: "nthOfType" });
@@ -29528,8 +29612,29 @@ function assertSupportedCloudBrowserMode(browser) {
29528
29612
  }
29529
29613
  }
29530
29614
  function isMissingCloudSessionError(error) {
29615
+ if (error instanceof OpensteerCloudRequestError) {
29616
+ return error.statusCode === 404 && (error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND");
29617
+ }
29531
29618
  return error instanceof Error && /\b404\b/.test(error.message);
29532
29619
  }
29620
+ function isRecoverableCloudSessionError(error) {
29621
+ if (!(error instanceof OpensteerCloudRequestError)) {
29622
+ return false;
29623
+ }
29624
+ if (error.statusCode === 404) {
29625
+ return error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND";
29626
+ }
29627
+ return error.statusCode === 409 && error.code === "CLOUD_SESSION_STALE";
29628
+ }
29629
+ function isBootstrapRecoveryOperation(operation) {
29630
+ return operation === "session.open" || operation === "instrumentation.route" || operation === "instrumentation.intercept-script";
29631
+ }
29632
+ function isReusableCloudSessionState(session) {
29633
+ if (session.status === "closing" || session.status === "closed" || session.status === "failed") {
29634
+ return false;
29635
+ }
29636
+ return !(typeof session.expiresAt === "number" && session.expiresAt <= Date.now() + CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS);
29637
+ }
29533
29638
  function isLoopbackBaseUrl(baseUrl) {
29534
29639
  let url;
29535
29640
  try {
@@ -29539,7 +29644,7 @@ function isLoopbackBaseUrl(baseUrl) {
29539
29644
  }
29540
29645
  return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]";
29541
29646
  }
29542
- var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CloudSessionProxy;
29647
+ var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS, CloudSessionProxy;
29543
29648
  var init_session_proxy = __esm({
29544
29649
  "src/cloud/session-proxy.ts"() {
29545
29650
  init_src3();
@@ -29549,8 +29654,10 @@ var init_session_proxy = __esm({
29549
29654
  init_policy2();
29550
29655
  init_semantic_rest_client();
29551
29656
  init_automation_client();
29657
+ init_client();
29552
29658
  init_workspace_sync();
29553
29659
  TEMPORARY_CLOUD_WORKSPACE_PREFIX = "opensteer-cloud-workspace-";
29660
+ CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS = 1e4;
29554
29661
  CloudSessionProxy = class {
29555
29662
  rootPath;
29556
29663
  workspace;
@@ -29564,6 +29671,8 @@ var init_session_proxy = __esm({
29564
29671
  automation;
29565
29672
  workspaceStore;
29566
29673
  syncWorkspaceOnClose = false;
29674
+ liveSessionStateEstablished = false;
29675
+ storedInstrumentation = [];
29567
29676
  constructor(cloud, options = {}) {
29568
29677
  this.cloud = cloud;
29569
29678
  this.workspace = options.workspace;
@@ -29593,15 +29702,12 @@ var init_session_proxy = __esm({
29593
29702
  if (this.client === void 0 && this.sessionId === void 0 && persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
29594
29703
  this.bindClient(persisted);
29595
29704
  }
29596
- if (this.automation) {
29597
- try {
29598
- const sessionInfo = await this.automation.getSessionInfo();
29599
- return {
29600
- ...sessionInfo,
29601
- ...this.workspace === void 0 ? {} : { workspace: this.workspace }
29602
- };
29603
- } catch {
29604
- }
29705
+ const sessionInfo = this.automation ? await this.automation.getSessionInfo().catch(() => void 0) : void 0;
29706
+ if (sessionInfo !== void 0) {
29707
+ return {
29708
+ ...sessionInfo,
29709
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace }
29710
+ };
29605
29711
  }
29606
29712
  return {
29607
29713
  provider: {
@@ -29713,12 +29819,26 @@ var init_session_proxy = __esm({
29713
29819
  return this.invokeSemanticOperation("session.cookies", input);
29714
29820
  }
29715
29821
  async route(input) {
29716
- await this.ensureSession();
29717
- return this.requireAutomation().route(input);
29822
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29823
+ "instrumentation.route",
29824
+ (automation) => automation.route(input)
29825
+ );
29826
+ this.storedInstrumentation.push({
29827
+ kind: "route",
29828
+ input
29829
+ });
29830
+ return registration;
29718
29831
  }
29719
29832
  async interceptScript(input) {
29720
- await this.ensureSession();
29721
- return this.requireAutomation().interceptScript(input);
29833
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29834
+ "instrumentation.intercept-script",
29835
+ (automation) => automation.interceptScript(input)
29836
+ );
29837
+ this.storedInstrumentation.push({
29838
+ kind: "intercept-script",
29839
+ input
29840
+ });
29841
+ return registration;
29722
29842
  }
29723
29843
  async getStorageSnapshot(input = {}) {
29724
29844
  return this.invokeSemanticOperation("session.storage", input);
@@ -29766,6 +29886,8 @@ var init_session_proxy = __esm({
29766
29886
  this.client = void 0;
29767
29887
  this.sessionId = void 0;
29768
29888
  this.semanticGrant = void 0;
29889
+ this.liveSessionStateEstablished = false;
29890
+ this.storedInstrumentation.length = 0;
29769
29891
  if (this.cleanupRootOnClose) {
29770
29892
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
29771
29893
  }
@@ -29793,6 +29915,8 @@ var init_session_proxy = __esm({
29793
29915
  this.automation = void 0;
29794
29916
  this.sessionId = void 0;
29795
29917
  this.semanticGrant = void 0;
29918
+ this.liveSessionStateEstablished = false;
29919
+ this.storedInstrumentation.length = 0;
29796
29920
  if (syncError !== void 0) {
29797
29921
  throw syncError;
29798
29922
  }
@@ -29840,6 +29964,7 @@ var init_session_proxy = __esm({
29840
29964
  };
29841
29965
  await this.writePersistedSession(record);
29842
29966
  this.bindClient(record, session.initialGrants?.semantic);
29967
+ await this.restoreStoredInstrumentation();
29843
29968
  }
29844
29969
  async syncWorkspaceToCloud() {
29845
29970
  if (this.workspace === void 0) {
@@ -29851,6 +29976,7 @@ var init_session_proxy = __esm({
29851
29976
  bindClient(record, initialSemanticGrant) {
29852
29977
  this.sessionId = record.sessionId;
29853
29978
  this.semanticGrant = initialSemanticGrant?.kind === "semantic" ? initialSemanticGrant : void 0;
29979
+ this.liveSessionStateEstablished = false;
29854
29980
  this.client = new OpensteerSemanticRestClient({
29855
29981
  getBaseUrl: async () => (await this.ensureSemanticGrant()).url,
29856
29982
  getAuthorizationHeader: async () => `Bearer ${(await this.ensureSemanticGrant()).token}`,
@@ -29858,6 +29984,19 @@ var init_session_proxy = __esm({
29858
29984
  });
29859
29985
  this.automation = new OpensteerCloudAutomationClient(this.cloud, record.sessionId);
29860
29986
  }
29987
+ async restoreStoredInstrumentation() {
29988
+ if (this.storedInstrumentation.length === 0) {
29989
+ return;
29990
+ }
29991
+ const automation = this.requireAutomation();
29992
+ for (const registration of this.storedInstrumentation) {
29993
+ if (registration.kind === "route") {
29994
+ await automation.route(registration.input);
29995
+ } else {
29996
+ await automation.interceptScript(registration.input);
29997
+ }
29998
+ }
29999
+ }
29861
30000
  async ensureWorkspaceStore() {
29862
30001
  if (this.workspaceStore !== void 0) {
29863
30002
  return this.workspaceStore;
@@ -29880,13 +30019,22 @@ var init_session_proxy = __esm({
29880
30019
  async clearPersistedSession() {
29881
30020
  await clearPersistedSessionRecord(this.rootPath, "cloud").catch(() => void 0);
29882
30021
  }
30022
+ async invalidateSession() {
30023
+ await this.automation?.close().catch(() => void 0);
30024
+ this.automation = void 0;
30025
+ this.client = void 0;
30026
+ this.sessionId = void 0;
30027
+ this.semanticGrant = void 0;
30028
+ this.liveSessionStateEstablished = false;
30029
+ await this.clearPersistedSession();
30030
+ }
29883
30031
  async isReusableCloudSession(sessionId, timeout) {
29884
30032
  try {
29885
30033
  const session = await this.cloud.getSession(sessionId, {
29886
30034
  signal: timeout?.signal,
29887
30035
  timeoutMs: timeout?.remainingMs()
29888
30036
  });
29889
- return session.status !== "closed" && session.status !== "failed";
30037
+ return isReusableCloudSessionState(session);
29890
30038
  } catch (error) {
29891
30039
  if (isMissingCloudSessionError(error)) {
29892
30040
  return false;
@@ -29935,26 +30083,79 @@ var init_session_proxy = __esm({
29935
30083
  try {
29936
30084
  await this.ensureSemanticGrant(true);
29937
30085
  return true;
29938
- } catch {
30086
+ } catch (refreshError) {
30087
+ if (await this.resetStaleSession(refreshError)) {
30088
+ throw refreshError;
30089
+ }
29939
30090
  return false;
29940
30091
  }
29941
30092
  }
29942
30093
  async invokeSemanticOperation(operation, input, sessionInit = {}) {
29943
- return this.runOperationWithPolicy(operation, async (timeout) => {
30094
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29944
30095
  await this.ensureSession(sessionInit, timeout);
29945
30096
  await this.ensureSemanticGrant(false, timeout);
29946
- return this.requireClient().invoke(operation, input, {
30097
+ const output = await this.requireClient().invoke(operation, input, {
29947
30098
  signal: timeout.signal,
29948
30099
  timeoutMs: timeout.remainingMs()
29949
30100
  });
30101
+ this.noteSuccessfulLiveOperation(operation);
30102
+ return output;
29950
30103
  });
29951
30104
  }
29952
30105
  async invokeAutomationOperation(operation, invoke, sessionInit = {}) {
29953
- return this.runOperationWithPolicy(operation, async (timeout) => {
30106
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29954
30107
  await this.ensureSession(sessionInit, timeout);
29955
- return invoke(this.requireAutomation());
30108
+ const output = await invoke(this.requireAutomation());
30109
+ this.noteSuccessfulLiveOperation(operation);
30110
+ return output;
29956
30111
  });
29957
30112
  }
30113
+ async invokeBootstrapInstrumentationOperation(operation, invoke) {
30114
+ let recovered = false;
30115
+ while (true) {
30116
+ try {
30117
+ await this.ensureSession();
30118
+ return await invoke(this.requireAutomation());
30119
+ } catch (error) {
30120
+ const stale = await this.resetStaleSession(error);
30121
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30122
+ throw error;
30123
+ }
30124
+ recovered = true;
30125
+ }
30126
+ }
30127
+ }
30128
+ async runOperationWithSessionRecovery(operation, invoke) {
30129
+ return this.runOperationWithPolicy(operation, async (timeout) => {
30130
+ let recovered = false;
30131
+ while (true) {
30132
+ try {
30133
+ return await invoke(timeout);
30134
+ } catch (error) {
30135
+ const stale = await this.resetStaleSession(error);
30136
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30137
+ throw error;
30138
+ }
30139
+ recovered = true;
30140
+ }
30141
+ }
30142
+ });
30143
+ }
30144
+ async resetStaleSession(error) {
30145
+ if (!isRecoverableCloudSessionError(error)) {
30146
+ return false;
30147
+ }
30148
+ await this.invalidateSession();
30149
+ return true;
30150
+ }
30151
+ canRecoverWithFreshSession(operation) {
30152
+ return !this.liveSessionStateEstablished && isBootstrapRecoveryOperation(operation);
30153
+ }
30154
+ noteSuccessfulLiveOperation(operation) {
30155
+ if (operation === "session.open" || operation === "page.list" || operation === "page.new" || operation === "page.activate" || operation === "page.close" || operation === "page.goto" || operation === "page.evaluate" || operation === "page.add-init-script" || operation === "page.snapshot" || operation === "dom.click" || operation === "dom.hover" || operation === "dom.input" || operation === "dom.scroll" || operation === "dom.extract" || operation === "network.query" || operation === "network.detail" || operation === "interaction.capture" || operation === "interaction.get" || operation === "interaction.diff" || operation === "interaction.replay" || operation === "scripts.capture" || operation === "artifact.read" || operation === "scripts.beautify" || operation === "scripts.deobfuscate" || operation === "scripts.sandbox" || operation === "captcha.solve" || operation === "session.cookies" || operation === "session.storage" || operation === "session.state" || operation === "session.fetch" || operation === "computer.execute") {
30156
+ this.liveSessionStateEstablished = true;
30157
+ }
30158
+ }
29958
30159
  async runOperationWithPolicy(operation, invoke) {
29959
30160
  return runWithPolicyTimeout(this.policy.timeout, { operation }, invoke);
29960
30161
  }
@@ -30269,14 +30470,85 @@ var init_session_control = __esm({
30269
30470
  }
30270
30471
  });
30271
30472
 
30473
+ // src/internal/errors.ts
30474
+ function normalizeThrownOpensteerError2(error, fallbackMessage) {
30475
+ if (isOpensteerProtocolError(error)) {
30476
+ return toOpensteerError(error);
30477
+ }
30478
+ if (isBrowserCoreError(error)) {
30479
+ return createOpensteerError(error.code, error.message, {
30480
+ retriable: error.retriable,
30481
+ ...error.details === void 0 ? {} : { details: error.details }
30482
+ });
30483
+ }
30484
+ if (error instanceof OpensteerAttachAmbiguousError) {
30485
+ return createOpensteerError("conflict", error.message, {
30486
+ details: {
30487
+ candidates: error.candidates,
30488
+ code: error.code,
30489
+ name: error.name
30490
+ }
30491
+ });
30492
+ }
30493
+ if (error instanceof Error && "opensteerError" in error && typeof error.opensteerError === "object" && error.opensteerError !== null) {
30494
+ const oe = error.opensteerError;
30495
+ return createOpensteerError(oe.code, oe.message, {
30496
+ retriable: oe.retriable,
30497
+ ...oe.capability === void 0 ? {} : { capability: oe.capability },
30498
+ ...oe.details === void 0 ? {} : { details: oe.details }
30499
+ });
30500
+ }
30501
+ if (error instanceof Error) {
30502
+ return createOpensteerError("operation-failed", error.message, {
30503
+ details: {
30504
+ name: error.name
30505
+ }
30506
+ });
30507
+ }
30508
+ return createOpensteerError("internal", fallbackMessage, {
30509
+ details: {
30510
+ value: error
30511
+ }
30512
+ });
30513
+ }
30514
+ var init_errors5 = __esm({
30515
+ "src/internal/errors.ts"() {
30516
+ init_src();
30517
+ init_src2();
30518
+ init_cdp_discovery();
30519
+ }
30520
+ });
30521
+
30272
30522
  // src/sdk/opensteer.ts
30273
30523
  var opensteer_exports = {};
30274
30524
  __export(opensteer_exports, {
30275
30525
  Opensteer: () => Opensteer
30276
30526
  });
30527
+ function createSdkProtocolError(error, fallbackMessage) {
30528
+ const normalized = normalizeThrownOpensteerError2(error, fallbackMessage);
30529
+ return new OpensteerProtocolError(normalized.code, normalized.message, {
30530
+ cause: error,
30531
+ retriable: normalized.retriable,
30532
+ ...normalized.capability === void 0 ? {} : { capability: normalized.capability },
30533
+ ...normalized.details === void 0 ? {} : { details: normalized.details }
30534
+ });
30535
+ }
30536
+ async function wrapSdkError(operation, fn) {
30537
+ try {
30538
+ return await fn();
30539
+ } catch (error) {
30540
+ if (isOpensteerProtocolError(error)) {
30541
+ throw error;
30542
+ }
30543
+ throw createSdkProtocolError(error, `${operation} failed`);
30544
+ }
30545
+ }
30277
30546
  function createUnsupportedBrowserController() {
30278
30547
  const fail = async () => {
30279
- throw new Error("browser.* helpers are only available in local mode.");
30548
+ throw new OpensteerProtocolError(
30549
+ "unsupported-operation",
30550
+ "browser.* helpers are only available in local mode."
30551
+ );
30280
30552
  };
30281
30553
  return {
30282
30554
  status: fail,
@@ -30289,7 +30561,10 @@ function normalizeTargetOptions(input) {
30289
30561
  const hasElement = input.element !== void 0;
30290
30562
  const hasSelector = input.selector !== void 0;
30291
30563
  if (hasElement && hasSelector) {
30292
- throw new Error("Specify exactly one of element, selector, or persist.");
30564
+ throw new OpensteerProtocolError(
30565
+ "invalid-argument",
30566
+ "Specify exactly one of element, selector, or persist."
30567
+ );
30293
30568
  }
30294
30569
  if (hasElement) {
30295
30570
  return {
@@ -30312,7 +30587,10 @@ function normalizeTargetOptions(input) {
30312
30587
  };
30313
30588
  }
30314
30589
  if (input.persist === void 0) {
30315
- throw new Error("Specify exactly one of element, selector, or persist.");
30590
+ throw new OpensteerProtocolError(
30591
+ "invalid-argument",
30592
+ "Specify exactly one of element, selector, or persist."
30593
+ );
30316
30594
  }
30317
30595
  return {
30318
30596
  target: {
@@ -30374,8 +30652,10 @@ function delay5(ms) {
30374
30652
  var SessionCookieJar, Opensteer;
30375
30653
  var init_opensteer = __esm({
30376
30654
  "src/sdk/opensteer.ts"() {
30655
+ init_src2();
30377
30656
  init_browser_manager();
30378
30657
  init_env();
30658
+ init_errors5();
30379
30659
  init_runtime_resolution();
30380
30660
  SessionCookieJar = class {
30381
30661
  domain;
@@ -30406,202 +30686,257 @@ var init_opensteer = __esm({
30406
30686
  dom;
30407
30687
  network;
30408
30688
  constructor(options = {}) {
30409
- const environment = resolveOpensteerEnvironment(options.rootDir);
30410
- const { provider, engineName, ...runtimeOptions } = options;
30411
- const runtimeConfig = resolveOpensteerRuntimeConfig({
30412
- ...provider === void 0 ? {} : { provider },
30413
- environment
30414
- });
30415
- if (runtimeConfig.provider.mode === "cloud") {
30416
- this.browserManager = void 0;
30417
- this.runtime = createOpensteerSemanticRuntime({
30418
- ...provider === void 0 ? {} : { provider },
30419
- ...engineName === void 0 ? {} : { engine: engineName },
30420
- environment,
30421
- runtimeOptions
30422
- });
30423
- this.browser = createUnsupportedBrowserController();
30424
- } else {
30425
- this.browserManager = new OpensteerBrowserManager({
30426
- ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
30427
- ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
30428
- ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
30429
- ...engineName === void 0 ? {} : { engineName },
30430
- environment,
30431
- ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
30432
- ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
30433
- ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
30434
- });
30435
- this.runtime = createOpensteerSemanticRuntime({
30689
+ try {
30690
+ const environment = resolveOpensteerEnvironment(options.rootDir);
30691
+ const { provider, engineName, ...runtimeOptions } = options;
30692
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
30436
30693
  ...provider === void 0 ? {} : { provider },
30437
- ...engineName === void 0 ? {} : { engine: engineName },
30438
- environment,
30439
- runtimeOptions: {
30440
- ...runtimeOptions,
30441
- rootPath: this.browserManager.rootPath,
30442
- cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
30443
- }
30694
+ environment
30444
30695
  });
30445
- this.browser = {
30446
- status: () => this.browserManager.status(),
30447
- clone: (input) => this.browserManager.clonePersistentBrowser(input),
30448
- reset: () => this.browserManager.reset(),
30449
- delete: () => this.browserManager.delete()
30696
+ if (runtimeConfig.provider.mode === "cloud") {
30697
+ this.browserManager = void 0;
30698
+ this.runtime = createOpensteerSemanticRuntime({
30699
+ ...provider === void 0 ? {} : { provider },
30700
+ ...engineName === void 0 ? {} : { engine: engineName },
30701
+ environment,
30702
+ runtimeOptions
30703
+ });
30704
+ this.browser = createUnsupportedBrowserController();
30705
+ } else {
30706
+ this.browserManager = new OpensteerBrowserManager({
30707
+ ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
30708
+ ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
30709
+ ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
30710
+ ...engineName === void 0 ? {} : { engineName },
30711
+ environment,
30712
+ ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
30713
+ ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
30714
+ ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
30715
+ });
30716
+ this.runtime = createOpensteerSemanticRuntime({
30717
+ ...provider === void 0 ? {} : { provider },
30718
+ ...engineName === void 0 ? {} : { engine: engineName },
30719
+ environment,
30720
+ runtimeOptions: {
30721
+ ...runtimeOptions,
30722
+ rootPath: this.browserManager.rootPath,
30723
+ cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
30724
+ }
30725
+ });
30726
+ this.browser = {
30727
+ status: () => wrapSdkError("browser.status", () => this.browserManager.status()),
30728
+ clone: (input) => wrapSdkError("browser.clone", () => this.browserManager.clonePersistentBrowser(input)),
30729
+ reset: () => wrapSdkError("browser.reset", () => this.browserManager.reset()),
30730
+ delete: () => wrapSdkError("browser.delete", () => this.browserManager.delete())
30731
+ };
30732
+ }
30733
+ this.dom = {
30734
+ click: (input) => this.click(input),
30735
+ hover: (input) => this.hover(input),
30736
+ input: (input) => this.input(input),
30737
+ scroll: (input) => this.scroll(input)
30738
+ };
30739
+ this.network = {
30740
+ query: (input = {}) => wrapSdkError("network.query", () => this.runtime.queryNetwork(input)),
30741
+ detail: (recordId, options2) => wrapSdkError(
30742
+ "network.detail",
30743
+ () => this.runtime.getNetworkDetail({ recordId, ...options2 })
30744
+ )
30450
30745
  };
30746
+ } catch (error) {
30747
+ if (isOpensteerProtocolError(error)) {
30748
+ throw error;
30749
+ }
30750
+ throw createSdkProtocolError(error, "Failed to initialize Opensteer");
30451
30751
  }
30452
- this.dom = {
30453
- click: (input) => this.click(input),
30454
- hover: (input) => this.hover(input),
30455
- input: (input) => this.input(input),
30456
- scroll: (input) => this.scroll(input)
30457
- };
30458
- this.network = {
30459
- query: (input = {}) => this.runtime.queryNetwork(input),
30460
- detail: (recordId, options2) => this.runtime.getNetworkDetail({ recordId, ...options2 })
30461
- };
30462
30752
  }
30463
30753
  async open(input = {}) {
30464
- return this.runtime.open(typeof input === "string" ? { url: input } : input);
30754
+ return wrapSdkError(
30755
+ "session.open",
30756
+ () => this.runtime.open(typeof input === "string" ? { url: input } : input)
30757
+ );
30465
30758
  }
30466
30759
  async info() {
30467
- return this.runtime.info();
30760
+ return wrapSdkError("session.info", () => this.runtime.info());
30468
30761
  }
30469
30762
  async listPages(input = {}) {
30470
- return this.runtime.listPages(input);
30763
+ return wrapSdkError("page.list", () => this.runtime.listPages(input));
30471
30764
  }
30472
30765
  async newPage(input = {}) {
30473
- return this.runtime.newPage(input);
30766
+ return wrapSdkError("page.new", () => this.runtime.newPage(input));
30474
30767
  }
30475
30768
  async activatePage(input) {
30476
- return this.runtime.activatePage(input);
30769
+ return wrapSdkError("page.activate", () => this.runtime.activatePage(input));
30477
30770
  }
30478
30771
  async closePage(input = {}) {
30479
- return this.runtime.closePage(input);
30772
+ return wrapSdkError("page.close", () => this.runtime.closePage(input));
30480
30773
  }
30481
30774
  async goto(url, options = {}) {
30482
- return this.runtime.goto({
30483
- url,
30484
- ...options
30485
- });
30775
+ return wrapSdkError(
30776
+ "page.goto",
30777
+ () => this.runtime.goto({
30778
+ url,
30779
+ ...options
30780
+ })
30781
+ );
30486
30782
  }
30487
30783
  async evaluate(input) {
30488
- const normalized = typeof input === "string" ? {
30489
- script: input
30490
- } : input;
30491
- return (await this.runtime.evaluate(normalized)).value;
30784
+ return wrapSdkError("page.evaluate", async () => {
30785
+ const normalized = typeof input === "string" ? {
30786
+ script: input
30787
+ } : input;
30788
+ return (await this.runtime.evaluate(normalized)).value;
30789
+ });
30492
30790
  }
30493
30791
  async addInitScript(input) {
30494
- return this.runtime.addInitScript(
30495
- typeof input === "string" ? {
30496
- script: input
30497
- } : input
30792
+ return wrapSdkError(
30793
+ "page.addInitScript",
30794
+ () => this.runtime.addInitScript(
30795
+ typeof input === "string" ? {
30796
+ script: input
30797
+ } : input
30798
+ )
30498
30799
  );
30499
30800
  }
30500
30801
  async click(input) {
30501
- const { button, clickCount, modifiers, ...target } = input;
30502
- return this.runtime.click({
30503
- ...normalizeTargetOptions(target),
30504
- ...button === void 0 ? {} : { button },
30505
- ...clickCount === void 0 ? {} : { clickCount },
30506
- ...modifiers === void 0 ? {} : { modifiers }
30802
+ return wrapSdkError("dom.click", () => {
30803
+ const { button, clickCount, modifiers, ...target } = input;
30804
+ return this.runtime.click({
30805
+ ...normalizeTargetOptions(target),
30806
+ ...button === void 0 ? {} : { button },
30807
+ ...clickCount === void 0 ? {} : { clickCount },
30808
+ ...modifiers === void 0 ? {} : { modifiers }
30809
+ });
30507
30810
  });
30508
30811
  }
30509
30812
  async hover(input) {
30510
- return this.runtime.hover(normalizeTargetOptions(input));
30813
+ return wrapSdkError("dom.hover", () => this.runtime.hover(normalizeTargetOptions(input)));
30511
30814
  }
30512
30815
  async input(input) {
30513
- return this.runtime.input({
30514
- ...normalizeTargetOptions(input),
30515
- text: input.text,
30516
- ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30517
- });
30816
+ return wrapSdkError(
30817
+ "dom.input",
30818
+ () => this.runtime.input({
30819
+ ...normalizeTargetOptions(input),
30820
+ text: input.text,
30821
+ ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30822
+ })
30823
+ );
30518
30824
  }
30519
30825
  async scroll(input) {
30520
- return this.runtime.scroll({
30521
- ...normalizeTargetOptions(input),
30522
- direction: input.direction,
30523
- amount: input.amount
30524
- });
30826
+ return wrapSdkError(
30827
+ "dom.scroll",
30828
+ () => this.runtime.scroll({
30829
+ ...normalizeTargetOptions(input),
30830
+ direction: input.direction,
30831
+ amount: input.amount
30832
+ })
30833
+ );
30525
30834
  }
30526
30835
  async extract(input) {
30527
- return (await this.runtime.extract(input)).data;
30836
+ return wrapSdkError("extract", async () => (await this.runtime.extract(input)).data);
30528
30837
  }
30529
30838
  async waitForPage(input = {}) {
30530
- const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
30531
- const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
30532
- const pollIntervalMs = input.pollIntervalMs ?? 100;
30533
- while (true) {
30534
- const match = (await this.runtime.listPages()).pages.find((page) => {
30535
- if (baseline.has(page.pageRef)) {
30536
- return false;
30537
- }
30538
- if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
30539
- return false;
30839
+ return wrapSdkError("page.waitForPage", async () => {
30840
+ const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
30841
+ const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
30842
+ const pollIntervalMs = input.pollIntervalMs ?? 100;
30843
+ while (true) {
30844
+ const match = (await this.runtime.listPages()).pages.find((page) => {
30845
+ if (baseline.has(page.pageRef)) {
30846
+ return false;
30847
+ }
30848
+ if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
30849
+ return false;
30850
+ }
30851
+ if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30852
+ return false;
30853
+ }
30854
+ return true;
30855
+ });
30856
+ if (match !== void 0) {
30857
+ return match;
30540
30858
  }
30541
- if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30542
- return false;
30859
+ if (Date.now() >= timeoutAt) {
30860
+ throw new OpensteerProtocolError("timeout", "waitForPage timed out");
30543
30861
  }
30544
- return true;
30545
- });
30546
- if (match !== void 0) {
30547
- return match;
30548
- }
30549
- if (Date.now() >= timeoutAt) {
30550
- throw new Error("waitForPage timed out");
30862
+ await delay5(pollIntervalMs);
30551
30863
  }
30552
- await delay5(pollIntervalMs);
30553
- }
30864
+ });
30554
30865
  }
30555
30866
  async cookies(domain) {
30556
- return new SessionCookieJar(
30557
- await this.runtime.getCookies(domain === void 0 ? {} : { domain })
30867
+ return wrapSdkError(
30868
+ "session.cookies",
30869
+ async () => new SessionCookieJar(await this.runtime.getCookies(domain === void 0 ? {} : { domain }))
30558
30870
  );
30559
30871
  }
30560
30872
  async storage(domain, type = "local") {
30561
- const snapshot = await this.runtime.getStorageSnapshot(domain === void 0 ? {} : { domain });
30562
- const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
30563
- if (domainSnapshot === void 0) {
30564
- return {};
30565
- }
30566
- const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
30567
- return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
30873
+ return wrapSdkError("session.storage", async () => {
30874
+ const snapshot = await this.runtime.getStorageSnapshot(
30875
+ domain === void 0 ? {} : { domain }
30876
+ );
30877
+ const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
30878
+ if (domainSnapshot === void 0) {
30879
+ return {};
30880
+ }
30881
+ const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
30882
+ return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
30883
+ });
30568
30884
  }
30569
30885
  async state(domain) {
30570
- return this.runtime.getBrowserState(domain === void 0 ? {} : { domain });
30886
+ return wrapSdkError(
30887
+ "session.state",
30888
+ () => this.runtime.getBrowserState(domain === void 0 ? {} : { domain })
30889
+ );
30571
30890
  }
30572
30891
  async fetch(url, options = {}) {
30573
- const input = buildFetchInput(url, options);
30574
- const result = await this.runtime.fetch(input);
30575
- if (result.response === void 0) {
30576
- throw new Error(result.note ?? `session.fetch did not produce a response for ${url}`);
30577
- }
30578
- return toResponse(result.response);
30892
+ return wrapSdkError("session.fetch", async () => {
30893
+ const input = buildFetchInput(url, options);
30894
+ const result = await this.runtime.fetch(input);
30895
+ if (result.response === void 0) {
30896
+ throw new OpensteerProtocolError(
30897
+ "operation-failed",
30898
+ result.note ?? `session.fetch did not produce a response for ${url}`
30899
+ );
30900
+ }
30901
+ return toResponse(result.response);
30902
+ });
30579
30903
  }
30580
30904
  async computerExecute(input) {
30581
- return this.runtime.computerExecute(input);
30905
+ return wrapSdkError("session.computerExecute", () => this.runtime.computerExecute(input));
30582
30906
  }
30583
30907
  async route(input) {
30584
- return this.requireOwnedInstrumentationRuntime("route").route(input);
30908
+ return wrapSdkError(
30909
+ "session.route",
30910
+ () => this.requireOwnedInstrumentationRuntime("route").route(input)
30911
+ );
30585
30912
  }
30586
30913
  async interceptScript(input) {
30587
- return this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input);
30914
+ return wrapSdkError(
30915
+ "session.interceptScript",
30916
+ () => this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input)
30917
+ );
30588
30918
  }
30589
30919
  async close() {
30590
- if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
30591
- return this.runtime.close();
30592
- }
30593
- const output = await this.runtime.close();
30594
- await this.browserManager.close();
30595
- return output;
30920
+ return wrapSdkError("session.close", async () => {
30921
+ if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
30922
+ return this.runtime.close();
30923
+ }
30924
+ const output = await this.runtime.close();
30925
+ await this.browserManager.close();
30926
+ return output;
30927
+ });
30596
30928
  }
30597
30929
  async disconnect() {
30598
- await this.runtime.disconnect();
30930
+ return wrapSdkError("session.disconnect", () => this.runtime.disconnect());
30599
30931
  }
30600
30932
  requireOwnedInstrumentationRuntime(method) {
30601
30933
  if (typeof this.runtime.route === "function" && typeof this.runtime.interceptScript === "function") {
30602
30934
  return this.runtime;
30603
30935
  }
30604
- throw new Error(`${method}() is not available for this session runtime.`);
30936
+ throw new OpensteerProtocolError(
30937
+ "unsupported-operation",
30938
+ `${method}() is not available for this session runtime.`
30939
+ );
30605
30940
  }
30606
30941
  };
30607
30942
  }
@@ -30609,7 +30944,7 @@ var init_opensteer = __esm({
30609
30944
 
30610
30945
  // package.json
30611
30946
  var package_default = {
30612
- version: "0.9.5"};
30947
+ version: "0.9.6"};
30613
30948
 
30614
30949
  // src/cli/bin.ts
30615
30950
  init_browser_manager();
@@ -30624,6 +30959,110 @@ async function loadCliEnvironment(cwd) {
30624
30959
  loadEnvironment(cwd);
30625
30960
  }
30626
30961
 
30962
+ // src/cli/errors.ts
30963
+ init_src();
30964
+ init_src2();
30965
+ var CliError = class extends Error {
30966
+ type;
30967
+ usage;
30968
+ constructor(type, message, usage) {
30969
+ super(message);
30970
+ this.name = "CliError";
30971
+ this.type = type;
30972
+ this.usage = usage;
30973
+ }
30974
+ format() {
30975
+ return this.usage === void 0 ? this.message : `${this.message}
30976
+ Usage: ${this.usage}`;
30977
+ }
30978
+ };
30979
+ function isCliError(value) {
30980
+ return value instanceof CliError;
30981
+ }
30982
+ function formatCliErrorOutput(error) {
30983
+ if (isCliError(error)) {
30984
+ return {
30985
+ success: false,
30986
+ error: error.format(),
30987
+ type: error.type
30988
+ };
30989
+ }
30990
+ if (isOpensteerProtocolError(error)) {
30991
+ return {
30992
+ success: false,
30993
+ error: error.message,
30994
+ code: error.code,
30995
+ retriable: error.retriable
30996
+ };
30997
+ }
30998
+ if (isBrowserCoreError(error)) {
30999
+ return {
31000
+ success: false,
31001
+ error: error.message,
31002
+ code: error.code,
31003
+ retriable: error.retriable
31004
+ };
31005
+ }
31006
+ if (error instanceof Error && "opensteerError" in error) {
31007
+ const oe = error.opensteerError;
31008
+ return {
31009
+ success: false,
31010
+ error: oe.message,
31011
+ code: oe.code,
31012
+ retriable: oe.retriable
31013
+ };
31014
+ }
31015
+ if (error instanceof SyntaxError) {
31016
+ return {
31017
+ success: false,
31018
+ error: error.message,
31019
+ type: "invalid_value"
31020
+ };
31021
+ }
31022
+ return {
31023
+ success: false,
31024
+ error: error instanceof Error ? error.message : String(error)
31025
+ };
31026
+ }
31027
+ function emitWarning(warning) {
31028
+ process.stderr.write(`${JSON.stringify({ warning })}
31029
+ `);
31030
+ }
31031
+ var CLI_USAGE_HINTS = {
31032
+ "session.open": "opensteer open <url> [--workspace <id>]",
31033
+ "page.goto": "opensteer goto <url>",
31034
+ "page.evaluate": "opensteer evaluate <script>",
31035
+ "page.add-init-script": "opensteer init-script <script>",
31036
+ "dom.click": "opensteer click <element> --persist <key>",
31037
+ "dom.hover": "opensteer hover <element> --persist <key>",
31038
+ "dom.input": "opensteer input <element> <text> --persist <key>",
31039
+ "dom.scroll": "opensteer scroll <direction> <amount> --persist <key>",
31040
+ "dom.extract": "opensteer extract <template> --persist <key>",
31041
+ "network.detail": "opensteer network detail <recordId>",
31042
+ "session.fetch": "opensteer fetch <url>",
31043
+ "captcha.solve": "opensteer captcha solve --provider <name> --api-key <key>",
31044
+ "scripts.capture": "opensteer scripts capture",
31045
+ "scripts.beautify": "opensteer scripts beautify <artifactId>",
31046
+ "scripts.deobfuscate": "opensteer scripts deobfuscate <artifactId>",
31047
+ "scripts.sandbox": "opensteer scripts sandbox <artifactId>",
31048
+ "interaction.capture": "opensteer interaction capture",
31049
+ "interaction.get": "opensteer interaction get <traceId>",
31050
+ "interaction.replay": "opensteer interaction replay <traceId>",
31051
+ "interaction.diff": "opensteer interaction diff <leftTraceId> <rightTraceId>",
31052
+ "artifact.read": "opensteer artifact read <artifactId>",
31053
+ "computer.click": "opensteer computer click <x> <y>",
31054
+ "computer.type": "opensteer computer type <text>",
31055
+ "computer.key": "opensteer computer key <key>",
31056
+ "computer.scroll": "opensteer computer scroll <x> <y> --dx <n> --dy <n>",
31057
+ "computer.move": "opensteer computer move <x> <y>",
31058
+ "computer.drag": "opensteer computer drag <x1> <y1> <x2> <y2>",
31059
+ "computer.screenshot": "opensteer computer screenshot",
31060
+ exec: "opensteer exec <expression>",
31061
+ record: "opensteer record --url <url> --workspace <id>",
31062
+ "browser.inspect": "opensteer browser inspect <endpoint>",
31063
+ "browser.clone": "opensteer browser clone --source-user-data-dir <path>"
31064
+ };
31065
+
30627
31066
  // src/cli/help.ts
30628
31067
  function getHelpText() {
30629
31068
  return `Opensteer CLI
@@ -30885,7 +31324,7 @@ function parseCommandLine(argv) {
30885
31324
  const key = token.slice(2, separator === -1 ? void 0 : separator);
30886
31325
  const spec = CLI_OPTION_SPECS[key];
30887
31326
  if (spec === void 0) {
30888
- throw new Error(`Unknown option: --${key}.`);
31327
+ throw new CliError("unknown_option", `Unknown option: --${key}.`);
30889
31328
  }
30890
31329
  if (separator !== -1) {
30891
31330
  rawOptions.set(key, [...rawOptions.get(key) ?? [], token.slice(separator + 1)]);
@@ -30914,7 +31353,8 @@ function parseCommandLine(argv) {
30914
31353
  continue;
30915
31354
  }
30916
31355
  if (next === void 0 || next.startsWith("--")) {
30917
- throw new Error(
31356
+ throw new CliError(
31357
+ "missing_arguments",
30918
31358
  `Option "--${key}" requires a value.${next?.startsWith("--") === true ? ` Use "--${key}=<value>" when the value begins with "--".` : ``}`
30919
31359
  );
30920
31360
  }
@@ -30960,10 +31400,14 @@ function parseCommandLine(argv) {
30960
31400
  const autoLocalView = readOptionalBoolean(rawOptions, "auto");
30961
31401
  const noAutoLocalView = readOptionalBoolean(rawOptions, "no-auto");
30962
31402
  if (autoLocalView === true && noAutoLocalView === true) {
30963
- throw new Error('Options "--auto" and "--no-auto" cannot be combined.');
31403
+ throw new CliError("invalid_option", 'Options "--auto" and "--no-auto" cannot be combined.');
30964
31404
  }
30965
31405
  if (command[0] !== "view" && (autoLocalView !== void 0 || noAutoLocalView !== void 0)) {
30966
- throw new Error('Options "--auto" and "--no-auto" are only supported with "view".');
31406
+ throw new CliError(
31407
+ "invalid_option",
31408
+ 'Options "--auto" and "--no-auto" are only supported with "view".',
31409
+ "opensteer view --auto"
31410
+ );
30967
31411
  }
30968
31412
  const global = readOptionalBoolean(rawOptions, "global");
30969
31413
  const yes = readOptionalBoolean(rawOptions, "yes");
@@ -31019,7 +31463,7 @@ function parseKeyValueList(values) {
31019
31463
  values.map((entry) => {
31020
31464
  const separator = entry.indexOf("=");
31021
31465
  if (separator <= 0) {
31022
- throw new Error(`Expected NAME=VALUE, received "${entry}".`);
31466
+ throw new CliError("invalid_value", `Expected NAME=VALUE, received "${entry}".`);
31023
31467
  }
31024
31468
  return [entry.slice(0, separator), entry.slice(separator + 1)];
31025
31469
  })
@@ -31050,7 +31494,7 @@ function readOptionalBoolean(options, name) {
31050
31494
  if (value === "false") {
31051
31495
  return false;
31052
31496
  }
31053
- throw new Error(`Option "--${name}" must be true or false.`);
31497
+ throw new CliError("invalid_value", `Option "--${name}" must be true or false.`);
31054
31498
  }
31055
31499
  function readOptionalNumber(options, name) {
31056
31500
  const value = readSingle(options, name);
@@ -31059,13 +31503,20 @@ function readOptionalNumber(options, name) {
31059
31503
  }
31060
31504
  const parsed = Number(value);
31061
31505
  if (!Number.isFinite(parsed)) {
31062
- throw new Error(`Option "--${name}" must be a number.`);
31506
+ throw new CliError("invalid_value", `Option "--${name}" must be a number.`);
31063
31507
  }
31064
31508
  return parsed;
31065
31509
  }
31066
31510
  function readJsonValue(options, name) {
31067
31511
  const value = readSingle(options, name);
31068
- return value === void 0 ? void 0 : JSON.parse(value);
31512
+ if (value === void 0) {
31513
+ return void 0;
31514
+ }
31515
+ try {
31516
+ return JSON.parse(value);
31517
+ } catch {
31518
+ throw new CliError("invalid_value", `Option "--${name}" contains invalid JSON.`);
31519
+ }
31069
31520
  }
31070
31521
  function readJsonObject(options, name) {
31071
31522
  const parsed = readJsonValue(options, name);
@@ -31073,7 +31524,7 @@ function readJsonObject(options, name) {
31073
31524
  return void 0;
31074
31525
  }
31075
31526
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31076
- throw new Error(`Option "--${name}" must be a JSON object.`);
31527
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON object.`);
31077
31528
  }
31078
31529
  return parsed;
31079
31530
  }
@@ -31083,7 +31534,7 @@ function readJsonArray(options, name) {
31083
31534
  return void 0;
31084
31535
  }
31085
31536
  if (!Array.isArray(parsed)) {
31086
- throw new Error(`Option "--${name}" must be a JSON array.`);
31537
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON array.`);
31087
31538
  }
31088
31539
  return parsed;
31089
31540
  }
@@ -31103,7 +31554,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31103
31554
  case "session.open": {
31104
31555
  const url = parsed.rest[0];
31105
31556
  if (url === void 0) {
31106
- throw new Error("open requires a URL.");
31557
+ throw new CliError("missing_arguments", "open requires a URL.", CLI_USAGE_HINTS[operation]);
31107
31558
  }
31108
31559
  return {
31109
31560
  url,
@@ -31136,7 +31587,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31136
31587
  }
31137
31588
  case "page.goto": {
31138
31589
  if (parsed.rest[0] === void 0) {
31139
- throw new Error("goto requires a URL.");
31590
+ throw new CliError("missing_arguments", "goto requires a URL.", CLI_USAGE_HINTS[operation]);
31140
31591
  }
31141
31592
  const captureNetwork = readSingle(parsed.rawOptions, "capture-network");
31142
31593
  return {
@@ -31148,14 +31599,14 @@ async function buildOperationInput(operation, parsed, runtime) {
31148
31599
  return parsed.rest[0] === void 0 ? {} : { mode: parsed.rest[0] };
31149
31600
  case "page.evaluate":
31150
31601
  if (parsed.rest[0] === void 0) {
31151
- throw new Error("evaluate requires a script.");
31602
+ throw new CliError("missing_arguments", "evaluate requires a script.", CLI_USAGE_HINTS[operation]);
31152
31603
  }
31153
31604
  return {
31154
31605
  script: joinRest(parsed.rest, 0)
31155
31606
  };
31156
31607
  case "page.add-init-script":
31157
31608
  if (parsed.rest[0] === void 0) {
31158
- throw new Error("init-script requires a script.");
31609
+ throw new CliError("missing_arguments", "init-script requires a script.", CLI_USAGE_HINTS[operation]);
31159
31610
  }
31160
31611
  return {
31161
31612
  script: joinRest(parsed.rest, 0)
@@ -31171,7 +31622,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31171
31622
  return buildElementTargetInput(parsed, "hover");
31172
31623
  case "dom.input": {
31173
31624
  if (parsed.rest[1] === void 0) {
31174
- throw new Error("input requires an element number and text.");
31625
+ throw new CliError("missing_arguments", "input requires an element number and text.", CLI_USAGE_HINTS[operation]);
31175
31626
  }
31176
31627
  const pressEnter = readOptionalBoolean(parsed.rawOptions, "press-enter");
31177
31628
  return {
@@ -31202,7 +31653,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31202
31653
  }
31203
31654
  case "dom.extract": {
31204
31655
  if (parsed.rest[0] === void 0) {
31205
- throw new Error("extract requires a template.");
31656
+ throw new CliError("missing_arguments", "extract requires a template.", CLI_USAGE_HINTS[operation]);
31206
31657
  }
31207
31658
  const persist = readPersistKey(parsed, "extract");
31208
31659
  return {
@@ -31238,7 +31689,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31238
31689
  }
31239
31690
  case "network.detail": {
31240
31691
  if (parsed.rest[0] === void 0) {
31241
- throw new Error("network detail requires a record id.");
31692
+ throw new CliError("missing_arguments", "network detail requires a record id.", CLI_USAGE_HINTS[operation]);
31242
31693
  }
31243
31694
  const probeFlag = readOptionalBoolean(parsed.rawOptions, "probe");
31244
31695
  return {
@@ -31249,7 +31700,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31249
31700
  case "session.fetch": {
31250
31701
  const url = parsed.rest[0];
31251
31702
  if (url === void 0) {
31252
- throw new Error("fetch requires a URL.");
31703
+ throw new CliError("missing_arguments", "fetch requires a URL.", CLI_USAGE_HINTS[operation]);
31253
31704
  }
31254
31705
  const bodyJson = readJsonValue(parsed.rawOptions, "body");
31255
31706
  const bodyText = readSingle(parsed.rawOptions, "body-text");
@@ -31257,7 +31708,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31257
31708
  const query = parseKeyValueList(parsed.rawOptions.get("query"));
31258
31709
  const headers = parseKeyValueList(parsed.rawOptions.get("header"));
31259
31710
  if (bodyJson !== void 0 && bodyText !== void 0) {
31260
- throw new Error('Use either "--body" or "--body-text", not both.');
31711
+ throw new CliError("invalid_option", 'Use either "--body" or "--body-text", not both.');
31261
31712
  }
31262
31713
  const transport = readSingle(parsed.rawOptions, "transport");
31263
31714
  const cookies = readOptionalBoolean(parsed.rawOptions, "cookies");
@@ -31288,7 +31739,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31288
31739
  const siteKey = readSingle(parsed.rawOptions, "site-key");
31289
31740
  const pageUrl = readSingle(parsed.rawOptions, "page-url");
31290
31741
  if (provider === void 0 || apiKey === void 0) {
31291
- throw new Error('captcha solve requires "--provider" and "--api-key".');
31742
+ throw new CliError("missing_arguments", 'captcha solve requires "--provider" and "--api-key".', CLI_USAGE_HINTS[operation]);
31292
31743
  }
31293
31744
  return {
31294
31745
  provider: readCaptchaProvider(provider),
@@ -31318,7 +31769,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31318
31769
  case "scripts.beautify":
31319
31770
  case "scripts.deobfuscate": {
31320
31771
  if (parsed.rest[0] === void 0) {
31321
- throw new Error(`${parsed.command.join(" ")} requires an artifact id.`);
31772
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires an artifact id.`, CLI_USAGE_HINTS[operation]);
31322
31773
  }
31323
31774
  const persist = readOptionalBoolean(parsed.rawOptions, "persist");
31324
31775
  return {
@@ -31328,7 +31779,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31328
31779
  }
31329
31780
  case "scripts.sandbox":
31330
31781
  if (parsed.rest[0] === void 0) {
31331
- throw new Error("scripts sandbox requires an artifact id.");
31782
+ throw new CliError("missing_arguments", "scripts sandbox requires an artifact id.", CLI_USAGE_HINTS[operation]);
31332
31783
  }
31333
31784
  {
31334
31785
  const fidelity = readSingle(parsed.rawOptions, "fidelity");
@@ -31377,14 +31828,14 @@ async function buildOperationInput(operation, parsed, runtime) {
31377
31828
  case "interaction.get":
31378
31829
  case "interaction.replay":
31379
31830
  if (parsed.rest[0] === void 0) {
31380
- throw new Error(`${parsed.command.join(" ")} requires a trace id.`);
31831
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires a trace id.`, CLI_USAGE_HINTS[operation]);
31381
31832
  }
31382
31833
  return {
31383
31834
  traceId: parsed.rest[0]
31384
31835
  };
31385
31836
  case "interaction.diff":
31386
31837
  if (parsed.rest[0] === void 0 || parsed.rest[1] === void 0) {
31387
- throw new Error("interaction diff requires two trace ids.");
31838
+ throw new CliError("missing_arguments", "interaction diff requires two trace ids.", CLI_USAGE_HINTS[operation]);
31388
31839
  }
31389
31840
  return {
31390
31841
  leftTraceId: parsed.rest[0],
@@ -31392,7 +31843,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31392
31843
  };
31393
31844
  case "artifact.read":
31394
31845
  if (parsed.rest[0] === void 0) {
31395
- throw new Error("artifact read requires an artifact id.");
31846
+ throw new CliError("missing_arguments", "artifact read requires an artifact id.", CLI_USAGE_HINTS[operation]);
31396
31847
  }
31397
31848
  return {
31398
31849
  artifactId: parsed.rest[0]
@@ -31400,7 +31851,8 @@ async function buildOperationInput(operation, parsed, runtime) {
31400
31851
  case "session.close":
31401
31852
  return {};
31402
31853
  default:
31403
- throw new Error(
31854
+ throw new CliError(
31855
+ "unsupported_operation",
31404
31856
  `${operation} does not have a direct CLI input shape. Use a supported command or the SDK.`
31405
31857
  );
31406
31858
  }
@@ -31447,7 +31899,7 @@ function buildComputerExecuteInput(parsed) {
31447
31899
  }
31448
31900
  case "type":
31449
31901
  if (parsed.rest[0] === void 0) {
31450
- throw new Error("computer type requires text.");
31902
+ throw new CliError("missing_arguments", "computer type requires text.");
31451
31903
  }
31452
31904
  return {
31453
31905
  action: {
@@ -31458,7 +31910,7 @@ function buildComputerExecuteInput(parsed) {
31458
31910
  };
31459
31911
  case "key": {
31460
31912
  if (parsed.rest[0] === void 0) {
31461
- throw new Error("computer key requires a key.");
31913
+ throw new CliError("missing_arguments", "computer key requires a key.");
31462
31914
  }
31463
31915
  const modifiers = readKeyModifiers(readSingle(parsed.rawOptions, "modifiers"));
31464
31916
  return {
@@ -31474,7 +31926,7 @@ function buildComputerExecuteInput(parsed) {
31474
31926
  const dx = readOptionalNumber(parsed.rawOptions, "dx");
31475
31927
  const dy = readOptionalNumber(parsed.rawOptions, "dy");
31476
31928
  if (dx === void 0 || dy === void 0) {
31477
- throw new Error('computer scroll requires "--dx" and "--dy".');
31929
+ throw new CliError("missing_arguments", 'computer scroll requires "--dx" and "--dy".');
31478
31930
  }
31479
31931
  return {
31480
31932
  action: {
@@ -31529,49 +31981,50 @@ function buildComputerExecuteInput(parsed) {
31529
31981
  }
31530
31982
  };
31531
31983
  default:
31532
- throw new Error(`Unknown computer command: ${parsed.command.join(" ")}`);
31984
+ throw new CliError("unknown_command", `Unknown computer command: ${parsed.command.join(" ")}`);
31533
31985
  }
31534
31986
  }
31535
31987
  async function resolvePageRefByIndex(runtime, index) {
31536
31988
  const pages = (await runtime.listPages({})).pages;
31537
31989
  const page = pages[index - 1];
31538
31990
  if (page === void 0) {
31539
- throw new Error(`tab ${String(index)} does not exist.`);
31991
+ throw new CliError("invalid_value", `tab ${String(index)} does not exist.`);
31540
31992
  }
31541
31993
  return page.pageRef;
31542
31994
  }
31543
31995
  function readRequiredPositiveInteger(value, message) {
31544
31996
  const parsed = readRequiredNumber(value, message);
31545
31997
  if (!Number.isInteger(parsed) || parsed < 1) {
31546
- throw new Error(message);
31998
+ throw new CliError("missing_arguments", message);
31547
31999
  }
31548
32000
  return parsed;
31549
32001
  }
31550
32002
  function readRequiredNumber(value, message) {
31551
32003
  if (value === void 0) {
31552
- throw new Error(message);
32004
+ throw new CliError("missing_arguments", message);
31553
32005
  }
31554
32006
  const parsed = Number(value);
31555
32007
  if (!Number.isFinite(parsed)) {
31556
- throw new Error(message);
32008
+ throw new CliError("missing_arguments", message);
31557
32009
  }
31558
32010
  return parsed;
31559
32011
  }
31560
32012
  function readSingleDirection(value) {
31561
32013
  if (value === void 0 || !SCROLL_DIRECTIONS.has(value)) {
31562
- throw new Error("scroll requires a direction: up, down, left, or right.");
32014
+ throw new CliError("missing_arguments", "scroll requires a direction: up, down, left, or right.");
31563
32015
  }
31564
32016
  return value;
31565
32017
  }
31566
32018
  function readClickButton(value) {
31567
32019
  if (value === void 0 || !CLICK_BUTTONS.has(value)) {
31568
- throw new Error('Expected "--button" to be one of: left, middle, right.');
32020
+ throw new CliError("invalid_value", 'Expected "--button" to be one of: left, middle, right.');
31569
32021
  }
31570
32022
  return value;
31571
32023
  }
31572
32024
  function readFetchTransport(value) {
31573
32025
  if (value === void 0 || !FETCH_TRANSPORTS.has(value)) {
31574
- throw new Error(
32026
+ throw new CliError(
32027
+ "invalid_value",
31575
32028
  'Expected "--transport" to be one of: auto, direct, matched-tls, context, page.'
31576
32029
  );
31577
32030
  }
@@ -31579,31 +32032,31 @@ function readFetchTransport(value) {
31579
32032
  }
31580
32033
  function readCaptchaProvider(value) {
31581
32034
  if (value === void 0 || !CAPTCHA_PROVIDERS.has(value)) {
31582
- throw new Error('Expected "--provider" to be one of: 2captcha, capsolver.');
32035
+ throw new CliError("invalid_value", 'Expected "--provider" to be one of: 2captcha, capsolver.');
31583
32036
  }
31584
32037
  return value;
31585
32038
  }
31586
32039
  function readCaptchaType(value) {
31587
32040
  if (value === void 0 || !CAPTCHA_TYPES.has(value)) {
31588
- throw new Error('Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
32041
+ throw new CliError("invalid_value", 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
31589
32042
  }
31590
32043
  return value;
31591
32044
  }
31592
32045
  function readSandboxFidelity(value) {
31593
32046
  if (value === void 0 || !SANDBOX_FIDELITIES.has(value)) {
31594
- throw new Error('Expected "--fidelity" to be one of: minimal, standard, full.');
32047
+ throw new CliError("invalid_value", 'Expected "--fidelity" to be one of: minimal, standard, full.');
31595
32048
  }
31596
32049
  return value;
31597
32050
  }
31598
32051
  function readSandboxClockMode(value) {
31599
32052
  if (value === void 0 || !SANDBOX_CLOCK_MODES.has(value)) {
31600
- throw new Error('Expected "--clock" to be one of: real, manual.');
32053
+ throw new CliError("invalid_value", 'Expected "--clock" to be one of: real, manual.');
31601
32054
  }
31602
32055
  return value;
31603
32056
  }
31604
32057
  function readScreenshotFormat(value) {
31605
32058
  if (value === void 0 || !SCREENSHOT_FORMATS.has(value)) {
31606
- throw new Error('Expected "--format" to be one of: png, jpeg, webp.');
32059
+ throw new CliError("invalid_value", 'Expected "--format" to be one of: png, jpeg, webp.');
31607
32060
  }
31608
32061
  return value;
31609
32062
  }
@@ -31614,7 +32067,7 @@ function readKeyModifiers(value) {
31614
32067
  }
31615
32068
  for (const modifier of modifiers) {
31616
32069
  if (!KEY_MODIFIERS.has(modifier)) {
31617
- throw new Error('Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
32070
+ throw new CliError("invalid_value", 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
31618
32071
  }
31619
32072
  }
31620
32073
  return [...new Set(modifiers)];
@@ -31622,17 +32075,22 @@ function readKeyModifiers(value) {
31622
32075
  function readPersistKey(parsed, verb) {
31623
32076
  const value = readSingle(parsed.rawOptions, "persist");
31624
32077
  if (value === void 0) {
31625
- throw new Error(`${verb} requires "--persist <key>".`);
32078
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31626
32079
  }
31627
32080
  if (value === "true" || value === "false") {
31628
- throw new Error(`${verb} requires "--persist <key>".`);
32081
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31629
32082
  }
31630
32083
  return value;
31631
32084
  }
31632
32085
  function parseRequiredJsonObjectArgument(value, label) {
31633
- const parsed = JSON.parse(value);
32086
+ let parsed;
32087
+ try {
32088
+ parsed = JSON.parse(value);
32089
+ } catch {
32090
+ throw new CliError("invalid_value", `${label} contains invalid JSON.`);
32091
+ }
31634
32092
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31635
- throw new Error(`${label} must be a JSON object.`);
32093
+ throw new CliError("invalid_value", `${label} must be a JSON object.`);
31636
32094
  }
31637
32095
  return parsed;
31638
32096
  }
@@ -34610,7 +35068,7 @@ async function handleViewCommand(parsed, options = {}) {
34610
35068
  return;
34611
35069
  }
34612
35070
  if (subcommand !== void 0) {
34613
- throw new Error(`Unknown view command: view ${subcommand}`);
35071
+ throw new CliError("unknown_command", `Unknown view command: view ${subcommand}`);
34614
35072
  }
34615
35073
  if (parsed.options.localViewMode !== void 0) {
34616
35074
  const preference = await setLocalViewMode(parsed.options.localViewMode);
@@ -34658,7 +35116,10 @@ async function resolveWorkspaceSessionId(input) {
34658
35116
  }
34659
35117
  function assertNoViewPreferenceFlag(parsed) {
34660
35118
  if (parsed.options.localViewMode !== void 0) {
34661
- throw new Error("View preference flags cannot be combined with this subcommand.");
35119
+ throw new CliError(
35120
+ "invalid_option",
35121
+ "View preference flags cannot be combined with this subcommand."
35122
+ );
34662
35123
  }
34663
35124
  }
34664
35125
  function writeViewOutput(parsed, value) {
@@ -34743,10 +35204,10 @@ async function main() {
34743
35204
  }
34744
35205
  const operation = resolveOperation(parsed.command);
34745
35206
  if (!operation) {
34746
- throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
35207
+ throw new CliError("unknown_command", `Unknown command: ${parsed.command.join(" ")}`);
34747
35208
  }
34748
35209
  if (parsed.options.workspace === void 0) {
34749
- throw new Error('Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
35210
+ throw new CliError("missing_workspace", 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
34750
35211
  }
34751
35212
  const { engineName, provider, runtimeProvider } = resolveCliRuntimeSelection(parsed);
34752
35213
  if (operation === "session.close") {
@@ -34768,24 +35229,39 @@ async function main() {
34768
35229
  let renderOperation = operation;
34769
35230
  try {
34770
35231
  const input = await buildOperationInput(operation, parsed, runtime);
34771
- result = await dispatchSemanticOperation(runtime, operation, input);
35232
+ const rawResult = await dispatchSemanticOperation(runtime, operation, input);
34772
35233
  if (parsed.command[0] === "tab" && operation !== "page.list") {
34773
35234
  renderOperation = "page.list";
34774
35235
  result = await runtime.listPages({});
35236
+ } else {
35237
+ result = rawResult;
35238
+ }
35239
+ let warning;
35240
+ if (result !== null && typeof result === "object" && "warning" in result) {
35241
+ const { warning: w, ...rest } = result;
35242
+ if (typeof w === "string") {
35243
+ warning = w;
35244
+ result = rest;
35245
+ }
34775
35246
  }
34776
35247
  process4__default.default.stdout.write(renderOperationOutput(renderOperation, result, input));
35248
+ if (warning !== void 0) {
35249
+ emitWarning(warning);
35250
+ }
34777
35251
  } finally {
34778
35252
  await runtime.disconnect().catch(() => void 0);
34779
35253
  }
34780
35254
  }
34781
35255
  async function handleExecCommand(parsed) {
34782
35256
  if (parsed.options.workspace === void 0) {
34783
- throw new Error('exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35257
+ throw new CliError("missing_workspace", 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34784
35258
  }
34785
35259
  const expression = parsed.rest.join(" ");
34786
35260
  if (!expression) {
34787
- throw new Error(
34788
- `exec requires an expression. Example: exec "await this.evaluate('document.title')"`
35261
+ throw new CliError(
35262
+ "missing_arguments",
35263
+ "exec requires an expression.",
35264
+ "opensteer exec <expression>"
34789
35265
  );
34790
35266
  }
34791
35267
  const { engineName, runtimeProvider } = resolveCliRuntimeSelection(parsed);
@@ -34821,8 +35297,10 @@ async function handleBrowserCommand(parsed) {
34821
35297
  if (subcommand === "inspect") {
34822
35298
  const endpoint = parsed.options.attachEndpoint ?? parsed.rest[0];
34823
35299
  if (!endpoint) {
34824
- throw new Error(
34825
- 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.'
35300
+ throw new CliError(
35301
+ "missing_arguments",
35302
+ 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.',
35303
+ "opensteer browser inspect <endpoint>"
34826
35304
  );
34827
35305
  }
34828
35306
  const result = await inspectCdpEndpoint({
@@ -34835,7 +35313,8 @@ async function handleBrowserCommand(parsed) {
34835
35313
  return;
34836
35314
  }
34837
35315
  if (parsed.options.workspace === void 0) {
34838
- throw new Error(
35316
+ throw new CliError(
35317
+ "missing_workspace",
34839
35318
  'Browser workspace commands require "--workspace <id>" or OPENSTEER_WORKSPACE.'
34840
35319
  );
34841
35320
  }
@@ -34858,7 +35337,11 @@ async function handleBrowserCommand(parsed) {
34858
35337
  case "clone": {
34859
35338
  const sourceUserDataDir = parsed.options.sourceUserDataDir;
34860
35339
  if (!sourceUserDataDir) {
34861
- throw new Error('browser clone requires "--source-user-data-dir <path>".');
35340
+ throw new CliError(
35341
+ "missing_arguments",
35342
+ 'browser clone requires "--source-user-data-dir <path>".',
35343
+ "opensteer browser clone --source-user-data-dir <path>"
35344
+ );
34862
35345
  }
34863
35346
  const result = await manager.clonePersistentBrowser({
34864
35347
  sourceUserDataDir,
@@ -34881,7 +35364,7 @@ async function handleBrowserCommand(parsed) {
34881
35364
  return;
34882
35365
  }
34883
35366
  default:
34884
- throw new Error(`Unknown browser command: ${parsed.command.join(" ")}`);
35367
+ throw new CliError("unknown_command", `Unknown browser command: ${parsed.command.join(" ")}`);
34885
35368
  }
34886
35369
  }
34887
35370
  async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvider) {
@@ -34914,23 +35397,27 @@ async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvi
34914
35397
  }
34915
35398
  async function handleRecordCommandEntry(parsed) {
34916
35399
  if (parsed.options.workspace === void 0) {
34917
- throw new Error('record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35400
+ throw new CliError("missing_workspace", 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34918
35401
  }
34919
35402
  const url = parsed.options.url ?? parsed.rest[0];
34920
35403
  if (url === void 0) {
34921
- throw new Error('record requires "--url <value>" or a positional URL.');
35404
+ throw new CliError(
35405
+ "missing_arguments",
35406
+ 'record requires "--url <value>" or a positional URL.',
35407
+ "opensteer record --url <url> --workspace <id>"
35408
+ );
34922
35409
  }
34923
35410
  const provider = resolveCliProvider(parsed);
34924
35411
  assertCloudCliOptionsMatchProvider(parsed, provider.mode);
34925
35412
  const engineName = resolveCliEngineName(parsed);
34926
35413
  if (engineName !== "playwright") {
34927
- throw new Error("record requires engine=playwright.");
35414
+ throw new CliError("config_conflict", "record requires engine=playwright.");
34928
35415
  }
34929
35416
  const rootDir = process4__default.default.cwd();
34930
35417
  const recordBrowser = parsed.options.browser;
34931
35418
  if (provider.mode === "cloud") {
34932
35419
  if (typeof recordBrowser === "object") {
34933
- throw new Error('record does not support browser.mode="attach".');
35420
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34934
35421
  }
34935
35422
  const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
34936
35423
  const runtimeConfig = resolveOpensteerRuntimeConfig({
@@ -34950,10 +35437,10 @@ async function handleRecordCommandEntry(parsed) {
34950
35437
  return;
34951
35438
  }
34952
35439
  if (parsed.options.launch?.headless === true) {
34953
- throw new Error('record requires a headed browser. Remove "--headless true".');
35440
+ throw new CliError("config_conflict", 'record requires a headed browser. Remove "--headless true".');
34954
35441
  }
34955
35442
  if (typeof recordBrowser === "object") {
34956
- throw new Error('record does not support browser.mode="attach".');
35443
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34957
35444
  }
34958
35445
  const launch = {
34959
35446
  ...parsed.options.launch ?? {},
@@ -35022,7 +35509,7 @@ function resolveCliBootstrapAction(argv) {
35022
35509
  }
35023
35510
  function buildCliBrowserProfile(parsed) {
35024
35511
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
35025
- throw new Error('"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
35512
+ throw new CliError("invalid_option", '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
35026
35513
  }
35027
35514
  return parsed.options.cloudProfileId === void 0 ? void 0 : {
35028
35515
  profileId: parsed.options.cloudProfileId,
@@ -35083,7 +35570,8 @@ function buildCliRuntimeProvider(parsed, providerMode) {
35083
35570
  }
35084
35571
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
35085
35572
  if (providerMode !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudAppBaseUrl !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
35086
- throw new Error(
35573
+ throw new CliError(
35574
+ "config_conflict",
35087
35575
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
35088
35576
  );
35089
35577
  }
@@ -35117,17 +35605,7 @@ function printVersion() {
35117
35605
  `);
35118
35606
  }
35119
35607
  main().catch((error) => {
35120
- const payload = error instanceof Error ? {
35121
- error: {
35122
- name: error.name,
35123
- message: error.message
35124
- }
35125
- } : {
35126
- error: {
35127
- name: "Error",
35128
- message: String(error)
35129
- }
35130
- };
35608
+ const payload = formatCliErrorOutput(error);
35131
35609
  process4__default.default.stderr.write(`${JSON.stringify(payload)}
35132
35610
  `);
35133
35611
  process4__default.default.exitCode = 1;