opensteer 0.9.5 → 0.9.7

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.
Files changed (35) hide show
  1. package/dist/{chunk-7LQL5YUR.js → chunk-3OHKIPBD.js} +571 -738
  2. package/dist/chunk-3OHKIPBD.js.map +1 -0
  3. package/dist/chunk-52UNH5UW.js +458 -0
  4. package/dist/chunk-52UNH5UW.js.map +1 -0
  5. package/dist/{chunk-GSCQQKZZ.js → chunk-PJXN7HED.js} +334 -18
  6. package/dist/chunk-PJXN7HED.js.map +1 -0
  7. package/dist/{chunk-ZRF7WMS3.js → chunk-R33BXCMQ.js} +16 -7
  8. package/dist/chunk-R33BXCMQ.js.map +1 -0
  9. package/dist/{chunk-T5P2QGZ3.js → chunk-U4BUCIZ4.js} +153 -12
  10. package/dist/chunk-U4BUCIZ4.js.map +1 -0
  11. package/dist/cli/bin.cjs +1421 -824
  12. package/dist/cli/bin.cjs.map +1 -1
  13. package/dist/cli/bin.js +286 -129
  14. package/dist/cli/bin.js.map +1 -1
  15. package/dist/index.cjs +1117 -703
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +58 -53
  18. package/dist/index.d.ts +58 -53
  19. package/dist/index.js +4 -4
  20. package/dist/local-view/public/assets/app.js +10 -1
  21. package/dist/local-view/serve-entry.cjs +6815 -1272
  22. package/dist/local-view/serve-entry.cjs.map +1 -1
  23. package/dist/local-view/serve-entry.js +2 -2
  24. package/dist/opensteer-CY2QUJEG.js +6 -0
  25. package/dist/{opensteer-T2JENADR.js.map → opensteer-CY2QUJEG.js.map} +1 -1
  26. package/dist/{session-control-M3JD7ZKA.js → session-control-FIP6ZJLH.js} +4 -4
  27. package/dist/{session-control-M3JD7ZKA.js.map → session-control-FIP6ZJLH.js.map} +1 -1
  28. package/package.json +7 -7
  29. package/dist/chunk-7D45QUZ3.js +0 -332
  30. package/dist/chunk-7D45QUZ3.js.map +0 -1
  31. package/dist/chunk-7LQL5YUR.js.map +0 -1
  32. package/dist/chunk-GSCQQKZZ.js.map +0 -1
  33. package/dist/chunk-T5P2QGZ3.js.map +0 -1
  34. package/dist/chunk-ZRF7WMS3.js.map +0 -1
  35. package/dist/opensteer-T2JENADR.js +0 -6
package/dist/cli/bin.cjs CHANGED
@@ -1662,31 +1662,131 @@ function isAlreadyExistsError(error) {
1662
1662
  }
1663
1663
  async function withFilesystemLock(lockPath, task) {
1664
1664
  await ensureDirectory(path10__default.default.dirname(lockPath));
1665
+ const ownerToken = crypto.randomUUID();
1665
1666
  let attempt = 0;
1666
1667
  while (true) {
1667
1668
  try {
1668
1669
  await promises.mkdir(lockPath);
1670
+ const acquiredAt = Date.now();
1671
+ await writeLockMetadata(lockPath, {
1672
+ version: LOCK_METADATA_VERSION,
1673
+ ownerToken,
1674
+ pid: process.pid,
1675
+ acquiredAt,
1676
+ heartbeatAt: acquiredAt
1677
+ });
1669
1678
  break;
1670
1679
  } catch (error) {
1671
1680
  if (!isAlreadyExistsError(error)) {
1672
1681
  throw error;
1673
1682
  }
1683
+ if (await tryRecoverFilesystemLock(lockPath)) {
1684
+ attempt = 0;
1685
+ continue;
1686
+ }
1674
1687
  const delayMs = LOCK_RETRY_DELAYS_MS[Math.min(attempt, LOCK_RETRY_DELAYS_MS.length - 1)];
1675
1688
  attempt += 1;
1676
1689
  await new Promise((resolve4) => setTimeout(resolve4, delayMs));
1677
1690
  }
1678
1691
  }
1692
+ const heartbeatTimer = setInterval(() => {
1693
+ void touchLockMetadata(lockPath, ownerToken);
1694
+ }, LOCK_HEARTBEAT_INTERVAL_MS);
1695
+ heartbeatTimer.unref?.();
1679
1696
  try {
1680
1697
  return await task();
1681
1698
  } finally {
1682
- await promises.rm(lockPath, { recursive: true, force: true });
1699
+ clearInterval(heartbeatTimer);
1700
+ const metadata = await readLockMetadata(lockPath);
1701
+ if (metadata?.ownerToken === ownerToken) {
1702
+ await promises.rm(lockPath, { recursive: true, force: true });
1703
+ }
1683
1704
  }
1684
1705
  }
1685
- var LOCK_RETRY_DELAYS_MS;
1706
+ async function tryRecoverFilesystemLock(lockPath) {
1707
+ if (!await shouldRecoverFilesystemLock(lockPath)) {
1708
+ return false;
1709
+ }
1710
+ await promises.rm(lockPath, { recursive: true, force: true });
1711
+ return true;
1712
+ }
1713
+ async function shouldRecoverFilesystemLock(lockPath) {
1714
+ const metadata = await readLockMetadata(lockPath);
1715
+ if (metadata !== void 0) {
1716
+ if (isProcessRunning2(metadata.pid)) {
1717
+ return false;
1718
+ }
1719
+ return Date.now() - metadata.heartbeatAt >= LOCK_ORPHAN_GRACE_MS;
1720
+ }
1721
+ const lockStat = await promises.stat(lockPath).catch(() => void 0);
1722
+ if (lockStat === void 0) {
1723
+ return false;
1724
+ }
1725
+ return Date.now() - lockStat.mtimeMs >= LOCK_METADATALESS_STALE_MS;
1726
+ }
1727
+ async function readLockMetadata(lockPath) {
1728
+ const metadataPath = path10__default.default.join(lockPath, LOCK_METADATA_FILENAME);
1729
+ if (!await pathExists(metadataPath)) {
1730
+ return void 0;
1731
+ }
1732
+ try {
1733
+ const parsed = await readJsonFile(metadataPath);
1734
+ const pid = parsed.pid;
1735
+ const acquiredAt = parsed.acquiredAt;
1736
+ const heartbeatAt = parsed.heartbeatAt;
1737
+ if (parsed.version !== LOCK_METADATA_VERSION || typeof parsed.ownerToken !== "string" || parsed.ownerToken.length === 0 || typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof acquiredAt !== "number" || !Number.isFinite(acquiredAt) || typeof heartbeatAt !== "number" || !Number.isFinite(heartbeatAt)) {
1738
+ return void 0;
1739
+ }
1740
+ return {
1741
+ version: LOCK_METADATA_VERSION,
1742
+ ownerToken: parsed.ownerToken,
1743
+ pid,
1744
+ acquiredAt,
1745
+ heartbeatAt
1746
+ };
1747
+ } catch {
1748
+ return void 0;
1749
+ }
1750
+ }
1751
+ async function writeLockMetadata(lockPath, metadata) {
1752
+ try {
1753
+ await writeJsonFileAtomic(path10__default.default.join(lockPath, LOCK_METADATA_FILENAME), metadata);
1754
+ } catch (error) {
1755
+ await promises.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
1756
+ throw error;
1757
+ }
1758
+ }
1759
+ async function touchLockMetadata(lockPath, ownerToken) {
1760
+ const metadata = await readLockMetadata(lockPath);
1761
+ if (metadata === void 0 || metadata.ownerToken !== ownerToken) {
1762
+ return;
1763
+ }
1764
+ await writeJsonFileAtomic(path10__default.default.join(lockPath, LOCK_METADATA_FILENAME), {
1765
+ ...metadata,
1766
+ heartbeatAt: Date.now()
1767
+ }).catch(() => void 0);
1768
+ }
1769
+ function isProcessRunning2(pid) {
1770
+ if (!Number.isInteger(pid) || pid <= 0) {
1771
+ return false;
1772
+ }
1773
+ try {
1774
+ process.kill(pid, 0);
1775
+ return true;
1776
+ } catch (error) {
1777
+ return error?.code === "EPERM";
1778
+ }
1779
+ }
1780
+ var LOCK_RETRY_DELAYS_MS, LOCK_METADATA_FILENAME, LOCK_METADATA_VERSION, LOCK_HEARTBEAT_INTERVAL_MS, LOCK_ORPHAN_GRACE_MS, LOCK_METADATALESS_STALE_MS;
1686
1781
  var init_filesystem = __esm({
1687
1782
  "../runtime-core/src/internal/filesystem.ts"() {
1688
1783
  init_json();
1689
1784
  LOCK_RETRY_DELAYS_MS = [1, 2, 5, 10, 20, 50];
1785
+ LOCK_METADATA_FILENAME = "owner.json";
1786
+ LOCK_METADATA_VERSION = 1;
1787
+ LOCK_HEARTBEAT_INTERVAL_MS = 1e3;
1788
+ LOCK_ORPHAN_GRACE_MS = 2e3;
1789
+ LOCK_METADATALESS_STALE_MS = 3e4;
1690
1790
  }
1691
1791
  });
1692
1792
  function normalizeScope(scope) {
@@ -2294,7 +2394,7 @@ var init_version = __esm({
2294
2394
  "../protocol/src/version.ts"() {
2295
2395
  init_json2();
2296
2396
  OPENSTEER_PROTOCOL_NAME = "opensteer";
2297
- OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 2;
2397
+ OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 3;
2298
2398
  OPENSTEER_PROTOCOL_VERSION = `0.${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}.0`;
2299
2399
  OPENSTEER_PROTOCOL_MEDIA_TYPE = `application/vnd.${OPENSTEER_PROTOCOL_NAME}+json;version=${OPENSTEER_PROTOCOL_VERSION}`;
2300
2400
  OPENSTEER_PROTOCOL_REST_BASE_PATH = `/api/v${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}`;
@@ -2598,10 +2698,8 @@ var init_cdp_dom_snapshot = __esm({
2598
2698
  });
2599
2699
 
2600
2700
  // ../browser-core/src/cdp-visual-stability.ts
2601
- var DEFAULT_VISUAL_STABILITY_SETTLE_MS;
2602
2701
  var init_cdp_visual_stability = __esm({
2603
2702
  "../browser-core/src/cdp-visual-stability.ts"() {
2604
- DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
2605
2703
  }
2606
2704
  });
2607
2705
 
@@ -6679,15 +6777,15 @@ function assertValidSemanticOperationInput(name, input) {
6679
6777
  }
6680
6778
  );
6681
6779
  }
6682
- var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerResolvedTargetSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerSessionStateSchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractTemplateSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerComputerTracePointSchema, opensteerComputerTraceEnrichmentSchema, opensteerComputerExecuteTimingSchema, opensteerComputerDisplayScaleSchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, opensteerSemanticOperationSpecificationMap, semanticRestBasePath, opensteerSemanticRestEndpoints;
6780
+ var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerNavigationSummarySchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageNewOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractTemplateSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerScreenshotSummarySchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, opensteerSemanticOperationSpecificationMap, semanticRestBasePath, opensteerSemanticRestEndpoints;
6683
6781
  var init_semantic = __esm({
6684
6782
  "../protocol/src/semantic.ts"() {
6685
6783
  init_json2();
6686
6784
  init_errors2();
6785
+ init_binary_location();
6687
6786
  init_identity2();
6688
6787
  init_geometry2();
6689
6788
  init_metadata2();
6690
- init_events2();
6691
6789
  init_envelopes();
6692
6790
  init_snapshots2();
6693
6791
  init_artifacts2();
@@ -6911,39 +7009,14 @@ var init_semantic = __esm({
6911
7009
  title: "OpensteerTargetInput"
6912
7010
  }
6913
7011
  );
6914
- opensteerResolvedTargetSchema = objectSchema(
6915
- {
6916
- pageRef: pageRefSchema,
6917
- frameRef: frameRefSchema,
6918
- documentRef: documentRefSchema,
6919
- documentEpoch: documentEpochSchema,
6920
- nodeRef: nodeRefSchema,
6921
- tagName: stringSchema(),
6922
- pathHint: stringSchema(),
6923
- persist: stringSchema(),
6924
- selectorUsed: stringSchema()
6925
- },
6926
- {
6927
- title: "OpensteerResolvedTarget",
6928
- required: [
6929
- "pageRef",
6930
- "frameRef",
6931
- "documentRef",
6932
- "documentEpoch",
6933
- "nodeRef",
6934
- "tagName",
6935
- "pathHint"
6936
- ]
6937
- }
6938
- );
6939
7012
  opensteerActionResultSchema = objectSchema(
6940
7013
  {
6941
- target: opensteerResolvedTargetSchema,
6942
- point: pointSchema
7014
+ tagName: stringSchema({ minLength: 1 }),
7015
+ persist: stringSchema({ minLength: 1 })
6943
7016
  },
6944
7017
  {
6945
7018
  title: "OpensteerActionResult",
6946
- required: ["target"]
7019
+ required: ["tagName"]
6947
7020
  }
6948
7021
  );
6949
7022
  opensteerSnapshotCounterSchema = objectSchema(
@@ -6989,16 +7062,14 @@ var init_semantic = __esm({
6989
7062
  ]
6990
7063
  }
6991
7064
  );
6992
- opensteerSessionStateSchema = objectSchema(
7065
+ opensteerNavigationSummarySchema = objectSchema(
6993
7066
  {
6994
- sessionRef: sessionRefSchema,
6995
- pageRef: pageRefSchema,
6996
7067
  url: stringSchema(),
6997
7068
  title: stringSchema()
6998
7069
  },
6999
7070
  {
7000
- title: "OpensteerSessionState",
7001
- required: ["sessionRef", "pageRef", "url", "title"]
7071
+ title: "OpensteerNavigationSummary",
7072
+ required: ["url", "title"]
7002
7073
  }
7003
7074
  );
7004
7075
  opensteerOpenInputSchema = objectSchema(
@@ -7066,6 +7137,17 @@ var init_semantic = __esm({
7066
7137
  required: ["closedPageRef", "pages"]
7067
7138
  }
7068
7139
  );
7140
+ opensteerPageNewOutputSchema = objectSchema(
7141
+ {
7142
+ pageRef: pageRefSchema,
7143
+ url: stringSchema(),
7144
+ title: stringSchema()
7145
+ },
7146
+ {
7147
+ title: "OpensteerPageNewOutput",
7148
+ required: ["pageRef", "url", "title"]
7149
+ }
7150
+ );
7069
7151
  opensteerPageGotoInputSchema = objectSchema(
7070
7152
  {
7071
7153
  url: stringSchema(),
@@ -7452,72 +7534,28 @@ var init_semantic = __esm({
7452
7534
  required: ["action"]
7453
7535
  }
7454
7536
  );
7455
- opensteerComputerTracePointSchema = objectSchema(
7456
- {
7457
- role: enumSchema(["point", "start", "end"]),
7458
- point: pointSchema,
7459
- hitTest: hitTestResultSchema,
7460
- target: opensteerResolvedTargetSchema
7461
- },
7462
- {
7463
- title: "OpensteerComputerTracePoint",
7464
- required: ["role", "point"]
7465
- }
7466
- );
7467
- opensteerComputerTraceEnrichmentSchema = objectSchema(
7537
+ opensteerScreenshotSummarySchema = objectSchema(
7468
7538
  {
7469
- points: arraySchema(opensteerComputerTracePointSchema)
7470
- },
7471
- {
7472
- title: "OpensteerComputerTraceEnrichment",
7473
- required: ["points"]
7474
- }
7475
- );
7476
- opensteerComputerExecuteTimingSchema = objectSchema(
7477
- {
7478
- actionMs: integerSchema({ minimum: 0 }),
7479
- waitMs: integerSchema({ minimum: 0 }),
7480
- totalMs: integerSchema({ minimum: 0 })
7481
- },
7482
- {
7483
- title: "OpensteerComputerExecuteTiming",
7484
- required: ["actionMs", "waitMs", "totalMs"]
7485
- }
7486
- );
7487
- opensteerComputerDisplayScaleSchema = objectSchema(
7488
- {
7489
- x: numberSchema({ exclusiveMinimum: 0 }),
7490
- y: numberSchema({ exclusiveMinimum: 0 })
7539
+ payload: externalBinaryLocationSchema,
7540
+ format: screenshotFormatSchema,
7541
+ size: sizeSchema,
7542
+ coordinateSpace: coordinateSpaceSchema,
7543
+ clip: rectSchema
7491
7544
  },
7492
7545
  {
7493
- title: "OpensteerComputerDisplayScale",
7494
- required: ["x", "y"]
7546
+ title: "OpensteerScreenshotSummary",
7547
+ required: ["payload", "format", "size", "coordinateSpace"]
7495
7548
  }
7496
7549
  );
7497
7550
  opensteerComputerExecuteOutputSchema = objectSchema(
7498
7551
  {
7499
- action: opensteerComputerActionSchema,
7500
- pageRef: pageRefSchema,
7501
- screenshot: screenshotArtifactSchema,
7502
- displayViewport: viewportMetricsSchema,
7503
- nativeViewport: viewportMetricsSchema,
7504
- displayScale: opensteerComputerDisplayScaleSchema,
7505
- events: arraySchema(opensteerEventSchema),
7506
- timing: opensteerComputerExecuteTimingSchema,
7507
- trace: opensteerComputerTraceEnrichmentSchema
7552
+ url: stringSchema(),
7553
+ title: stringSchema(),
7554
+ screenshot: opensteerScreenshotSummarySchema
7508
7555
  },
7509
7556
  {
7510
7557
  title: "OpensteerComputerExecuteOutput",
7511
- required: [
7512
- "action",
7513
- "pageRef",
7514
- "screenshot",
7515
- "displayViewport",
7516
- "nativeViewport",
7517
- "displayScale",
7518
- "events",
7519
- "timing"
7520
- ]
7558
+ required: ["url", "title", "screenshot"]
7521
7559
  }
7522
7560
  );
7523
7561
  opensteerSemanticOperationSpecificationsBase = [
@@ -7525,7 +7563,7 @@ var init_semantic = __esm({
7525
7563
  name: "session.open",
7526
7564
  description: "Open or resume the current Opensteer session and primary page.",
7527
7565
  inputSchema: opensteerOpenInputSchema,
7528
- outputSchema: opensteerSessionStateSchema,
7566
+ outputSchema: opensteerNavigationSummarySchema,
7529
7567
  requiredCapabilities: ["sessions.manage", "pages.manage"],
7530
7568
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["sessions.manage", "pages.manage"] : ["sessions.manage", "pages.manage", "pages.navigate"]
7531
7569
  }),
@@ -7540,7 +7578,7 @@ var init_semantic = __esm({
7540
7578
  name: "page.new",
7541
7579
  description: "Create and optionally navigate a new top-level page in the current session.",
7542
7580
  inputSchema: opensteerPageNewInputSchema,
7543
- outputSchema: opensteerSessionStateSchema,
7581
+ outputSchema: opensteerPageNewOutputSchema,
7544
7582
  requiredCapabilities: ["pages.manage"],
7545
7583
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["pages.manage"] : ["pages.manage", "pages.navigate"]
7546
7584
  }),
@@ -7548,7 +7586,7 @@ var init_semantic = __esm({
7548
7586
  name: "page.activate",
7549
7587
  description: "Activate an existing top-level page in the current session.",
7550
7588
  inputSchema: opensteerPageActivateInputSchema,
7551
- outputSchema: opensteerSessionStateSchema,
7589
+ outputSchema: opensteerNavigationSummarySchema,
7552
7590
  requiredCapabilities: ["pages.manage", "inspect.pages"]
7553
7591
  }),
7554
7592
  defineSemanticOperationSpec({
@@ -7562,7 +7600,7 @@ var init_semantic = __esm({
7562
7600
  name: "page.goto",
7563
7601
  description: "Navigate the current Opensteer page to a new URL.",
7564
7602
  inputSchema: opensteerPageGotoInputSchema,
7565
- outputSchema: opensteerSessionStateSchema,
7603
+ outputSchema: opensteerNavigationSummarySchema,
7566
7604
  requiredCapabilities: ["pages.navigate"]
7567
7605
  }),
7568
7606
  defineSemanticOperationSpec({
@@ -9804,16 +9842,37 @@ async function writePersistedSessionRecord(rootPath, record) {
9804
9842
  async function clearPersistedSessionRecord(rootPath, provider) {
9805
9843
  await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
9806
9844
  }
9845
+ function getPersistedLocalBrowserSessionOwnership(record) {
9846
+ return record.ownership === "attached" ? "attached" : "owned";
9847
+ }
9848
+ async function isAttachedLocalBrowserSessionReachable(record) {
9849
+ if (getPersistedLocalBrowserSessionOwnership(record) !== "attached") {
9850
+ return false;
9851
+ }
9852
+ if (record.engine !== "playwright" || record.endpoint === void 0) {
9853
+ return false;
9854
+ }
9855
+ try {
9856
+ await inspectCdpEndpoint({
9857
+ endpoint: record.endpoint,
9858
+ timeoutMs: 1500
9859
+ });
9860
+ return true;
9861
+ } catch {
9862
+ return false;
9863
+ }
9864
+ }
9807
9865
  function isPersistedCloudSessionRecord(value) {
9808
9866
  return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "cloud" && typeof value.sessionId === "string" && value.sessionId.length > 0 && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
9809
9867
  }
9810
9868
  function isPersistedLocalBrowserSessionRecord(value) {
9811
- return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "local" && (value.engine === "playwright" || value.engine === "abp") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && typeof value.userDataDir === "string" && value.userDataDir.length > 0;
9869
+ return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "local" && (value.engine === "playwright" || value.engine === "abp") && (value.ownership === void 0 || value.ownership === "owned" || value.ownership === "attached") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && typeof value.userDataDir === "string" && value.userDataDir.length > 0;
9812
9870
  }
9813
9871
  var OPENSTEER_LIVE_SESSION_LAYOUT, OPENSTEER_LIVE_SESSION_VERSION;
9814
9872
  var init_live_session = __esm({
9815
9873
  "src/live-session.ts"() {
9816
9874
  init_filesystem2();
9875
+ init_cdp_discovery();
9817
9876
  OPENSTEER_LIVE_SESSION_LAYOUT = "opensteer-session";
9818
9877
  OPENSTEER_LIVE_SESSION_VERSION = 1;
9819
9878
  }
@@ -10302,19 +10361,40 @@ var init_service = __esm({
10302
10361
  }
10303
10362
  });
10304
10363
  function buildLocalViewSessionId(input) {
10364
+ const ownership = input.ownership ?? "owned";
10365
+ const identity = ownership === "attached" ? input.endpoint ?? input.remoteDebuggingUrl ?? input.baseUrl ?? "attached" : `pid:${String(input.pid ?? 0)}`;
10305
10366
  const hash = crypto.createHash("sha256").update(`${input.rootPath}
10306
- ${String(input.pid)}
10367
+ ${ownership}
10368
+ ${identity}
10307
10369
  ${String(input.startedAt)}`).digest("hex");
10308
10370
  return `local_${hash.slice(0, 24)}`;
10309
10371
  }
10372
+ function buildLocalViewSessionIdForRecord(input) {
10373
+ const ownership = getPersistedLocalBrowserSessionOwnership(input.live);
10374
+ if (ownership === "attached") {
10375
+ return buildLocalViewSessionId({
10376
+ rootPath: input.rootPath,
10377
+ ownership,
10378
+ startedAt: input.live.startedAt,
10379
+ ...input.live.endpoint === void 0 ? {} : { endpoint: input.live.endpoint },
10380
+ ...input.live.baseUrl === void 0 ? {} : { baseUrl: input.live.baseUrl },
10381
+ ...input.live.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: input.live.remoteDebuggingUrl }
10382
+ });
10383
+ }
10384
+ return buildLocalViewSessionId({
10385
+ rootPath: input.rootPath,
10386
+ ownership,
10387
+ startedAt: input.live.startedAt,
10388
+ pid: input.live.pid
10389
+ });
10390
+ }
10310
10391
  function createLocalViewSessionManifest(input) {
10311
10392
  return {
10312
10393
  layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
10313
10394
  version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
10314
- sessionId: buildLocalViewSessionId({
10395
+ sessionId: buildLocalViewSessionIdForRecord({
10315
10396
  rootPath: input.rootPath,
10316
- pid: input.live.pid,
10317
- startedAt: input.live.startedAt
10397
+ live: input.live
10318
10398
  }),
10319
10399
  rootPath: input.rootPath,
10320
10400
  ...input.workspace === void 0 ? {} : { workspace: input.workspace },
@@ -10365,6 +10445,7 @@ var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT, OPENSTEER_LOCAL_VIEW_SESSION_VERSION;
10365
10445
  var init_session_manifest = __esm({
10366
10446
  "src/local-view/session-manifest.ts"() {
10367
10447
  init_filesystem2();
10448
+ init_live_session();
10368
10449
  init_runtime_dir();
10369
10450
  OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
10370
10451
  OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
@@ -10413,7 +10494,8 @@ function normalizeOpensteerEngineName(value, source = "engine") {
10413
10494
  if (normalized === "playwright" || normalized === "abp") {
10414
10495
  return normalized;
10415
10496
  }
10416
- throw new Error(
10497
+ throw new OpensteerProtocolError(
10498
+ "invalid-argument",
10417
10499
  `${source} must be one of ${OPENSTEER_ENGINE_NAMES.join(", ")}; received "${value}".`
10418
10500
  );
10419
10501
  }
@@ -10422,7 +10504,8 @@ function assertSupportedEngineOptions(input) {
10422
10504
  return;
10423
10505
  }
10424
10506
  if (typeof input.browser === "object" && input.browser !== null && input.browser.mode === "attach") {
10425
- throw new Error(
10507
+ throw new OpensteerProtocolError(
10508
+ "invalid-argument",
10426
10509
  'ABP engine does not support browser.mode="attach". Use the Playwright engine for attach flows.'
10427
10510
  );
10428
10511
  }
@@ -10430,7 +10513,8 @@ function assertSupportedEngineOptions(input) {
10430
10513
  if (unsupportedContextOptionNames.length === 0) {
10431
10514
  return;
10432
10515
  }
10433
- throw new Error(
10516
+ throw new OpensteerProtocolError(
10517
+ "invalid-argument",
10434
10518
  `ABP engine does not support ${unsupportedContextOptionNames.join(", ")}. Supported ABP context options: context.viewport.`
10435
10519
  );
10436
10520
  }
@@ -10488,6 +10572,7 @@ function stripWindowSizeArgs(args) {
10488
10572
  var OPENSTEER_ENGINE_NAMES, DEFAULT_OPENSTEER_ENGINE;
10489
10573
  var init_engine_selection = __esm({
10490
10574
  "../runtime-core/src/internal/engine-selection.ts"() {
10575
+ init_src2();
10491
10576
  OPENSTEER_ENGINE_NAMES = ["playwright", "abp"];
10492
10577
  DEFAULT_OPENSTEER_ENGINE = "playwright";
10493
10578
  }
@@ -10529,6 +10614,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
10529
10614
  version: 1,
10530
10615
  provider: "local",
10531
10616
  ...workspace === void 0 ? {} : { workspace },
10617
+ ownership: live.ownership,
10532
10618
  engine: live.engine,
10533
10619
  ...live.endpoint === void 0 ? {} : { endpoint: live.endpoint },
10534
10620
  ...live.baseUrl === void 0 ? {} : { baseUrl: live.baseUrl },
@@ -10544,6 +10630,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
10544
10630
  function toWorkspaceLiveBrowserRecord(record) {
10545
10631
  return {
10546
10632
  mode: "persistent",
10633
+ ownership: getPersistedLocalBrowserSessionOwnership(record),
10547
10634
  engine: record.engine,
10548
10635
  ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
10549
10636
  ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
@@ -10570,7 +10657,12 @@ function isAttachBrowserOptions(browser) {
10570
10657
  async function resolveAttachEndpoint(browser) {
10571
10658
  const endpoint = browser?.endpoint?.trim();
10572
10659
  if (endpoint && endpoint.length > 0) {
10573
- return endpoint;
10660
+ const inspected = await inspectCdpEndpoint({
10661
+ endpoint,
10662
+ ...browser?.headers === void 0 ? {} : { headers: browser.headers },
10663
+ timeoutMs: DEFAULT_TIMEOUT_MS
10664
+ });
10665
+ return inspected.endpoint;
10574
10666
  }
10575
10667
  const selection = await selectAttachBrowserCandidate({
10576
10668
  timeoutMs: DEFAULT_TIMEOUT_MS
@@ -10996,7 +11088,7 @@ var init_browser_manager = __esm({
10996
11088
  }
10997
11089
  const liveRecord = await this.readLivePersistentBrowser(await this.ensureWorkspaceStore());
10998
11090
  return {
10999
- mode: this.mode,
11091
+ mode: liveRecord?.ownership === "attached" ? "attach" : this.mode,
11000
11092
  engine: liveRecord?.engine ?? this.engineName,
11001
11093
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
11002
11094
  live: liveRecord !== void 0
@@ -11124,6 +11216,7 @@ var init_browser_manager = __esm({
11124
11216
  });
11125
11217
  const liveRecord = {
11126
11218
  mode: "persistent",
11219
+ ownership: "owned",
11127
11220
  engine: "abp",
11128
11221
  baseUrl: launched.baseUrl,
11129
11222
  remoteDebuggingUrl: launched.remoteDebuggingUrl,
@@ -11210,11 +11303,78 @@ var init_browser_manager = __esm({
11210
11303
  }
11211
11304
  async createAttachEngine() {
11212
11305
  const endpoint = await resolveAttachEndpoint(this.browserOptions);
11213
- return this.createAttachedEngine({
11214
- endpoint,
11215
- ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
11216
- freshTab: this.browserOptions?.freshTab ?? true,
11217
- onDispose: async () => void 0
11306
+ if (this.workspace === void 0) {
11307
+ return this.createAttachedEngine({
11308
+ endpoint,
11309
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
11310
+ freshTab: this.browserOptions?.freshTab ?? true,
11311
+ onDispose: async () => void 0
11312
+ });
11313
+ }
11314
+ const workspace = await this.ensureWorkspaceStore();
11315
+ return workspace.lock(async () => {
11316
+ const live = await this.readLivePersistentBrowser(workspace);
11317
+ if (live) {
11318
+ if (live.engine !== "playwright") {
11319
+ throw new Error(
11320
+ `workspace "${this.workspace}" already has a live ${live.engine} browser. Close it before attaching a Playwright browser.`
11321
+ );
11322
+ }
11323
+ if (live.ownership !== "attached") {
11324
+ throw new Error(
11325
+ `workspace "${this.workspace}" already has a live Opensteer-owned browser. Close it before attaching another browser.`
11326
+ );
11327
+ }
11328
+ if (live.endpoint === void 0) {
11329
+ throw new Error("workspace live browser record is missing a DevTools endpoint.");
11330
+ }
11331
+ if (live.endpoint !== endpoint) {
11332
+ throw new Error(
11333
+ `workspace "${this.workspace}" is already attached to a different browser endpoint. Close it before reattaching.`
11334
+ );
11335
+ }
11336
+ await bestEffortRegisterLocalViewSession({
11337
+ rootPath: workspace.rootPath,
11338
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
11339
+ live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
11340
+ ownership: "attached"
11341
+ });
11342
+ return this.createAttachedEngine({
11343
+ endpoint: live.endpoint,
11344
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
11345
+ freshTab: this.browserOptions?.freshTab ?? true,
11346
+ onDispose: async () => void 0
11347
+ });
11348
+ }
11349
+ const liveRecord = {
11350
+ mode: "persistent",
11351
+ ownership: "attached",
11352
+ engine: "playwright",
11353
+ endpoint,
11354
+ pid: 0,
11355
+ startedAt: Date.now(),
11356
+ userDataDir: workspace.browserUserDataDir
11357
+ };
11358
+ await this.writeLivePersistentBrowser(workspace, liveRecord);
11359
+ const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
11360
+ await bestEffortRegisterLocalViewSession({
11361
+ rootPath: workspace.rootPath,
11362
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
11363
+ live: persistedLiveRecord,
11364
+ ownership: "attached"
11365
+ });
11366
+ try {
11367
+ return await this.createAttachedEngine({
11368
+ endpoint,
11369
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
11370
+ freshTab: this.browserOptions?.freshTab ?? true,
11371
+ onDispose: async () => void 0
11372
+ });
11373
+ } catch (error) {
11374
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
11375
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
11376
+ throw error;
11377
+ }
11218
11378
  });
11219
11379
  }
11220
11380
  async createPersistentEngine() {
@@ -11234,7 +11394,7 @@ var init_browser_manager = __esm({
11234
11394
  rootPath: workspace.rootPath,
11235
11395
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
11236
11396
  live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
11237
- ownership: "owned"
11397
+ ownership: live.ownership
11238
11398
  });
11239
11399
  return this.createAttachedEngine({
11240
11400
  endpoint: live.endpoint,
@@ -11250,6 +11410,7 @@ var init_browser_manager = __esm({
11250
11410
  });
11251
11411
  const liveRecord = {
11252
11412
  mode: "persistent",
11413
+ ownership: "owned",
11253
11414
  engine: "playwright",
11254
11415
  endpoint: launched.endpoint,
11255
11416
  pid: launched.pid,
@@ -11379,7 +11540,20 @@ var init_browser_manager = __esm({
11379
11540
  if (live === void 0) {
11380
11541
  return void 0;
11381
11542
  }
11543
+ if (live.ownership === "attached") {
11544
+ const attachedRecord = toPersistedLocalBrowserSessionRecord(this.workspace, live);
11545
+ if (!await isAttachedLocalBrowserSessionReachable(attachedRecord)) {
11546
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, attachedRecord);
11547
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
11548
+ return void 0;
11549
+ }
11550
+ return live;
11551
+ }
11382
11552
  if (!isProcessRunning(live.pid)) {
11553
+ await this.unregisterLocalViewSessionForRecord(
11554
+ workspace.rootPath,
11555
+ toPersistedLocalBrowserSessionRecord(this.workspace, live)
11556
+ );
11383
11557
  await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
11384
11558
  return void 0;
11385
11559
  }
@@ -11427,6 +11601,10 @@ var init_browser_manager = __esm({
11427
11601
  workspace.rootPath,
11428
11602
  toPersistedLocalBrowserSessionRecord(this.workspace, live)
11429
11603
  );
11604
+ if (live.ownership === "attached") {
11605
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
11606
+ return;
11607
+ }
11430
11608
  if (live.engine === "playwright") {
11431
11609
  if (live.endpoint !== void 0) {
11432
11610
  await requestBrowserClose(live.endpoint).catch(() => void 0);
@@ -11455,10 +11633,18 @@ var init_browser_manager = __esm({
11455
11633
  }
11456
11634
  async unregisterLocalViewSessionForRecord(rootPath, record) {
11457
11635
  await bestEffortUnregisterLocalViewSession(
11458
- buildLocalViewSessionId({
11636
+ getPersistedLocalBrowserSessionOwnership(record) === "attached" ? buildLocalViewSessionId({
11459
11637
  rootPath,
11460
- pid: record.pid,
11461
- startedAt: record.startedAt
11638
+ startedAt: record.startedAt,
11639
+ ownership: "attached",
11640
+ ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
11641
+ ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
11642
+ ...record.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: record.remoteDebuggingUrl }
11643
+ }) : buildLocalViewSessionId({
11644
+ rootPath,
11645
+ startedAt: record.startedAt,
11646
+ ownership: "owned",
11647
+ pid: record.pid
11462
11648
  })
11463
11649
  );
11464
11650
  }
@@ -11472,7 +11658,8 @@ function assertProviderSupportsEngine(provider, engine) {
11472
11658
  return;
11473
11659
  }
11474
11660
  if (provider === "cloud") {
11475
- throw new Error(
11661
+ throw new OpensteerProtocolError(
11662
+ "invalid-argument",
11476
11663
  "ABP is not supported for provider=cloud. Cloud provider currently requires Playwright."
11477
11664
  );
11478
11665
  }
@@ -11482,7 +11669,8 @@ function normalizeOpensteerProviderMode(value, source = "OPENSTEER_PROVIDER") {
11482
11669
  if (normalized === OPENSTEER_PROVIDER_MODES[0] || normalized === OPENSTEER_PROVIDER_MODES[1]) {
11483
11670
  return normalized;
11484
11671
  }
11485
- throw new Error(
11672
+ throw new OpensteerProtocolError(
11673
+ "invalid-argument",
11486
11674
  `${source} must be one of ${OPENSTEER_PROVIDER_MODES.join(", ")}; received "${value}".`
11487
11675
  );
11488
11676
  }
@@ -11507,6 +11695,7 @@ function resolveOpensteerProvider(input = {}) {
11507
11695
  var OPENSTEER_PROVIDER_MODES;
11508
11696
  var init_config = __esm({
11509
11697
  "src/provider/config.ts"() {
11698
+ init_src2();
11510
11699
  OPENSTEER_PROVIDER_MODES = ["local", "cloud"];
11511
11700
  }
11512
11701
  });
@@ -11968,12 +12157,60 @@ function wrapCloudFetchError(error, input) {
11968
12157
  wrapped.name = error.name;
11969
12158
  return wrapped;
11970
12159
  }
11971
- var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudClient;
12160
+ async function createCloudRequestError(response, input) {
12161
+ const payload = await readCloudErrorPayload(response);
12162
+ return new OpensteerCloudRequestError({
12163
+ statusCode: response.status,
12164
+ method: input.method,
12165
+ pathname: input.pathname,
12166
+ url: input.url,
12167
+ message: payload?.error ?? `${input.method} ${input.pathname} failed with ${String(response.status)}.`,
12168
+ ...payload?.code === void 0 ? {} : { code: payload.code },
12169
+ ...payload?.details === void 0 ? {} : { details: payload.details }
12170
+ });
12171
+ }
12172
+ async function readCloudErrorPayload(response) {
12173
+ try {
12174
+ return asCloudErrorPayload(await response.json());
12175
+ } catch {
12176
+ return void 0;
12177
+ }
12178
+ }
12179
+ function asCloudErrorPayload(value) {
12180
+ if (value === null || typeof value !== "object") {
12181
+ return void 0;
12182
+ }
12183
+ const payload = value;
12184
+ return {
12185
+ ...typeof payload.error === "string" ? { error: payload.error } : {},
12186
+ ...typeof payload.code === "string" ? { code: payload.code } : {},
12187
+ ..."details" in payload ? { details: payload.details } : {}
12188
+ };
12189
+ }
12190
+ var CLOUD_CLOSE_TIMEOUT_MS, CLOUD_CLOSE_POLL_INTERVAL_MS, OpensteerCloudRequestError, OpensteerCloudClient;
11972
12191
  var init_client = __esm({
11973
12192
  "src/cloud/client.ts"() {
11974
12193
  init_profile_sync();
11975
12194
  CLOUD_CLOSE_TIMEOUT_MS = 6e4;
11976
12195
  CLOUD_CLOSE_POLL_INTERVAL_MS = 250;
12196
+ OpensteerCloudRequestError = class extends Error {
12197
+ statusCode;
12198
+ code;
12199
+ details;
12200
+ method;
12201
+ pathname;
12202
+ url;
12203
+ constructor(args) {
12204
+ super(args.message);
12205
+ this.name = "OpensteerCloudRequestError";
12206
+ this.statusCode = args.statusCode;
12207
+ this.code = args.code;
12208
+ this.details = args.details;
12209
+ this.method = args.method;
12210
+ this.pathname = args.pathname;
12211
+ this.url = args.url;
12212
+ }
12213
+ };
11977
12214
  OpensteerCloudClient = class {
11978
12215
  constructor(config) {
11979
12216
  this.config = config;
@@ -12149,7 +12386,11 @@ var init_client = __esm({
12149
12386
  });
12150
12387
  }
12151
12388
  if (!response.ok) {
12152
- throw new Error(`${init.method} ${pathname} failed with ${String(response.status)}.`);
12389
+ throw await createCloudRequestError(response, {
12390
+ method: init.method,
12391
+ pathname,
12392
+ url
12393
+ });
12153
12394
  }
12154
12395
  return response;
12155
12396
  }
@@ -12220,7 +12461,7 @@ var init_package = __esm({
12220
12461
  "../runtime-core/package.json"() {
12221
12462
  package_default2 = {
12222
12463
  name: "@opensteer/runtime-core",
12223
- version: "0.2.4",
12464
+ version: "0.2.6",
12224
12465
  description: "Shared semantic runtime for Opensteer local and cloud execution.",
12225
12466
  license: "MIT",
12226
12467
  type: "module",
@@ -12372,7 +12613,7 @@ var init_errors3 = __esm({
12372
12613
  function defaultPolicy() {
12373
12614
  return DEFAULT_POLICY;
12374
12615
  }
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;
12616
+ 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
12617
  var init_defaults = __esm({
12377
12618
  "../runtime-core/src/policy/defaults.ts"() {
12378
12619
  init_src();
@@ -12401,30 +12642,15 @@ var init_defaults = __esm({
12401
12642
  "dom-action": 100,
12402
12643
  snapshot: 0
12403
12644
  };
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
12645
  DOM_ACTION_VISUAL_STABILITY_PROFILES = {
12420
- "dom.click": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
12421
- "dom.input": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
12422
- "dom.scroll": { settleMs: 600, scope: "visible-frames", timeoutMs: 7e3 },
12646
+ "dom.click": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
12647
+ "dom.input": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
12648
+ "dom.scroll": { settleMs: 600, scope: "main-frame", timeoutMs: 7e3 },
12423
12649
  "dom.hover": { settleMs: 200, scope: "main-frame", timeoutMs: 2500 }
12424
12650
  };
12425
12651
  DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE = {
12426
12652
  settleMs: 750,
12427
- scope: "visible-frames",
12653
+ scope: "main-frame",
12428
12654
  timeoutMs: 7e3
12429
12655
  };
12430
12656
  NAVIGATION_VISUAL_STABILITY_PROFILE = {
@@ -12448,6 +12674,7 @@ var init_defaults = __esm({
12448
12674
  pageRef: input.pageRef,
12449
12675
  timeoutMs: effectiveTimeout,
12450
12676
  settleMs: profile.settleMs,
12677
+ ...input.observedMutationQuietMs === void 0 ? {} : { initialQuietMs: input.observedMutationQuietMs },
12451
12678
  scope: profile.scope
12452
12679
  });
12453
12680
  return true;
@@ -12468,15 +12695,20 @@ var init_defaults = __esm({
12468
12695
  return false;
12469
12696
  }
12470
12697
  try {
12471
- const startedAt = Date.now();
12472
- await input.engine.waitForPostLoadQuiet({
12473
- pageRef: input.pageRef,
12474
- timeoutMs: effectiveTimeout,
12475
- quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
12476
- captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
12477
- signal: input.signal
12478
- });
12479
- const visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
12698
+ let visualTimeout = effectiveTimeout;
12699
+ let initialQuietMs = input.observedMutationQuietMs ?? 0;
12700
+ if (!input.postLoadHandled) {
12701
+ const startedAt = Date.now();
12702
+ await input.engine.waitForPostLoadQuiet({
12703
+ pageRef: input.pageRef,
12704
+ timeoutMs: effectiveTimeout,
12705
+ quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
12706
+ captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
12707
+ signal: input.signal
12708
+ });
12709
+ visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
12710
+ initialQuietMs = Math.max(initialQuietMs, DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS);
12711
+ }
12480
12712
  if (visualTimeout <= 0) {
12481
12713
  return true;
12482
12714
  }
@@ -12484,6 +12716,7 @@ var init_defaults = __esm({
12484
12716
  pageRef: input.pageRef,
12485
12717
  timeoutMs: visualTimeout,
12486
12718
  settleMs: profile.settleMs,
12719
+ ...initialQuietMs <= 0 ? {} : { initialQuietMs },
12487
12720
  scope: profile.scope
12488
12721
  });
12489
12722
  return true;
@@ -12494,7 +12727,6 @@ var init_defaults = __esm({
12494
12727
  };
12495
12728
  Object.freeze(defaultNavigationSettleObserver);
12496
12729
  DEFAULT_SETTLE_OBSERVERS = Object.freeze([
12497
- defaultSnapshotSettleObserver,
12498
12730
  defaultDomActionSettleObserver,
12499
12731
  defaultNavigationSettleObserver
12500
12732
  ]);
@@ -12982,6 +13214,9 @@ function buildClauseSelector(node, clause) {
12982
13214
  if (!clause || typeof clause !== "object") {
12983
13215
  return "";
12984
13216
  }
13217
+ if (clause.kind === "text") {
13218
+ return "";
13219
+ }
12985
13220
  if (clause.kind === "position") {
12986
13221
  if (clause.axis === "nthOfType") {
12987
13222
  return `:nth-of-type(${Math.max(1, Number(node.position?.nthOfType || 1))})`;
@@ -13097,7 +13332,7 @@ function resolveExtractedValueInContext(normalizedValue, options) {
13097
13332
  function stripPositionClauses(nodes) {
13098
13333
  return (nodes || []).map((node) => ({
13099
13334
  ...node,
13100
- match: (node.match || []).filter((clause) => clause.kind !== "position")
13335
+ match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
13101
13336
  }));
13102
13337
  }
13103
13338
  function dedupeSelectors(selectors) {
@@ -13685,9 +13920,17 @@ function resolveDomPathInScope(index, domPath, scope) {
13685
13920
  if (!candidates.length) {
13686
13921
  return null;
13687
13922
  }
13923
+ const lastNode = domPath[domPath.length - 1];
13924
+ const textClauses = lastNode?.match.filter((c) => c.kind === "text") ?? [];
13688
13925
  let fallback = null;
13689
13926
  for (const selector of candidates) {
13690
- const matches = querySelectorAllInScope(index, selector, scope);
13927
+ let matches = querySelectorAllInScope(index, selector, scope);
13928
+ if (textClauses.length > 0 && matches.length > 1) {
13929
+ const filtered = matches.filter((node) => matchesTextClauses(node, textClauses));
13930
+ if (filtered.length > 0) {
13931
+ matches = filtered;
13932
+ }
13933
+ }
13691
13934
  if (matches.length === 1) {
13692
13935
  return {
13693
13936
  node: matches[0],
@@ -13707,6 +13950,10 @@ function resolveDomPathInScope(index, domPath, scope) {
13707
13950
  }
13708
13951
  return fallback;
13709
13952
  }
13953
+ function matchesTextClauses(node, clauses) {
13954
+ const text = (node.textContent ?? "").replace(/\s+/g, " ").trim();
13955
+ return clauses.every((clause) => text.includes(clause.value));
13956
+ }
13710
13957
  function queryAllDomPathInScope(index, domPath, scope) {
13711
13958
  const selectors = buildPathCandidates(domPath);
13712
13959
  for (const selector of selectors) {
@@ -13865,7 +14112,13 @@ function clonePathNode(node) {
13865
14112
  };
13866
14113
  }
13867
14114
  function cloneMatchClause(clause) {
13868
- return clause.kind === "position" ? { kind: "position", axis: clause.axis } : {
14115
+ if (clause.kind === "position") {
14116
+ return { kind: "position", axis: clause.axis };
14117
+ }
14118
+ if (clause.kind === "text") {
14119
+ return { kind: "text", value: clause.value };
14120
+ }
14121
+ return {
13869
14122
  kind: "attr",
13870
14123
  key: clause.key,
13871
14124
  ...clause.op === void 0 ? {} : { op: clause.op },
@@ -13920,6 +14173,13 @@ function normalizeMatch(rawMatch, attrs, position, tag) {
13920
14173
  op,
13921
14174
  ...value === void 0 ? {} : { value }
13922
14175
  });
14176
+ continue;
14177
+ }
14178
+ if (record.kind === "text") {
14179
+ const textValue = typeof record.value === "string" ? record.value.trim() : "";
14180
+ if (textValue) {
14181
+ push({ kind: "text", value: textValue.slice(0, 80) });
14182
+ }
13923
14183
  }
13924
14184
  }
13925
14185
  }
@@ -14334,7 +14594,7 @@ var init_executor = __esm({
14334
14594
  init_action_boundary2();
14335
14595
  init_policy();
14336
14596
  init_bridge();
14337
- MAX_DOM_ACTION_ATTEMPTS = 3;
14597
+ MAX_DOM_ACTION_ATTEMPTS = 2;
14338
14598
  DEFAULT_SCROLL_OPTIONS = {
14339
14599
  block: "center",
14340
14600
  inline: "center"
@@ -14624,7 +14884,7 @@ var init_executor = __esm({
14624
14884
  ...snapshot === void 0 ? {} : { snapshot },
14625
14885
  signal: timeout.signal,
14626
14886
  remainingMs: () => timeout.remainingMs(),
14627
- policySettle: async (targetPageRef, trigger) => {
14887
+ policySettle: async (targetPageRef, trigger, boundary2) => {
14628
14888
  try {
14629
14889
  await settleWithPolicy(this.options.policy.settle, {
14630
14890
  operation,
@@ -14632,7 +14892,9 @@ var init_executor = __esm({
14632
14892
  engine: this.options.engine,
14633
14893
  pageRef: targetPageRef,
14634
14894
  signal: timeout.signal,
14635
- remainingMs: timeout.remainingMs()
14895
+ remainingMs: timeout.remainingMs(),
14896
+ ...boundary2?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary2.observedMutationQuietMs },
14897
+ ...boundary2?.postLoadHandled === true ? { postLoadHandled: true } : {}
14636
14898
  });
14637
14899
  } catch (error) {
14638
14900
  if (snapshot !== void 0 && isSoftSettleTimeoutError(error, timeout.signal)) {
@@ -14821,11 +15083,12 @@ var init_executor = __esm({
14821
15083
  throw this.createActionabilityError(
14822
15084
  operation,
14823
15085
  "obscured",
14824
- `hit test resolved ${hit.nodeRef} outside the target subtree rooted at ${resolved.nodeRef}`,
15086
+ `target is obscured by ${assessment.blockingDescription ?? "another element"} at the click point`,
14825
15087
  {
14826
15088
  ...details,
14827
15089
  hitRelation: assessment.relation,
14828
15090
  ...assessment.ambiguous === void 0 ? {} : { hitAmbiguous: assessment.ambiguous },
15091
+ ...assessment.blockingDescription === void 0 ? {} : { blockingDescription: assessment.blockingDescription },
14829
15092
  ...assessment.canonicalTarget === void 0 ? {} : {
14830
15093
  canonicalNodeRef: assessment.canonicalTarget.nodeRef,
14831
15094
  canonicalDocumentRef: assessment.canonicalTarget.documentRef,
@@ -14839,8 +15102,7 @@ var init_executor = __esm({
14839
15102
  hitMissingFromSnapshot: !resolved.snapshot.nodes.some(
14840
15103
  (node) => node.nodeRef === hit.nodeRef
14841
15104
  )
14842
- },
14843
- true
15105
+ }
14844
15106
  );
14845
15107
  }
14846
15108
  async resolveActionablePointerTarget(session, operation, resolved) {
@@ -15031,7 +15293,12 @@ var init_runtime = __esm({
15031
15293
  });
15032
15294
  }
15033
15295
  async buildPath(input) {
15034
- return sanitizeReplayElementPath(await this.requireBridge().buildReplayPath(input.locator));
15296
+ return sanitizeReplayElementPath(
15297
+ await this.requireBridge().buildReplayPath(
15298
+ input.locator,
15299
+ input.enableTextMatch ? { enableTextMatch: true } : void 0
15300
+ )
15301
+ );
15035
15302
  }
15036
15303
  async resolveTarget(input) {
15037
15304
  return this.withSnapshotSession((session) => this.resolveTargetWithSession(session, input));
@@ -15251,7 +15518,7 @@ var init_runtime = __esm({
15251
15518
  if (resolvedByLocator) {
15252
15519
  const { snapshot, node } = resolvedByLocator;
15253
15520
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15254
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15521
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch: true });
15255
15522
  return this.createResolvedTarget("live", snapshot, node, anchor, {
15256
15523
  ...target.persist === void 0 ? {} : { persist: target.persist },
15257
15524
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15269,11 +15536,12 @@ var init_runtime = __esm({
15269
15536
  const { snapshot, node } = resolved;
15270
15537
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
15271
15538
  const writeDescriptor = descriptorWriter ?? ((input) => this.descriptors.write(input));
15272
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
15539
+ const enableTextMatch = method !== "extract";
15540
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch });
15273
15541
  const descriptor = target.persist === void 0 ? void 0 : await writeDescriptor({
15274
15542
  method,
15275
15543
  persist: target.persist,
15276
- path: replayPath ?? await this.buildPathForNode(snapshot, node),
15544
+ path: replayPath ?? await this.buildPathForNode(snapshot, node, { enableTextMatch }),
15277
15545
  sourceUrl: snapshot.url
15278
15546
  });
15279
15547
  return this.createResolvedTarget("selector", snapshot, node, anchor, {
@@ -15373,7 +15641,7 @@ var init_runtime = __esm({
15373
15641
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
15374
15642
  );
15375
15643
  }
15376
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node);
15644
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
15377
15645
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
15378
15646
  ...persist === void 0 ? {} : { persist },
15379
15647
  ...replayPath === void 0 ? {} : { replayPath }
@@ -15544,19 +15812,20 @@ var init_runtime = __esm({
15544
15812
  }
15545
15813
  return this.bridge;
15546
15814
  }
15547
- async buildPathForNode(snapshot, node) {
15815
+ async buildPathForNode(snapshot, node, options) {
15548
15816
  if (node.nodeRef === void 0) {
15549
15817
  throw new Error(
15550
15818
  `snapshot node ${String(node.snapshotNodeId)} does not expose a live node reference`
15551
15819
  );
15552
15820
  }
15553
15821
  return this.buildPath({
15554
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef)
15822
+ locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
15823
+ ...options?.enableTextMatch ? { enableTextMatch: true } : {}
15555
15824
  });
15556
15825
  }
15557
- async tryBuildPathFromNode(snapshot, node) {
15826
+ async tryBuildPathFromNode(snapshot, node, options) {
15558
15827
  try {
15559
- return await this.buildPathForNode(snapshot, node);
15828
+ return await this.buildPathForNode(snapshot, node, options);
15560
15829
  } catch {
15561
15830
  return void 0;
15562
15831
  }
@@ -15883,12 +16152,16 @@ function toOpensteerResolvedTarget(target) {
15883
16152
  documentRef: target.documentRef,
15884
16153
  documentEpoch: target.documentEpoch,
15885
16154
  nodeRef: target.nodeRef,
15886
- tagName: target.node.nodeName.toUpperCase(),
16155
+ tagName: toOpensteerTagName(target.node.nodeName),
15887
16156
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
15888
16157
  ...target.persist === void 0 ? {} : { persist: target.persist },
15889
16158
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
15890
16159
  };
15891
16160
  }
16161
+ function toOpensteerTagName(nodeName) {
16162
+ const tagName = String(nodeName).trim().toLowerCase();
16163
+ return tagName.length === 0 ? "element" : tagName;
16164
+ }
15892
16165
  var init_trace_enrichment = __esm({
15893
16166
  "../runtime-core/src/runtimes/computer-use/trace-enrichment.ts"() {
15894
16167
  init_src();
@@ -15944,7 +16217,7 @@ var init_runtime2 = __esm({
15944
16217
  screenshot,
15945
16218
  signal: input.timeout.signal,
15946
16219
  remainingMs: () => input.timeout.remainingMs(),
15947
- policySettle: async (pageRef, trigger) => {
16220
+ policySettle: async (pageRef, trigger, boundary) => {
15948
16221
  try {
15949
16222
  await settleWithPolicy(this.options.policy.settle, {
15950
16223
  operation: "computer.execute",
@@ -15952,7 +16225,9 @@ var init_runtime2 = __esm({
15952
16225
  engine: this.options.engine,
15953
16226
  pageRef,
15954
16227
  signal: input.timeout.signal,
15955
- remainingMs: input.timeout.remainingMs()
16228
+ remainingMs: input.timeout.remainingMs(),
16229
+ ...boundary?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary.observedMutationQuietMs },
16230
+ ...boundary?.postLoadHandled === true ? { postLoadHandled: true } : {}
15956
16231
  });
15957
16232
  } catch (error) {
15958
16233
  if (pageRef === input.pageRef && isSoftSettleTimeoutError(error, input.timeout.signal)) {
@@ -16948,29 +17223,29 @@ function buildVariantDescriptorFromCluster(descriptors) {
16948
17223
  const keyStats = /* @__PURE__ */ new Map();
16949
17224
  for (const descriptor of descriptors) {
16950
17225
  for (const field of descriptor.fields) {
16951
- const stat2 = keyStats.get(field.path) ?? {
17226
+ const stat3 = keyStats.get(field.path) ?? {
16952
17227
  indices: /* @__PURE__ */ new Set(),
16953
17228
  pathNodes: [],
16954
17229
  attributes: [],
16955
17230
  sources: []
16956
17231
  };
16957
- stat2.indices.add(descriptor.index);
17232
+ stat3.indices.add(descriptor.index);
16958
17233
  if (isPersistedOpensteerExtractionValueNode(field.node)) {
16959
- stat2.pathNodes.push(field.node.$path);
16960
- stat2.attributes.push(field.node.attribute);
17234
+ stat3.pathNodes.push(field.node.$path);
17235
+ stat3.attributes.push(field.node.attribute);
16961
17236
  } else if (isPersistedOpensteerExtractionSourceNode(field.node)) {
16962
- stat2.sources.push("current_url");
17237
+ stat3.sources.push("current_url");
16963
17238
  }
16964
- keyStats.set(field.path, stat2);
17239
+ keyStats.set(field.path, stat3);
16965
17240
  }
16966
17241
  }
16967
17242
  const mergedFields = [];
16968
- for (const [fieldPath, stat2] of keyStats) {
16969
- if (stat2.indices.size < threshold) {
17243
+ for (const [fieldPath, stat3] of keyStats) {
17244
+ if (stat3.indices.size < threshold) {
16970
17245
  continue;
16971
17246
  }
16972
- if (stat2.pathNodes.length >= threshold) {
16973
- let mergedFieldPath = stat2.pathNodes.length === 1 ? sanitizeElementPath(stat2.pathNodes[0]) : mergeElementPathsByMajority(stat2.pathNodes);
17247
+ if (stat3.pathNodes.length >= threshold) {
17248
+ let mergedFieldPath = stat3.pathNodes.length === 1 ? sanitizeElementPath(stat3.pathNodes[0]) : mergeElementPathsByMajority(stat3.pathNodes);
16974
17249
  if (!mergedFieldPath) {
16975
17250
  continue;
16976
17251
  }
@@ -16978,8 +17253,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
16978
17253
  mergedFieldPath = relaxPathForSingleSample(mergedFieldPath, "field");
16979
17254
  }
16980
17255
  mergedFieldPath = minimizePathMatchClauses(mergedFieldPath, "field");
16981
- const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
16982
- const attribute = pickModeString(stat2.attributes, attrThreshold);
17256
+ const attrThreshold = stat3.pathNodes.length === 1 ? 1 : majorityThreshold(stat3.pathNodes.length);
17257
+ const attribute = pickModeString(stat3.attributes, attrThreshold);
16983
17258
  mergedFields.push({
16984
17259
  path: fieldPath,
16985
17260
  node: createValueNode({
@@ -16989,7 +17264,7 @@ function buildVariantDescriptorFromCluster(descriptors) {
16989
17264
  });
16990
17265
  continue;
16991
17266
  }
16992
- const dominantSource = pickModeString(stat2.sources, threshold);
17267
+ const dominantSource = pickModeString(stat3.sources, threshold);
16993
17268
  if (dominantSource === "current_url") {
16994
17269
  mergedFields.push({
16995
17270
  path: fieldPath,
@@ -17126,6 +17401,9 @@ function relaxPathForSingleSample(path24, mode) {
17126
17401
  }
17127
17402
  return !isLast;
17128
17403
  }
17404
+ if (clause.kind === "text") {
17405
+ return false;
17406
+ }
17129
17407
  const key = String(clause.key || "").trim().toLowerCase();
17130
17408
  if (!key || !shouldKeepAttrForSingleSample(key)) {
17131
17409
  return false;
@@ -17228,9 +17506,11 @@ function buildNodeStructure(node) {
17228
17506
  }
17229
17507
  structuralAttrs[key] = value;
17230
17508
  }
17231
- const matchClauses = (node.match || []).map(
17232
- (clause) => clause.kind === "position" ? `position:${clause.axis}` : `attr:${String(clause.key || "").trim().toLowerCase()}`
17233
- ).sort();
17509
+ const matchClauses = (node.match || []).map((clause) => {
17510
+ if (clause.kind === "position") return `position:${clause.axis}`;
17511
+ if (clause.kind === "text") return `text:${clause.value}`;
17512
+ return `attr:${String(clause.key || "").trim().toLowerCase()}`;
17513
+ }).sort();
17234
17514
  return {
17235
17515
  tag,
17236
17516
  attrs: structuralAttrs,
@@ -17636,6 +17916,9 @@ function mergeMatchByMajority(matchLists, attrs, threshold, positionFlags = {
17636
17916
  });
17637
17917
  continue;
17638
17918
  }
17919
+ if (clause.kind === "text") {
17920
+ continue;
17921
+ }
17639
17922
  if (clause.axis === "nthOfType") {
17640
17923
  if (positionFlags.hasNthOfType) {
17641
17924
  merged.push({ kind: "position", axis: "nthOfType" });
@@ -19089,28 +19372,24 @@ function restoreBoundedAttr(el, attr, value) {
19089
19372
  }
19090
19373
  setBoundedAttr(el, attr, value);
19091
19374
  }
19092
- function deduplicateImages(html) {
19375
+ function deduplicateImagesInDom($) {
19093
19376
  const seen = /* @__PURE__ */ new Set();
19094
- return html.replace(/<img\b([^>]*)>/gi, (full, attrContent) => {
19095
- if (/\bc\s*=/.test(attrContent)) {
19096
- return full;
19097
- }
19098
- const srcMatch = attrContent.match(/\bsrc\s*=\s*(["']?)(.*?)\1/);
19099
- const srcsetMatch = attrContent.match(/\bsrcset\s*=\s*(["'])(.*?)\1/);
19100
- let src = null;
19101
- if (srcMatch && srcMatch[2]) {
19102
- src = srcMatch[2].trim();
19103
- } else if (srcsetMatch && srcsetMatch[2]) {
19104
- src = srcsetMatch[2].split(",")[0]?.trim().split(" ")[0] ?? null;
19377
+ $("img").each(function deduplicateDomImage() {
19378
+ const el = $(this);
19379
+ if (el.attr("c") !== void 0) {
19380
+ return;
19105
19381
  }
19382
+ const srcValue = el.attr("src")?.trim();
19383
+ const srcsetValue = el.attr("srcset");
19384
+ const src = srcValue && srcValue.length > 0 ? srcValue : srcsetValue?.split(",")[0]?.trim().split(/\s+/u)[0];
19106
19385
  if (!src) {
19107
- return full;
19386
+ return;
19108
19387
  }
19109
19388
  if (seen.has(src)) {
19110
- return "";
19389
+ el.remove();
19390
+ return;
19111
19391
  }
19112
19392
  seen.add(src);
19113
- return full;
19114
19393
  });
19115
19394
  }
19116
19395
  function hasAttribute2(node, attr) {
@@ -19168,23 +19447,6 @@ function isPreservedImageElement(node) {
19168
19447
  function getElementsInReverseDocumentOrder($) {
19169
19448
  return $.root().find("*").toArray().reverse().filter((node) => node.type === "tag");
19170
19449
  }
19171
- function getNodeDepth(node) {
19172
- let depth = 0;
19173
- let current = node.parent;
19174
- while (current) {
19175
- depth++;
19176
- current = current.parent;
19177
- }
19178
- return depth;
19179
- }
19180
- function getElementsByDepthDescending($) {
19181
- const elements = $.root().find("*").toArray().filter((node) => node.type === "tag");
19182
- const depths = /* @__PURE__ */ new Map();
19183
- for (const el of elements) {
19184
- depths.set(el, getNodeDepth(el));
19185
- }
19186
- return elements.sort((a, b) => (depths.get(b) ?? 0) - (depths.get(a) ?? 0));
19187
- }
19188
19450
  function flattenExtractionTree($) {
19189
19451
  for (const node of getElementsInReverseDocumentOrder($)) {
19190
19452
  const el = $(node);
@@ -19202,19 +19464,6 @@ function flattenExtractionTree($) {
19202
19464
  el.replaceWith(el.contents());
19203
19465
  }
19204
19466
  }
19205
- function hasMarkedAncestor(el, attr) {
19206
- let current = el[0]?.parent;
19207
- while (current) {
19208
- if (!isElementLikeNode(current)) {
19209
- return false;
19210
- }
19211
- if (current.attribs?.[attr] !== void 0) {
19212
- return true;
19213
- }
19214
- current = current.parent;
19215
- }
19216
- return false;
19217
- }
19218
19467
  function isIndicatorImage(node) {
19219
19468
  return (node?.tagName || "").toLowerCase() === "img" && (hasAttribute2(node, "alt") || hasAttribute2(node, "src") || hasAttribute2(node, "srcset"));
19220
19469
  }
@@ -19332,7 +19581,7 @@ function serializeForExtraction($, root) {
19332
19581
  traverse(root, 0);
19333
19582
  return lines.map((l) => l.trim()).filter((l) => l.length > 0).join("");
19334
19583
  }
19335
- function isClickable($, el, context) {
19584
+ function isClickable(el, context) {
19336
19585
  if (context.hasPreMarked) {
19337
19586
  return el.attr(OPENSTEER_INTERACTIVE_ATTR) !== void 0;
19338
19587
  }
@@ -19366,21 +19615,17 @@ function isClickable($, el, context) {
19366
19615
  }
19367
19616
  return false;
19368
19617
  }
19369
- function cleanForExtraction(html) {
19618
+ function prepareExtractionSnapshotDom(html) {
19370
19619
  if (!html.trim()) {
19371
- return "";
19620
+ return void 0;
19372
19621
  }
19373
19622
  const $ = cheerio__namespace.load(html, { xmlMode: false });
19374
19623
  removeNoise($);
19375
19624
  removeComments($);
19376
19625
  markInlineSelfHiddenFallback($);
19377
19626
  pruneSelfHiddenNodes($);
19378
- const $clean = cheerio__namespace.load(
19379
- $.html().replace(/\n{2,}/g, "\n").trim(),
19380
- { xmlMode: false }
19381
- );
19382
- $clean("*").each(function stripAndRestoreExtractionAttrs() {
19383
- const el = $clean(this);
19627
+ $("*").each(function stripAndRestoreExtractionAttrs() {
19628
+ const el = $(this);
19384
19629
  const node = el[0];
19385
19630
  if (!node) {
19386
19631
  return;
@@ -19421,16 +19666,20 @@ function cleanForExtraction(html) {
19421
19666
  restoreBoundedAttr(el, "href", hrefValue);
19422
19667
  }
19423
19668
  });
19424
- flattenExtractionTree($clean);
19425
- const root = $clean.root()[0];
19669
+ flattenExtractionTree($);
19670
+ deduplicateImagesInDom($);
19671
+ return $;
19672
+ }
19673
+ function serializePreparedExtractionSnapshot($) {
19674
+ const root = $.root()[0];
19426
19675
  if (root === void 0) {
19427
19676
  return "";
19428
19677
  }
19429
- return deduplicateImages(serializeForExtraction($clean, root));
19678
+ return serializeForExtraction($, root);
19430
19679
  }
19431
- function cleanForAction(html) {
19680
+ function prepareActionSnapshotDom(html) {
19432
19681
  if (!html.trim()) {
19433
- return "";
19682
+ return void 0;
19434
19683
  }
19435
19684
  const $ = cheerio__namespace.load(html, { xmlMode: false });
19436
19685
  removeNoise($);
@@ -19439,13 +19688,12 @@ function cleanForAction(html) {
19439
19688
  pruneSelfHiddenNodes($);
19440
19689
  const clickableMark = "data-clickable-marker";
19441
19690
  const indicatorMark = "data-keep-indicator";
19442
- const branchMark = "data-keep-branch";
19443
19691
  const context = {
19444
19692
  hasPreMarked: $(`[${OPENSTEER_INTERACTIVE_ATTR}]`).length > 0
19445
19693
  };
19446
19694
  $("*").each(function markClickables() {
19447
19695
  const el = $(this);
19448
- if (isClickable($, el, context)) {
19696
+ if (isClickable(el, context)) {
19449
19697
  el.attr(clickableMark, "1");
19450
19698
  }
19451
19699
  });
@@ -19475,25 +19723,7 @@ function cleanForAction(html) {
19475
19723
  el.remove();
19476
19724
  }
19477
19725
  });
19478
- $(`[${clickableMark}], [${indicatorMark}]`).each(function markBranches() {
19479
- let current = $(this).parent();
19480
- while (current.length > 0) {
19481
- const node = current[0];
19482
- if (!node || node.type !== "tag") {
19483
- break;
19484
- }
19485
- const ancestor = current;
19486
- const tag = (node.tagName || "").toLowerCase();
19487
- if (ROOT_TAGS.has(tag) || ancestor.attr(clickableMark) !== void 0) {
19488
- break;
19489
- }
19490
- if (!isBoundaryTag(tag)) {
19491
- ancestor.attr(branchMark, "1");
19492
- }
19493
- current = ancestor.parent();
19494
- }
19495
- });
19496
- for (const node of getElementsByDepthDescending($)) {
19726
+ for (const node of getElementsInReverseDocumentOrder($)) {
19497
19727
  const el = $(node);
19498
19728
  const tag = (node.tagName || "").toLowerCase();
19499
19729
  if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
@@ -19502,17 +19732,7 @@ function cleanForAction(html) {
19502
19732
  if (el.attr(clickableMark) !== void 0 || el.attr(indicatorMark) !== void 0) {
19503
19733
  continue;
19504
19734
  }
19505
- const insideClickable = hasMarkedAncestor(el, clickableMark);
19506
- const preserveBranch = el.attr(branchMark) !== void 0;
19507
19735
  const hasContent = hasElementChildren(node) || hasDirectText(node);
19508
- if (insideClickable || preserveBranch) {
19509
- if (!hasContent) {
19510
- el.remove();
19511
- } else {
19512
- unwrapActionNode($, el);
19513
- }
19514
- continue;
19515
- }
19516
19736
  if (!hasContent) {
19517
19737
  el.remove();
19518
19738
  continue;
@@ -19613,13 +19833,20 @@ function cleanForAction(html) {
19613
19833
  }
19614
19834
  el.removeAttr(clickableMark);
19615
19835
  el.removeAttr(indicatorMark);
19616
- el.removeAttr(branchMark);
19617
19836
  el.removeAttr(OPENSTEER_INTERACTIVE_ATTR);
19618
19837
  el.removeAttr(OPENSTEER_HIDDEN_ATTR);
19619
19838
  el.removeAttr(OPENSTEER_SCROLLABLE_ATTR);
19620
19839
  el.removeAttr(OPENSTEER_SELF_HIDDEN_ATTR);
19621
19840
  });
19622
- return compactHtml(deduplicateImages($.html()));
19841
+ deduplicateImagesInDom($);
19842
+ return $;
19843
+ }
19844
+ function serializePreparedActionSnapshot($) {
19845
+ const normalized = compactHtml($.html());
19846
+ if (normalized.length === 0) {
19847
+ return "";
19848
+ }
19849
+ return cheerio__namespace.load(normalized, { xmlMode: false }).html();
19623
19850
  }
19624
19851
  var STRIP_TAGS, TEXT_ATTR_MAX, SRCSET_ATTR_MAX, MIDDLE_TRUNCATED_URL_ATTRS, TEXT_ATTRS, TRUNCATION_SUFFIX, MIDDLE_TRUNCATION_MARKER, MIDDLE_TRUNCATION_HEAD_MAX, MIDDLE_TRUNCATION_TAIL_MAX, SRCSET_CANDIDATE_HEAD_MAX, SRCSET_CANDIDATE_TAIL_MAX, SRCSET_COMPACT_CANDIDATE_HEAD_MAX, SRCSET_COMPACT_CANDIDATE_TAIL_MAX, SRCSET_FALLBACK_HEAD_MAX, SRCSET_FALLBACK_TAIL_MAX, NOISE_SELECTORS, VOID_TAGS2;
19625
19852
  var init_cleaner = __esm({
@@ -19680,23 +19907,27 @@ async function markLiveSnapshotSemantics(options) {
19680
19907
  const frames = await options.engine.listFrames({
19681
19908
  pageRef: options.pageRef
19682
19909
  });
19683
- for (const frame of frames) {
19684
- await evaluateFrameBestEffort(
19685
- options.engine,
19686
- frame.frameRef,
19687
- MARK_SNAPSHOT_SEMANTICS_SCRIPT,
19688
- SNAPSHOT_SEMANTIC_ARGS
19689
- );
19690
- }
19691
- return async () => {
19692
- for (const frame of frames) {
19693
- await evaluateFrameBestEffort(
19910
+ await Promise.all(
19911
+ frames.map(
19912
+ (frame) => evaluateFrameBestEffort(
19694
19913
  options.engine,
19695
19914
  frame.frameRef,
19696
- CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
19697
- CLEAR_SNAPSHOT_SEMANTIC_ARGS
19698
- );
19699
- }
19915
+ MARK_SNAPSHOT_SEMANTICS_SCRIPT,
19916
+ SNAPSHOT_SEMANTIC_ARGS
19917
+ )
19918
+ )
19919
+ );
19920
+ return async () => {
19921
+ await Promise.all(
19922
+ frames.map(
19923
+ (frame) => evaluateFrameBestEffort(
19924
+ options.engine,
19925
+ frame.frameRef,
19926
+ CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
19927
+ CLEAR_SNAPSHOT_SEMANTIC_ARGS
19928
+ )
19929
+ )
19930
+ );
19700
19931
  };
19701
19932
  }
19702
19933
  var MARK_SNAPSHOT_SEMANTICS_SCRIPT, CLEAR_SNAPSHOT_SEMANTICS_SCRIPT, SNAPSHOT_SEMANTIC_ARGS, CLEAR_SNAPSHOT_SEMANTIC_ARGS;
@@ -19894,6 +20125,8 @@ var init_marking = __esm({
19894
20125
  ];
19895
20126
  }
19896
20127
  });
20128
+
20129
+ // ../runtime-core/src/sdk/snapshot/compiler.ts
19897
20130
  function isLiveCounterSyncError(error) {
19898
20131
  return error instanceof LiveCounterSyncError;
19899
20132
  }
@@ -19931,20 +20164,22 @@ function ensureSparseCountersForAllRecords(counterRecords) {
19931
20164
  async function clearOpensteerLiveCounters(engine, pageRef) {
19932
20165
  const frames = await engine.listFrames({ pageRef });
19933
20166
  const failures = [];
19934
- for (const frame of frames) {
19935
- try {
19936
- await engine.evaluateFrame({
19937
- frameRef: frame.frameRef,
19938
- script: CLEAR_LIVE_COUNTERS_SCRIPT,
19939
- args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
19940
- });
19941
- } catch (error) {
19942
- if (isDetachedFrameSyncError(error)) {
19943
- continue;
20167
+ await Promise.all(
20168
+ frames.map(async (frame) => {
20169
+ try {
20170
+ await engine.evaluateFrame({
20171
+ frameRef: frame.frameRef,
20172
+ script: CLEAR_LIVE_COUNTERS_SCRIPT,
20173
+ args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
20174
+ });
20175
+ } catch (error) {
20176
+ if (isDetachedFrameSyncError(error)) {
20177
+ return;
20178
+ }
20179
+ failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
19944
20180
  }
19945
- failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
19946
- }
19947
- }
20181
+ })
20182
+ );
19948
20183
  if (failures.length > 0) {
19949
20184
  throw buildLiveCounterSyncError("clear live counters", failures);
19950
20185
  }
@@ -19993,25 +20228,29 @@ async function syncDenseCountersToLiveDom(engine, pageRef, sparseToDirectMapping
19993
20228
  denseCounter
19994
20229
  ])
19995
20230
  );
19996
- for (const frame of frames) {
19997
- try {
19998
- await engine.evaluateFrame({
19999
- frameRef: frame.frameRef,
20000
- script: APPLY_DENSE_COUNTERS_SCRIPT,
20001
- args: [
20002
- {
20003
- sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
20004
- mapping: mappingObj
20005
- }
20006
- ]
20007
- });
20008
- } catch (error) {
20009
- if (isDetachedFrameSyncError(error)) {
20010
- continue;
20231
+ await Promise.all(
20232
+ frames.map(async (frame) => {
20233
+ try {
20234
+ await engine.evaluateFrame({
20235
+ frameRef: frame.frameRef,
20236
+ script: APPLY_DENSE_COUNTERS_SCRIPT,
20237
+ args: [
20238
+ {
20239
+ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
20240
+ mapping: mappingObj
20241
+ }
20242
+ ]
20243
+ });
20244
+ } catch (error) {
20245
+ if (isDetachedFrameSyncError(error)) {
20246
+ return;
20247
+ }
20248
+ failures.push(
20249
+ `frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`
20250
+ );
20011
20251
  }
20012
- failures.push(`frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`);
20013
- }
20014
- }
20252
+ })
20253
+ );
20015
20254
  if (failures.length > 0) {
20016
20255
  throw buildLiveCounterSyncError("synchronize dense counters", failures);
20017
20256
  }
@@ -20029,26 +20268,26 @@ async function compileOpensteerSnapshot(options) {
20029
20268
  await clearOpensteerLiveCounters(options.engine, options.pageRef);
20030
20269
  await assignSparseCountersToLiveDom(options.engine, options.pageRef);
20031
20270
  const pageInfo = await options.engine.getPageInfo({ pageRef: options.pageRef });
20032
- const mainSnapshot = await getMainDocumentSnapshot(options.engine, options.pageRef);
20033
- const snapshotsByDocumentRef = await collectDocumentSnapshots(options.engine, mainSnapshot);
20271
+ const { mainSnapshot, snapshotsByDocumentRef } = await getPageDocumentSnapshots(
20272
+ options.engine,
20273
+ options.pageRef
20274
+ );
20034
20275
  await cleanupLiveSemantics();
20035
20276
  cleanupLiveSemantics = async () => {
20036
20277
  };
20037
- const snapshotIndices = /* @__PURE__ */ new Map();
20038
20278
  const renderedNodes = /* @__PURE__ */ new Map();
20039
20279
  const rawHtml = renderDocumentSnapshot(
20040
20280
  mainSnapshot.documentRef,
20041
20281
  snapshotsByDocumentRef,
20042
- snapshotIndices,
20043
20282
  renderedNodes,
20044
20283
  {
20045
20284
  iframeDepth: 0,
20046
20285
  shadowDepth: 0
20047
20286
  }
20048
20287
  );
20049
- const cleanedHtml = options.mode === "extraction" ? cleanForExtraction(rawHtml) : cleanForAction(rawHtml);
20050
- const compiledHtml = assignCounters(cleanedHtml, renderedNodes);
20051
- const finalHtml = options.mode === "extraction" ? unwrapExtractionHtml(compiledHtml.html) : compiledHtml.html;
20288
+ const preparedSnapshotDom = options.mode === "extraction" ? prepareExtractionSnapshotDom(rawHtml) : prepareActionSnapshotDom(rawHtml);
20289
+ const compiledHtml = assignCountersInDom(preparedSnapshotDom, renderedNodes, options.mode);
20290
+ const finalHtml = preparedSnapshotDom === void 0 ? "" : options.mode === "extraction" ? serializePreparedExtractionSnapshot(preparedSnapshotDom) : serializePreparedActionSnapshot(preparedSnapshotDom);
20052
20291
  ensureSparseCountersForAllRecords(compiledHtml.counterRecords);
20053
20292
  await syncDenseCountersToLiveDom(
20054
20293
  options.engine,
@@ -20083,6 +20322,25 @@ async function getMainDocumentSnapshot(engine, pageRef) {
20083
20322
  }
20084
20323
  return engine.getDomSnapshot({ frameRef: mainFrame.frameRef });
20085
20324
  }
20325
+ async function getPageDocumentSnapshots(engine, pageRef) {
20326
+ const bundleEngine = engine;
20327
+ const bundledSnapshots = await bundleEngine.getPageDomSnapshots?.({ pageRef });
20328
+ if (bundledSnapshots && bundledSnapshots.length > 0) {
20329
+ const mainSnapshot2 = bundledSnapshots.find((snapshot) => snapshot.parentDocumentRef === void 0) ?? bundledSnapshots[0];
20330
+ return {
20331
+ mainSnapshot: mainSnapshot2,
20332
+ snapshotsByDocumentRef: new Map(
20333
+ bundledSnapshots.map((snapshot) => [snapshot.documentRef, snapshot])
20334
+ )
20335
+ };
20336
+ }
20337
+ const mainSnapshot = await getMainDocumentSnapshot(engine, pageRef);
20338
+ const snapshotsByDocumentRef = await collectDocumentSnapshots(engine, mainSnapshot);
20339
+ return {
20340
+ mainSnapshot,
20341
+ snapshotsByDocumentRef
20342
+ };
20343
+ }
20086
20344
  async function collectDocumentSnapshots(engine, mainSnapshot) {
20087
20345
  const snapshotsByDocumentRef = /* @__PURE__ */ new Map([
20088
20346
  [mainSnapshot.documentRef, mainSnapshot]
@@ -20101,7 +20359,7 @@ async function collectDocumentSnapshots(engine, mainSnapshot) {
20101
20359
  }
20102
20360
  return snapshotsByDocumentRef;
20103
20361
  }
20104
- function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
20362
+ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, renderedNodes, depth) {
20105
20363
  const snapshot = snapshotsByDocumentRef.get(documentRef);
20106
20364
  if (!snapshot) {
20107
20365
  return "";
@@ -20113,17 +20371,9 @@ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotInd
20113
20371
  `snapshot ${snapshot.documentRef} is missing root node ${String(snapshot.rootSnapshotNodeId)}`
20114
20372
  );
20115
20373
  }
20116
- return renderNode(
20117
- snapshot,
20118
- rootNode,
20119
- nodesById,
20120
- snapshotsByDocumentRef,
20121
- snapshotIndices,
20122
- renderedNodes,
20123
- depth
20124
- );
20374
+ return renderNode(snapshot, rootNode, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
20125
20375
  }
20126
- function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
20376
+ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
20127
20377
  if (node.nodeType === 3) {
20128
20378
  return escapeHtml(node.nodeValue || node.textContent || "");
20129
20379
  }
@@ -20131,56 +20381,26 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20131
20381
  return "";
20132
20382
  }
20133
20383
  if (node.nodeType === 9 || node.nodeType === 11) {
20134
- return renderChildren(
20135
- snapshot,
20136
- node,
20137
- nodesById,
20138
- snapshotsByDocumentRef,
20139
- snapshotIndices,
20140
- renderedNodes,
20141
- depth
20142
- );
20384
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
20143
20385
  }
20144
20386
  if (node.nodeType !== 1) {
20145
- return renderChildren(
20146
- snapshot,
20147
- node,
20148
- nodesById,
20149
- snapshotsByDocumentRef,
20150
- snapshotIndices,
20151
- renderedNodes,
20152
- depth
20153
- );
20387
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
20154
20388
  }
20155
20389
  const tagName = normalizeTagName(node.nodeName);
20156
20390
  if (isPseudoElementTagName(tagName)) {
20157
- return renderChildren(
20158
- snapshot,
20159
- node,
20160
- nodesById,
20161
- snapshotsByDocumentRef,
20162
- snapshotIndices,
20163
- renderedNodes,
20164
- depth
20165
- );
20391
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
20166
20392
  }
20167
20393
  if ((depth.iframeDepth > 0 || depth.shadowDepth > 0) && (tagName === "html" || tagName === "head" || tagName === "body")) {
20168
- return renderChildren(
20169
- snapshot,
20170
- node,
20171
- nodesById,
20172
- snapshotsByDocumentRef,
20173
- snapshotIndices,
20174
- renderedNodes,
20175
- depth
20176
- );
20394
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
20177
20395
  }
20178
20396
  const snapshotAttributes = normalizeNodeAttributes(node.attributes);
20397
+ const snapshotAttributeIndex = indexNodeAttributes(snapshotAttributes);
20179
20398
  const authoredAttributes = stripInternalSnapshotAttributes(snapshotAttributes);
20399
+ const authoredAttributeIndex = indexNodeAttributes(authoredAttributes);
20180
20400
  const attributes = [...authoredAttributes];
20181
- const subtreeHidden = hasAttribute3(snapshotAttributes, OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
20182
- const selfHidden = !subtreeHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
20183
- const interactive = !subtreeHidden && !selfHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes));
20401
+ const subtreeHidden = snapshotAttributeIndex.has(OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
20402
+ const selfHidden = !subtreeHidden && (snapshotAttributeIndex.has(OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
20403
+ const interactive = !subtreeHidden && !selfHidden && (snapshotAttributeIndex.has(OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes, authoredAttributeIndex));
20184
20404
  if (interactive) {
20185
20405
  attributes.push({ name: OPENSTEER_INTERACTIVE_ATTR, value: "1" });
20186
20406
  }
@@ -20189,7 +20409,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20189
20409
  } else if (selfHidden) {
20190
20410
  attributes.push({ name: OPENSTEER_SELF_HIDDEN_ATTR, value: "1" });
20191
20411
  }
20192
- const sparseCounter = findAttributeValue(snapshotAttributes, OPENSTEER_SPARSE_COUNTER_ATTR);
20412
+ const sparseCounter = snapshotAttributeIndex.get(OPENSTEER_SPARSE_COUNTER_ATTR);
20193
20413
  if (sparseCounter !== void 0) {
20194
20414
  attributes.push({ name: OPENSTEER_SPARSE_COUNTER_ATTR, value: sparseCounter });
20195
20415
  }
@@ -20200,21 +20420,18 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20200
20420
  const syntheticNodeId = buildSyntheticNodeId(snapshot, node);
20201
20421
  attributes.push({ name: OPENSTEER_NODE_ID_ATTR, value: syntheticNodeId });
20202
20422
  renderedNodes.set(syntheticNodeId, {
20203
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
20204
- anchor: buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices),
20205
20423
  pageRef: snapshot.pageRef,
20206
20424
  frameRef: snapshot.frameRef,
20207
20425
  documentRef: snapshot.documentRef,
20208
20426
  documentEpoch: snapshot.documentEpoch,
20209
20427
  nodeRef: node.nodeRef,
20210
20428
  tagName: tagName.toUpperCase(),
20211
- pathHint: buildPathHint(tagName, authoredAttributes),
20212
- ...buildTextSnippet(node.textContent) === void 0 ? {} : { text: buildTextSnippet(node.textContent) },
20213
20429
  ...authoredAttributes.length === 0 ? {} : { attributes: authoredAttributes },
20214
20430
  iframeDepth: depth.iframeDepth,
20215
20431
  shadowDepth: depth.shadowDepth,
20216
20432
  interactive,
20217
- liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById)
20433
+ liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById),
20434
+ ...node.textContent === void 0 ? {} : { textContent: node.textContent }
20218
20435
  });
20219
20436
  }
20220
20437
  const attributeText = attributesToHtml(attributes);
@@ -20223,7 +20440,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20223
20440
  node,
20224
20441
  nodesById,
20225
20442
  snapshotsByDocumentRef,
20226
- snapshotIndices,
20227
20443
  renderedNodes,
20228
20444
  depth
20229
20445
  );
@@ -20234,7 +20450,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20234
20450
  const iframeHtml = renderDocumentSnapshot(
20235
20451
  node.contentDocumentRef,
20236
20452
  snapshotsByDocumentRef,
20237
- snapshotIndices,
20238
20453
  renderedNodes,
20239
20454
  {
20240
20455
  iframeDepth: depth.iframeDepth + 1,
@@ -20246,7 +20461,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
20246
20461
  }
20247
20462
  return `${elementHtml}<${OPENSTEER_IFRAME_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="iframe">${iframeHtml}</${OPENSTEER_IFRAME_BOUNDARY_TAG}>`;
20248
20463
  }
20249
- function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
20464
+ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
20250
20465
  const regularChildren = [];
20251
20466
  const shadowChildren = [];
20252
20467
  for (const childSnapshotNodeId of node.childSnapshotNodeIds) {
@@ -20263,18 +20478,10 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
20263
20478
  const chunks = [];
20264
20479
  if (shadowChildren.length > 0) {
20265
20480
  const shadowHtml = shadowChildren.map(
20266
- (child) => renderNode(
20267
- snapshot,
20268
- child,
20269
- nodesById,
20270
- snapshotsByDocumentRef,
20271
- snapshotIndices,
20272
- renderedNodes,
20273
- {
20274
- iframeDepth: depth.iframeDepth,
20275
- shadowDepth: depth.shadowDepth + 1
20276
- }
20277
- )
20481
+ (child) => renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, {
20482
+ iframeDepth: depth.iframeDepth,
20483
+ shadowDepth: depth.shadowDepth + 1
20484
+ })
20278
20485
  ).join("");
20279
20486
  chunks.push(
20280
20487
  `<${OPENSTEER_SHADOW_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="shadow">${shadowHtml}</${OPENSTEER_SHADOW_BOUNDARY_TAG}>`
@@ -20282,24 +20489,21 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
20282
20489
  }
20283
20490
  for (const child of regularChildren) {
20284
20491
  chunks.push(
20285
- renderNode(
20286
- snapshot,
20287
- child,
20288
- nodesById,
20289
- snapshotsByDocumentRef,
20290
- snapshotIndices,
20291
- renderedNodes,
20292
- depth
20293
- )
20492
+ renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, depth)
20294
20493
  );
20295
20494
  }
20296
20495
  return chunks.join("");
20297
20496
  }
20298
- function assignCounters(cleanedHtml, renderedNodes) {
20299
- const $ = cheerio__namespace.load(cleanedHtml, { xmlMode: false });
20497
+ function assignCountersInDom($, renderedNodes, mode) {
20300
20498
  const counterRecords = /* @__PURE__ */ new Map();
20301
20499
  const sparseToDirectMapping = /* @__PURE__ */ new Map();
20302
20500
  let nextCounter = 1;
20501
+ if (!$) {
20502
+ return {
20503
+ counterRecords,
20504
+ sparseToDirectMapping
20505
+ };
20506
+ }
20303
20507
  $("*").each(function assignElementCounter() {
20304
20508
  const el = $(this);
20305
20509
  const syntheticNodeId = el.attr(OPENSTEER_NODE_ID_ATTR);
@@ -20311,14 +20515,24 @@ function assignCounters(cleanedHtml, renderedNodes) {
20311
20515
  if (!rendered) {
20312
20516
  return;
20313
20517
  }
20518
+ if (mode === "extraction" && EXTRACTION_SKIPPED_COUNTER_TAGS.has(rendered.tagName.toLowerCase())) {
20519
+ el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
20520
+ return;
20521
+ }
20314
20522
  const rawSparseCounter = el.attr(OPENSTEER_SPARSE_COUNTER_ATTR);
20315
20523
  el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
20316
20524
  const sparseCounter = rawSparseCounter ? Number.parseInt(rawSparseCounter, 10) : void 0;
20525
+ const replayableSparseCounter = typeof sparseCounter === "number" && Number.isFinite(sparseCounter) ? sparseCounter : void 0;
20526
+ if (rendered.liveCounterSyncEligible && replayableSparseCounter === void 0) {
20527
+ return;
20528
+ }
20317
20529
  const counter = nextCounter++;
20318
20530
  el.attr("c", String(counter));
20319
- if (sparseCounter !== void 0 && Number.isFinite(sparseCounter)) {
20320
- sparseToDirectMapping.set(sparseCounter, counter);
20531
+ if (replayableSparseCounter !== void 0) {
20532
+ sparseToDirectMapping.set(replayableSparseCounter, counter);
20321
20533
  }
20534
+ const pathHint = buildPathHint(rendered.tagName.toLowerCase(), rendered.attributes ?? []);
20535
+ const text = buildTextSnippet(rendered.textContent);
20322
20536
  counterRecords.set(counter, {
20323
20537
  element: counter,
20324
20538
  pageRef: rendered.pageRef,
@@ -20327,20 +20541,17 @@ function assignCounters(cleanedHtml, renderedNodes) {
20327
20541
  documentEpoch: rendered.documentEpoch,
20328
20542
  nodeRef: rendered.nodeRef,
20329
20543
  tagName: rendered.tagName,
20330
- pathHint: rendered.pathHint,
20331
- ...rendered.text === void 0 ? {} : { text: rendered.text },
20544
+ pathHint,
20545
+ ...text === void 0 ? {} : { text },
20332
20546
  ...rendered.attributes === void 0 ? {} : { attributes: rendered.attributes },
20333
20547
  iframeDepth: rendered.iframeDepth,
20334
20548
  shadowDepth: rendered.shadowDepth,
20335
20549
  interactive: rendered.interactive,
20336
20550
  liveCounterSyncEligible: rendered.liveCounterSyncEligible,
20337
- locator: rendered.locator,
20338
- anchor: rendered.anchor,
20339
- ...sparseCounter !== void 0 && Number.isFinite(sparseCounter) ? { sparseCounter } : {}
20551
+ ...replayableSparseCounter === void 0 ? {} : { sparseCounter: replayableSparseCounter }
20340
20552
  });
20341
20553
  });
20342
20554
  return {
20343
- html: $.html(),
20344
20555
  counterRecords,
20345
20556
  sparseToDirectMapping
20346
20557
  };
@@ -20414,28 +20625,28 @@ function isLikelySelfHidden(node, nodesById) {
20414
20625
  }
20415
20626
  return !hasVisibleOutOfFlowChild(node, nodesById);
20416
20627
  }
20417
- function isLikelyInteractive(tagName, node, attributes) {
20628
+ function isLikelyInteractive(tagName, node, attributes, attributeIndex) {
20418
20629
  if (NATIVE_INTERACTIVE_TAGS.has(tagName)) {
20419
- if (tagName === "input" && findAttributeValue(attributes, "type")?.toLowerCase() === "hidden") {
20630
+ if (tagName === "input" && attributeIndex.get("type")?.toLowerCase() === "hidden") {
20420
20631
  return false;
20421
20632
  }
20422
20633
  if (tagName !== "a") {
20423
20634
  return true;
20424
20635
  }
20425
20636
  }
20426
- if (tagName === "a" && findAttributeValue(attributes, "href") !== void 0) {
20637
+ if (tagName === "a" && attributeIndex.has("href")) {
20427
20638
  return true;
20428
20639
  }
20429
- if (findAttributeValue(attributes, "onclick") !== void 0 || findAttributeValue(attributes, "onmousedown") !== void 0 || findAttributeValue(attributes, "onmouseup") !== void 0 || findAttributeValue(attributes, "data-action") !== void 0 || findAttributeValue(attributes, "data-click") !== void 0 || findAttributeValue(attributes, "data-toggle") !== void 0) {
20640
+ if (attributeIndex.has("onclick") || attributeIndex.has("onmousedown") || attributeIndex.has("onmouseup") || attributeIndex.has("data-action") || attributeIndex.has("data-click") || attributeIndex.has("data-toggle")) {
20430
20641
  return true;
20431
20642
  }
20432
- if (hasNonNegativeTabIndex(findAttributeValue(attributes, "tabindex"))) {
20643
+ if (hasNonNegativeTabIndex(attributeIndex.get("tabindex"))) {
20433
20644
  return true;
20434
20645
  }
20435
- if (findAttributeValue(attributes, "contenteditable")?.toLowerCase() === "true") {
20646
+ if (attributeIndex.get("contenteditable")?.toLowerCase() === "true") {
20436
20647
  return true;
20437
20648
  }
20438
- const role = findAttributeValue(attributes, "role")?.toLowerCase();
20649
+ const role = attributeIndex.get("role")?.toLowerCase();
20439
20650
  return role !== void 0 && INTERACTIVE_ROLE_SET.has(role);
20440
20651
  }
20441
20652
  function hasVisibleOutOfFlowChild(node, nodesById) {
@@ -20498,14 +20709,6 @@ function parseOpacity(value) {
20498
20709
  const parsed = Number.parseFloat(value);
20499
20710
  return Number.isFinite(parsed) ? parsed : Number.NaN;
20500
20711
  }
20501
- function hasAttribute3(attributes, name) {
20502
- const normalizedName = name.toLowerCase();
20503
- return attributes.some((attribute) => attribute.name.toLowerCase() === normalizedName);
20504
- }
20505
- function unwrapExtractionHtml(html) {
20506
- const $ = cheerio__namespace.load(html, { xmlMode: false });
20507
- return $("body").html()?.trim() || html;
20508
- }
20509
20712
  function buildSyntheticNodeId(snapshot, node) {
20510
20713
  return `${snapshot.documentRef}:${String(snapshot.documentEpoch)}:${String(node.snapshotNodeId)}`;
20511
20714
  }
@@ -20552,6 +20755,13 @@ function findAttributeValue(attributes, name) {
20552
20755
  const normalizedName = name.toLowerCase();
20553
20756
  return attributes.find((attribute) => attribute.name.toLowerCase() === normalizedName)?.value;
20554
20757
  }
20758
+ function indexNodeAttributes(attributes) {
20759
+ const indexed = /* @__PURE__ */ new Map();
20760
+ for (const attribute of attributes) {
20761
+ indexed.set(attribute.name.toLowerCase(), attribute.value);
20762
+ }
20763
+ return indexed;
20764
+ }
20555
20765
  function attributesToHtml(attributes) {
20556
20766
  if (attributes.length === 0) {
20557
20767
  return "";
@@ -20561,68 +20771,16 @@ function attributesToHtml(attributes) {
20561
20771
  function escapeAttribute(value) {
20562
20772
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20563
20773
  }
20564
- function buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices) {
20565
- const index = getSnapshotIndex(snapshot.documentRef, snapshotsByDocumentRef, snapshotIndices);
20566
- const localAnchor = buildLocalStructuralElementAnchor(index, node);
20567
- return prefixIframeContext(snapshot, localAnchor, snapshotsByDocumentRef, snapshotIndices);
20568
- }
20569
- function prefixIframeContext(snapshot, localPath, snapshotsByDocumentRef, snapshotIndices) {
20570
- if (snapshot.parentDocumentRef === void 0) {
20571
- return sanitizeStructuralElementAnchor(localPath);
20572
- }
20573
- const parentSnapshot = snapshotsByDocumentRef.get(snapshot.parentDocumentRef);
20574
- if (!parentSnapshot) {
20575
- throw new Error(
20576
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no parent snapshot`
20577
- );
20578
- }
20579
- const parentIndex = getSnapshotIndex(
20580
- parentSnapshot.documentRef,
20581
- snapshotsByDocumentRef,
20582
- snapshotIndices
20583
- );
20584
- const iframeHost = findIframeHostNode(parentIndex, snapshot.documentRef);
20585
- if (!iframeHost) {
20586
- throw new Error(
20587
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no iframe host`
20588
- );
20589
- }
20590
- const hostPath = buildSnapshotElementAnchor(
20591
- parentSnapshot,
20592
- iframeHost,
20593
- snapshotsByDocumentRef,
20594
- snapshotIndices
20595
- );
20596
- return sanitizeStructuralElementAnchor({
20597
- context: [...hostPath.context, { kind: "iframe", host: hostPath.nodes }, ...localPath.context],
20598
- nodes: localPath.nodes
20599
- });
20600
- }
20601
- function getSnapshotIndex(documentRef, snapshotsByDocumentRef, snapshotIndices) {
20602
- const existing = snapshotIndices.get(documentRef);
20603
- if (existing) {
20604
- return existing;
20605
- }
20606
- const snapshot = snapshotsByDocumentRef.get(documentRef);
20607
- if (!snapshot) {
20608
- throw new Error(`missing DOM snapshot for ${documentRef}`);
20609
- }
20610
- const index = createSnapshotIndex(snapshot);
20611
- snapshotIndices.set(documentRef, index);
20612
- return index;
20613
- }
20614
20774
  function escapeHtml(value) {
20615
20775
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
20616
20776
  }
20617
- var INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES, MAX_LIVE_COUNTER_SYNC_ATTEMPTS, CLEAR_LIVE_COUNTERS_SCRIPT, ASSIGN_SPARSE_COUNTERS_SCRIPT, APPLY_DENSE_COUNTERS_SCRIPT, LiveCounterSyncError;
20777
+ var EXTRACTION_SKIPPED_COUNTER_TAGS, INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES, MAX_LIVE_COUNTER_SYNC_ATTEMPTS, CLEAR_LIVE_COUNTERS_SCRIPT, ASSIGN_SPARSE_COUNTERS_SCRIPT, APPLY_DENSE_COUNTERS_SCRIPT, LiveCounterSyncError;
20618
20778
  var init_compiler = __esm({
20619
20779
  "../runtime-core/src/sdk/snapshot/compiler.ts"() {
20620
- init_src();
20621
- init_path();
20622
- init_selectors();
20623
20780
  init_cleaner();
20624
20781
  init_constants();
20625
20782
  init_marking();
20783
+ EXTRACTION_SKIPPED_COUNTER_TAGS = /* @__PURE__ */ new Set(["html", "head", "body"]);
20626
20784
  INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
20627
20785
  "c",
20628
20786
  OPENSTEER_BOUNDARY_ATTR,
@@ -20637,7 +20795,7 @@ var init_compiler = __esm({
20637
20795
  MAX_LIVE_COUNTER_SYNC_ATTEMPTS = 4;
20638
20796
  CLEAR_LIVE_COUNTERS_SCRIPT = `(({ sparseCounterAttr }) => {
20639
20797
  const walk = (root) => {
20640
- for (const child of root.children) {
20798
+ for (const child of Array.from(root?.children || [])) {
20641
20799
  child.removeAttribute("c");
20642
20800
  child.removeAttribute(sparseCounterAttr);
20643
20801
  walk(child);
@@ -20653,7 +20811,7 @@ var init_compiler = __esm({
20653
20811
  ASSIGN_SPARSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, startCounter }) => {
20654
20812
  let counter = startCounter;
20655
20813
  const walk = (root) => {
20656
- for (const child of root.children) {
20814
+ for (const child of Array.from(root?.children || [])) {
20657
20815
  child.setAttribute(sparseCounterAttr, String(counter++));
20658
20816
  walk(child);
20659
20817
  if (child.shadowRoot) {
@@ -20667,7 +20825,7 @@ var init_compiler = __esm({
20667
20825
  })`;
20668
20826
  APPLY_DENSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, mapping }) => {
20669
20827
  const walk = (root) => {
20670
- for (const child of root.children) {
20828
+ for (const child of Array.from(root?.children || [])) {
20671
20829
  child.removeAttribute("c");
20672
20830
  const sparse = child.getAttribute(sparseCounterAttr);
20673
20831
  if (sparse !== null) {
@@ -22851,15 +23009,10 @@ function normalizeNamespace2(value) {
22851
23009
  const normalized = String(value ?? "default").trim();
22852
23010
  return normalized.length === 0 ? "default" : normalized;
22853
23011
  }
22854
- function toOpensteerActionResult(result) {
23012
+ function toOpensteerActionResult(target) {
22855
23013
  return {
22856
- target: toOpensteerResolvedTarget2(result.resolved),
22857
- ...result.point === void 0 ? {} : {
22858
- point: {
22859
- x: result.point.x,
22860
- y: result.point.y
22861
- }
22862
- }
23014
+ tagName: toOpensteerTagName2(target.node.nodeName),
23015
+ ...target.persist === void 0 ? {} : { persist: target.persist }
22863
23016
  };
22864
23017
  }
22865
23018
  function toOpensteerResolvedTarget2(target) {
@@ -22869,12 +23022,16 @@ function toOpensteerResolvedTarget2(target) {
22869
23022
  documentRef: target.documentRef,
22870
23023
  documentEpoch: target.documentEpoch,
22871
23024
  nodeRef: target.nodeRef,
22872
- tagName: target.node.nodeName.toUpperCase(),
23025
+ tagName: toOpensteerTagName2(target.node.nodeName),
22873
23026
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
22874
23027
  ...target.persist === void 0 ? {} : { persist: target.persist },
22875
23028
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
22876
23029
  };
22877
23030
  }
23031
+ function toOpensteerTagName2(nodeName) {
23032
+ const tagName = String(nodeName).trim().toLowerCase();
23033
+ return tagName.length === 0 ? "element" : tagName;
23034
+ }
22878
23035
  function normalizeOpensteerError(error) {
22879
23036
  return normalizeThrownOpensteerError(error, "Unknown Opensteer runtime failure");
22880
23037
  }
@@ -23192,7 +23349,7 @@ var init_runtime3 = __esm({
23192
23349
  options
23193
23350
  );
23194
23351
  }
23195
- return this.readSessionState();
23352
+ return this.readNavigationSummary();
23196
23353
  }
23197
23354
  const startedAt = Date.now();
23198
23355
  const root = await this.ensureRoot();
@@ -23213,7 +23370,8 @@ var init_runtime3 = __esm({
23213
23370
  openedSessionRef = sessionRef;
23214
23371
  const createdPage = await timeout.runStep(
23215
23372
  () => engine.createPage({
23216
- sessionRef
23373
+ sessionRef,
23374
+ ...input.url === void 0 ? {} : { url: input.url }
23217
23375
  })
23218
23376
  );
23219
23377
  openedPageRef = createdPage.data.pageRef;
@@ -23223,18 +23381,19 @@ var init_runtime3 = __esm({
23223
23381
  await timeout.runStep(() => this.ensureSemantics());
23224
23382
  let frameRef2 = createdPage.frameRef;
23225
23383
  if (input.url !== void 0) {
23226
- const navigation = await this.navigatePage(
23227
- {
23384
+ await timeout.runStep(
23385
+ () => settleWithPolicy(this.policy.settle, {
23228
23386
  operation: "session.open",
23387
+ trigger: "navigation",
23388
+ engine: this.requireEngine(),
23229
23389
  pageRef: createdPage.data.pageRef,
23230
- url: input.url
23231
- },
23232
- timeout
23390
+ signal: timeout.signal,
23391
+ remainingMs: timeout.remainingMs()
23392
+ })
23233
23393
  );
23234
- frameRef2 = navigation.data.mainFrame.frameRef;
23235
23394
  }
23236
23395
  return {
23237
- state: await timeout.runStep(() => this.readSessionState()),
23396
+ state: await timeout.runStep(() => this.readNavigationSummary()),
23238
23397
  frameRef: frameRef2
23239
23398
  };
23240
23399
  },
@@ -23327,7 +23486,11 @@ var init_runtime3 = __esm({
23327
23486
  "page.new cannot use openerPageRef before a session exists"
23328
23487
  );
23329
23488
  }
23330
- return this.open(input.url === void 0 ? {} : { url: input.url }, options);
23489
+ const summary = await this.open(input.url === void 0 ? {} : { url: input.url }, options);
23490
+ return {
23491
+ pageRef: await this.ensurePageRef(),
23492
+ ...summary
23493
+ };
23331
23494
  }
23332
23495
  const startedAt = Date.now();
23333
23496
  try {
@@ -23342,7 +23505,7 @@ var init_runtime3 = __esm({
23342
23505
  })
23343
23506
  );
23344
23507
  this.pageRef = created.data.pageRef;
23345
- return this.readSessionState();
23508
+ return this.readCreatedPageOutput(created.data.pageRef);
23346
23509
  },
23347
23510
  options
23348
23511
  );
@@ -23384,7 +23547,7 @@ var init_runtime3 = __esm({
23384
23547
  () => this.requireEngine().activatePage({ pageRef: input.pageRef })
23385
23548
  );
23386
23549
  this.pageRef = input.pageRef;
23387
- return this.readSessionState();
23550
+ return this.readNavigationSummary(input.pageRef);
23388
23551
  },
23389
23552
  options
23390
23553
  );
@@ -23510,7 +23673,7 @@ var init_runtime3 = __esm({
23510
23673
  timeout.throwIfAborted();
23511
23674
  return {
23512
23675
  navigation: navigation2,
23513
- state: await timeout.runStep(() => this.readSessionState())
23676
+ state: await timeout.runStep(() => this.readNavigationSummary(pageRef))
23514
23677
  };
23515
23678
  },
23516
23679
  (diagnostics) => {
@@ -24744,7 +24907,7 @@ var init_runtime3 = __esm({
24744
24907
  let mutationCaptureDiagnostics;
24745
24908
  let boundaryDiagnostics;
24746
24909
  try {
24747
- const { artifacts, output } = await this.runMutationCapturedOperation(
24910
+ const { artifacts, output, result } = await this.runMutationCapturedOperation(
24748
24911
  "computer.execute",
24749
24912
  {
24750
24913
  ...input.captureNetwork === void 0 ? {} : { captureNetwork: input.captureNetwork },
@@ -24762,9 +24925,14 @@ var init_runtime3 = __esm({
24762
24925
  await this.invalidateLiveSnapshotCounters([pageRef, output2.pageRef], timeout);
24763
24926
  this.pageRef = output2.pageRef;
24764
24927
  const artifacts2 = await this.persistComputerArtifacts(output2, timeout);
24928
+ const result2 = {
24929
+ ...await timeout.runStep(() => this.readNavigationSummary(output2.pageRef)),
24930
+ screenshot: artifacts2.screenshot
24931
+ };
24765
24932
  return {
24766
24933
  artifacts: { manifests: artifacts2.manifests },
24767
- output: artifacts2.output
24934
+ output: output2,
24935
+ result: result2
24768
24936
  };
24769
24937
  } catch (error) {
24770
24938
  boundaryDiagnostics ??= takeActionBoundaryDiagnostics(timeout.signal);
@@ -24801,7 +24969,7 @@ var init_runtime3 = __esm({
24801
24969
  documentEpoch: output.screenshot.documentEpoch
24802
24970
  })
24803
24971
  });
24804
- return output;
24972
+ return result;
24805
24973
  } catch (error) {
24806
24974
  await this.appendTrace({
24807
24975
  operation: "computer.execute",
@@ -24949,8 +25117,9 @@ var init_runtime3 = __esm({
24949
25117
  mutationCaptureDiagnostics = diagnostics;
24950
25118
  }
24951
25119
  );
24952
- const output = toOpensteerActionResult(executed.result);
25120
+ const output = toOpensteerActionResult(executed.result.resolved);
24953
25121
  const actionEvents = "events" in executed.result ? executed.result.events : void 0;
25122
+ const resolvedTarget = toOpensteerResolvedTarget2(executed.result.resolved);
24954
25123
  await this.appendTrace({
24955
25124
  operation,
24956
25125
  startedAt,
@@ -24958,8 +25127,13 @@ var init_runtime3 = __esm({
24958
25127
  outcome: "ok",
24959
25128
  ...actionEvents === void 0 ? {} : { events: actionEvents },
24960
25129
  data: {
24961
- target: output.target,
24962
- ...output.point === void 0 ? {} : { point: output.point },
25130
+ target: resolvedTarget,
25131
+ ..."point" in executed.result && executed.result.point !== void 0 ? {
25132
+ point: {
25133
+ x: executed.result.point.x,
25134
+ y: executed.result.point.y
25135
+ }
25136
+ } : {},
24963
25137
  ...boundaryDiagnostics === void 0 ? {} : { settle: boundaryDiagnostics },
24964
25138
  ...buildMutationCaptureTraceData(mutationCaptureDiagnostics)
24965
25139
  },
@@ -26424,20 +26598,20 @@ var init_runtime3 = __esm({
26424
26598
  throw error;
26425
26599
  }
26426
26600
  }
26427
- async readSessionState() {
26428
- const pageRef = await this.ensurePageRef();
26601
+ async readNavigationSummary(targetPageRef) {
26602
+ const pageRef = targetPageRef ?? await this.ensurePageRef();
26429
26603
  const pageInfo = await this.requireEngine().getPageInfo({ pageRef });
26430
- const sessionRef = this.sessionRef;
26431
- if (!sessionRef) {
26432
- throw new Error("Opensteer session is not initialized");
26433
- }
26434
26604
  return {
26435
- sessionRef,
26436
- pageRef,
26437
26605
  url: pageInfo.url,
26438
26606
  title: pageInfo.title
26439
26607
  };
26440
26608
  }
26609
+ async readCreatedPageOutput(pageRef) {
26610
+ return {
26611
+ pageRef,
26612
+ ...await this.readNavigationSummary(pageRef)
26613
+ };
26614
+ }
26441
26615
  async captureSnapshotArtifacts(pageRef, options, timeout) {
26442
26616
  const root = this.requireRoot();
26443
26617
  const mainFrame = await timeout.runStep(() => getMainFrame(this.requireEngine(), pageRef));
@@ -26509,12 +26683,12 @@ var init_runtime3 = __esm({
26509
26683
  const screenshotPayload = manifestToExternalBinaryLocation(root.rootPath, screenshotManifest);
26510
26684
  return {
26511
26685
  manifests,
26512
- output: {
26513
- ...output,
26514
- screenshot: {
26515
- ...output.screenshot,
26516
- payload: screenshotPayload
26517
- }
26686
+ screenshot: {
26687
+ payload: screenshotPayload,
26688
+ format: output.screenshot.format,
26689
+ size: output.screenshot.size,
26690
+ coordinateSpace: output.screenshot.coordinateSpace,
26691
+ ...output.screenshot.clip === void 0 ? {} : { clip: output.screenshot.clip }
26518
26692
  }
26519
26693
  };
26520
26694
  }
@@ -28599,7 +28773,11 @@ function generateReplayScript(options) {
28599
28773
  ``,
28600
28774
  ...renderOpensteerBootstrap(replayTarget),
28601
28775
  ``,
28602
- `const ${initialPageId} = (await opensteer.open(${JSON.stringify(initialPages[0]?.initialUrl ?? "")})).pageRef;`,
28776
+ `await opensteer.open(${JSON.stringify(initialPages[0]?.initialUrl ?? "")});`,
28777
+ `const ${initialPageId} = (await opensteer.listPages()).activePageRef;`,
28778
+ `if (!${initialPageId}) {`,
28779
+ ` throw new Error("Opensteer did not report an active page after open().");`,
28780
+ `}`,
28603
28781
  `let activePageRef: string | undefined = ${initialPageId};`,
28604
28782
  ``,
28605
28783
  `async function ensureActive(pageRef: string): Promise<void> {`,
@@ -29528,8 +29706,29 @@ function assertSupportedCloudBrowserMode(browser) {
29528
29706
  }
29529
29707
  }
29530
29708
  function isMissingCloudSessionError(error) {
29709
+ if (error instanceof OpensteerCloudRequestError) {
29710
+ return error.statusCode === 404 && (error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND");
29711
+ }
29531
29712
  return error instanceof Error && /\b404\b/.test(error.message);
29532
29713
  }
29714
+ function isRecoverableCloudSessionError(error) {
29715
+ if (!(error instanceof OpensteerCloudRequestError)) {
29716
+ return false;
29717
+ }
29718
+ if (error.statusCode === 404) {
29719
+ return error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND";
29720
+ }
29721
+ return error.statusCode === 409 && error.code === "CLOUD_SESSION_STALE";
29722
+ }
29723
+ function isBootstrapRecoveryOperation(operation) {
29724
+ return operation === "session.open" || operation === "instrumentation.route" || operation === "instrumentation.intercept-script";
29725
+ }
29726
+ function isReusableCloudSessionState(session) {
29727
+ if (session.status === "closing" || session.status === "closed" || session.status === "failed") {
29728
+ return false;
29729
+ }
29730
+ return !(typeof session.expiresAt === "number" && session.expiresAt <= Date.now() + CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS);
29731
+ }
29533
29732
  function isLoopbackBaseUrl(baseUrl) {
29534
29733
  let url;
29535
29734
  try {
@@ -29539,7 +29738,7 @@ function isLoopbackBaseUrl(baseUrl) {
29539
29738
  }
29540
29739
  return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]";
29541
29740
  }
29542
- var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CloudSessionProxy;
29741
+ var TEMPORARY_CLOUD_WORKSPACE_PREFIX, CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS, CloudSessionProxy;
29543
29742
  var init_session_proxy = __esm({
29544
29743
  "src/cloud/session-proxy.ts"() {
29545
29744
  init_src3();
@@ -29549,8 +29748,10 @@ var init_session_proxy = __esm({
29549
29748
  init_policy2();
29550
29749
  init_semantic_rest_client();
29551
29750
  init_automation_client();
29751
+ init_client();
29552
29752
  init_workspace_sync();
29553
29753
  TEMPORARY_CLOUD_WORKSPACE_PREFIX = "opensteer-cloud-workspace-";
29754
+ CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS = 1e4;
29554
29755
  CloudSessionProxy = class {
29555
29756
  rootPath;
29556
29757
  workspace;
@@ -29564,6 +29765,8 @@ var init_session_proxy = __esm({
29564
29765
  automation;
29565
29766
  workspaceStore;
29566
29767
  syncWorkspaceOnClose = false;
29768
+ liveSessionStateEstablished = false;
29769
+ storedInstrumentation = [];
29567
29770
  constructor(cloud, options = {}) {
29568
29771
  this.cloud = cloud;
29569
29772
  this.workspace = options.workspace;
@@ -29593,15 +29796,12 @@ var init_session_proxy = __esm({
29593
29796
  if (this.client === void 0 && this.sessionId === void 0 && persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
29594
29797
  this.bindClient(persisted);
29595
29798
  }
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
- }
29799
+ const sessionInfo = this.automation ? await this.automation.getSessionInfo().catch(() => void 0) : void 0;
29800
+ if (sessionInfo !== void 0) {
29801
+ return {
29802
+ ...sessionInfo,
29803
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace }
29804
+ };
29605
29805
  }
29606
29806
  return {
29607
29807
  provider: {
@@ -29713,12 +29913,26 @@ var init_session_proxy = __esm({
29713
29913
  return this.invokeSemanticOperation("session.cookies", input);
29714
29914
  }
29715
29915
  async route(input) {
29716
- await this.ensureSession();
29717
- return this.requireAutomation().route(input);
29916
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29917
+ "instrumentation.route",
29918
+ (automation) => automation.route(input)
29919
+ );
29920
+ this.storedInstrumentation.push({
29921
+ kind: "route",
29922
+ input
29923
+ });
29924
+ return registration;
29718
29925
  }
29719
29926
  async interceptScript(input) {
29720
- await this.ensureSession();
29721
- return this.requireAutomation().interceptScript(input);
29927
+ const registration = await this.invokeBootstrapInstrumentationOperation(
29928
+ "instrumentation.intercept-script",
29929
+ (automation) => automation.interceptScript(input)
29930
+ );
29931
+ this.storedInstrumentation.push({
29932
+ kind: "intercept-script",
29933
+ input
29934
+ });
29935
+ return registration;
29722
29936
  }
29723
29937
  async getStorageSnapshot(input = {}) {
29724
29938
  return this.invokeSemanticOperation("session.storage", input);
@@ -29766,6 +29980,8 @@ var init_session_proxy = __esm({
29766
29980
  this.client = void 0;
29767
29981
  this.sessionId = void 0;
29768
29982
  this.semanticGrant = void 0;
29983
+ this.liveSessionStateEstablished = false;
29984
+ this.storedInstrumentation.length = 0;
29769
29985
  if (this.cleanupRootOnClose) {
29770
29986
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
29771
29987
  }
@@ -29793,6 +30009,8 @@ var init_session_proxy = __esm({
29793
30009
  this.automation = void 0;
29794
30010
  this.sessionId = void 0;
29795
30011
  this.semanticGrant = void 0;
30012
+ this.liveSessionStateEstablished = false;
30013
+ this.storedInstrumentation.length = 0;
29796
30014
  if (syncError !== void 0) {
29797
30015
  throw syncError;
29798
30016
  }
@@ -29840,6 +30058,7 @@ var init_session_proxy = __esm({
29840
30058
  };
29841
30059
  await this.writePersistedSession(record);
29842
30060
  this.bindClient(record, session.initialGrants?.semantic);
30061
+ await this.restoreStoredInstrumentation();
29843
30062
  }
29844
30063
  async syncWorkspaceToCloud() {
29845
30064
  if (this.workspace === void 0) {
@@ -29851,6 +30070,7 @@ var init_session_proxy = __esm({
29851
30070
  bindClient(record, initialSemanticGrant) {
29852
30071
  this.sessionId = record.sessionId;
29853
30072
  this.semanticGrant = initialSemanticGrant?.kind === "semantic" ? initialSemanticGrant : void 0;
30073
+ this.liveSessionStateEstablished = false;
29854
30074
  this.client = new OpensteerSemanticRestClient({
29855
30075
  getBaseUrl: async () => (await this.ensureSemanticGrant()).url,
29856
30076
  getAuthorizationHeader: async () => `Bearer ${(await this.ensureSemanticGrant()).token}`,
@@ -29858,6 +30078,19 @@ var init_session_proxy = __esm({
29858
30078
  });
29859
30079
  this.automation = new OpensteerCloudAutomationClient(this.cloud, record.sessionId);
29860
30080
  }
30081
+ async restoreStoredInstrumentation() {
30082
+ if (this.storedInstrumentation.length === 0) {
30083
+ return;
30084
+ }
30085
+ const automation = this.requireAutomation();
30086
+ for (const registration of this.storedInstrumentation) {
30087
+ if (registration.kind === "route") {
30088
+ await automation.route(registration.input);
30089
+ } else {
30090
+ await automation.interceptScript(registration.input);
30091
+ }
30092
+ }
30093
+ }
29861
30094
  async ensureWorkspaceStore() {
29862
30095
  if (this.workspaceStore !== void 0) {
29863
30096
  return this.workspaceStore;
@@ -29880,13 +30113,22 @@ var init_session_proxy = __esm({
29880
30113
  async clearPersistedSession() {
29881
30114
  await clearPersistedSessionRecord(this.rootPath, "cloud").catch(() => void 0);
29882
30115
  }
30116
+ async invalidateSession() {
30117
+ await this.automation?.close().catch(() => void 0);
30118
+ this.automation = void 0;
30119
+ this.client = void 0;
30120
+ this.sessionId = void 0;
30121
+ this.semanticGrant = void 0;
30122
+ this.liveSessionStateEstablished = false;
30123
+ await this.clearPersistedSession();
30124
+ }
29883
30125
  async isReusableCloudSession(sessionId, timeout) {
29884
30126
  try {
29885
30127
  const session = await this.cloud.getSession(sessionId, {
29886
30128
  signal: timeout?.signal,
29887
30129
  timeoutMs: timeout?.remainingMs()
29888
30130
  });
29889
- return session.status !== "closed" && session.status !== "failed";
30131
+ return isReusableCloudSessionState(session);
29890
30132
  } catch (error) {
29891
30133
  if (isMissingCloudSessionError(error)) {
29892
30134
  return false;
@@ -29935,26 +30177,79 @@ var init_session_proxy = __esm({
29935
30177
  try {
29936
30178
  await this.ensureSemanticGrant(true);
29937
30179
  return true;
29938
- } catch {
30180
+ } catch (refreshError) {
30181
+ if (await this.resetStaleSession(refreshError)) {
30182
+ throw refreshError;
30183
+ }
29939
30184
  return false;
29940
30185
  }
29941
30186
  }
29942
30187
  async invokeSemanticOperation(operation, input, sessionInit = {}) {
29943
- return this.runOperationWithPolicy(operation, async (timeout) => {
30188
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29944
30189
  await this.ensureSession(sessionInit, timeout);
29945
30190
  await this.ensureSemanticGrant(false, timeout);
29946
- return this.requireClient().invoke(operation, input, {
30191
+ const output = await this.requireClient().invoke(operation, input, {
29947
30192
  signal: timeout.signal,
29948
30193
  timeoutMs: timeout.remainingMs()
29949
30194
  });
30195
+ this.noteSuccessfulLiveOperation(operation);
30196
+ return output;
29950
30197
  });
29951
30198
  }
29952
30199
  async invokeAutomationOperation(operation, invoke, sessionInit = {}) {
29953
- return this.runOperationWithPolicy(operation, async (timeout) => {
30200
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
29954
30201
  await this.ensureSession(sessionInit, timeout);
29955
- return invoke(this.requireAutomation());
30202
+ const output = await invoke(this.requireAutomation());
30203
+ this.noteSuccessfulLiveOperation(operation);
30204
+ return output;
29956
30205
  });
29957
30206
  }
30207
+ async invokeBootstrapInstrumentationOperation(operation, invoke) {
30208
+ let recovered = false;
30209
+ while (true) {
30210
+ try {
30211
+ await this.ensureSession();
30212
+ return await invoke(this.requireAutomation());
30213
+ } catch (error) {
30214
+ const stale = await this.resetStaleSession(error);
30215
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30216
+ throw error;
30217
+ }
30218
+ recovered = true;
30219
+ }
30220
+ }
30221
+ }
30222
+ async runOperationWithSessionRecovery(operation, invoke) {
30223
+ return this.runOperationWithPolicy(operation, async (timeout) => {
30224
+ let recovered = false;
30225
+ while (true) {
30226
+ try {
30227
+ return await invoke(timeout);
30228
+ } catch (error) {
30229
+ const stale = await this.resetStaleSession(error);
30230
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
30231
+ throw error;
30232
+ }
30233
+ recovered = true;
30234
+ }
30235
+ }
30236
+ });
30237
+ }
30238
+ async resetStaleSession(error) {
30239
+ if (!isRecoverableCloudSessionError(error)) {
30240
+ return false;
30241
+ }
30242
+ await this.invalidateSession();
30243
+ return true;
30244
+ }
30245
+ canRecoverWithFreshSession(operation) {
30246
+ return !this.liveSessionStateEstablished && isBootstrapRecoveryOperation(operation);
30247
+ }
30248
+ noteSuccessfulLiveOperation(operation) {
30249
+ 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") {
30250
+ this.liveSessionStateEstablished = true;
30251
+ }
30252
+ }
29958
30253
  async runOperationWithPolicy(operation, invoke) {
29959
30254
  return runWithPolicyTimeout(this.policy.timeout, { operation }, invoke);
29960
30255
  }
@@ -30269,14 +30564,85 @@ var init_session_control = __esm({
30269
30564
  }
30270
30565
  });
30271
30566
 
30567
+ // src/internal/errors.ts
30568
+ function normalizeThrownOpensteerError2(error, fallbackMessage) {
30569
+ if (isOpensteerProtocolError(error)) {
30570
+ return toOpensteerError(error);
30571
+ }
30572
+ if (isBrowserCoreError(error)) {
30573
+ return createOpensteerError(error.code, error.message, {
30574
+ retriable: error.retriable,
30575
+ ...error.details === void 0 ? {} : { details: error.details }
30576
+ });
30577
+ }
30578
+ if (error instanceof OpensteerAttachAmbiguousError) {
30579
+ return createOpensteerError("conflict", error.message, {
30580
+ details: {
30581
+ candidates: error.candidates,
30582
+ code: error.code,
30583
+ name: error.name
30584
+ }
30585
+ });
30586
+ }
30587
+ if (error instanceof Error && "opensteerError" in error && typeof error.opensteerError === "object" && error.opensteerError !== null) {
30588
+ const oe = error.opensteerError;
30589
+ return createOpensteerError(oe.code, oe.message, {
30590
+ retriable: oe.retriable,
30591
+ ...oe.capability === void 0 ? {} : { capability: oe.capability },
30592
+ ...oe.details === void 0 ? {} : { details: oe.details }
30593
+ });
30594
+ }
30595
+ if (error instanceof Error) {
30596
+ return createOpensteerError("operation-failed", error.message, {
30597
+ details: {
30598
+ name: error.name
30599
+ }
30600
+ });
30601
+ }
30602
+ return createOpensteerError("internal", fallbackMessage, {
30603
+ details: {
30604
+ value: error
30605
+ }
30606
+ });
30607
+ }
30608
+ var init_errors5 = __esm({
30609
+ "src/internal/errors.ts"() {
30610
+ init_src();
30611
+ init_src2();
30612
+ init_cdp_discovery();
30613
+ }
30614
+ });
30615
+
30272
30616
  // src/sdk/opensteer.ts
30273
30617
  var opensteer_exports = {};
30274
30618
  __export(opensteer_exports, {
30275
30619
  Opensteer: () => Opensteer
30276
30620
  });
30621
+ function createSdkProtocolError(error, fallbackMessage) {
30622
+ const normalized = normalizeThrownOpensteerError2(error, fallbackMessage);
30623
+ return new OpensteerProtocolError(normalized.code, normalized.message, {
30624
+ cause: error,
30625
+ retriable: normalized.retriable,
30626
+ ...normalized.capability === void 0 ? {} : { capability: normalized.capability },
30627
+ ...normalized.details === void 0 ? {} : { details: normalized.details }
30628
+ });
30629
+ }
30630
+ async function wrapSdkError(operation, fn) {
30631
+ try {
30632
+ return await fn();
30633
+ } catch (error) {
30634
+ if (isOpensteerProtocolError(error)) {
30635
+ throw error;
30636
+ }
30637
+ throw createSdkProtocolError(error, `${operation} failed`);
30638
+ }
30639
+ }
30277
30640
  function createUnsupportedBrowserController() {
30278
30641
  const fail = async () => {
30279
- throw new Error("browser.* helpers are only available in local mode.");
30642
+ throw new OpensteerProtocolError(
30643
+ "unsupported-operation",
30644
+ "browser.* helpers are only available in local mode."
30645
+ );
30280
30646
  };
30281
30647
  return {
30282
30648
  status: fail,
@@ -30289,7 +30655,10 @@ function normalizeTargetOptions(input) {
30289
30655
  const hasElement = input.element !== void 0;
30290
30656
  const hasSelector = input.selector !== void 0;
30291
30657
  if (hasElement && hasSelector) {
30292
- throw new Error("Specify exactly one of element, selector, or persist.");
30658
+ throw new OpensteerProtocolError(
30659
+ "invalid-argument",
30660
+ "Specify exactly one of element, selector, or persist."
30661
+ );
30293
30662
  }
30294
30663
  if (hasElement) {
30295
30664
  return {
@@ -30312,7 +30681,10 @@ function normalizeTargetOptions(input) {
30312
30681
  };
30313
30682
  }
30314
30683
  if (input.persist === void 0) {
30315
- throw new Error("Specify exactly one of element, selector, or persist.");
30684
+ throw new OpensteerProtocolError(
30685
+ "invalid-argument",
30686
+ "Specify exactly one of element, selector, or persist."
30687
+ );
30316
30688
  }
30317
30689
  return {
30318
30690
  target: {
@@ -30374,8 +30746,10 @@ function delay5(ms) {
30374
30746
  var SessionCookieJar, Opensteer;
30375
30747
  var init_opensteer = __esm({
30376
30748
  "src/sdk/opensteer.ts"() {
30749
+ init_src2();
30377
30750
  init_browser_manager();
30378
30751
  init_env();
30752
+ init_errors5();
30379
30753
  init_runtime_resolution();
30380
30754
  SessionCookieJar = class {
30381
30755
  domain;
@@ -30406,202 +30780,257 @@ var init_opensteer = __esm({
30406
30780
  dom;
30407
30781
  network;
30408
30782
  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({
30783
+ try {
30784
+ const environment = resolveOpensteerEnvironment(options.rootDir);
30785
+ const { provider, engineName, ...runtimeOptions } = options;
30786
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
30436
30787
  ...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
- }
30788
+ environment
30444
30789
  });
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()
30790
+ if (runtimeConfig.provider.mode === "cloud") {
30791
+ this.browserManager = void 0;
30792
+ this.runtime = createOpensteerSemanticRuntime({
30793
+ ...provider === void 0 ? {} : { provider },
30794
+ ...engineName === void 0 ? {} : { engine: engineName },
30795
+ environment,
30796
+ runtimeOptions
30797
+ });
30798
+ this.browser = createUnsupportedBrowserController();
30799
+ } else {
30800
+ this.browserManager = new OpensteerBrowserManager({
30801
+ ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
30802
+ ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
30803
+ ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
30804
+ ...engineName === void 0 ? {} : { engineName },
30805
+ environment,
30806
+ ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
30807
+ ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
30808
+ ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
30809
+ });
30810
+ this.runtime = createOpensteerSemanticRuntime({
30811
+ ...provider === void 0 ? {} : { provider },
30812
+ ...engineName === void 0 ? {} : { engine: engineName },
30813
+ environment,
30814
+ runtimeOptions: {
30815
+ ...runtimeOptions,
30816
+ rootPath: this.browserManager.rootPath,
30817
+ cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
30818
+ }
30819
+ });
30820
+ this.browser = {
30821
+ status: () => wrapSdkError("browser.status", () => this.browserManager.status()),
30822
+ clone: (input) => wrapSdkError("browser.clone", () => this.browserManager.clonePersistentBrowser(input)),
30823
+ reset: () => wrapSdkError("browser.reset", () => this.browserManager.reset()),
30824
+ delete: () => wrapSdkError("browser.delete", () => this.browserManager.delete())
30825
+ };
30826
+ }
30827
+ this.dom = {
30828
+ click: (input) => this.click(input),
30829
+ hover: (input) => this.hover(input),
30830
+ input: (input) => this.input(input),
30831
+ scroll: (input) => this.scroll(input)
30832
+ };
30833
+ this.network = {
30834
+ query: (input = {}) => wrapSdkError("network.query", () => this.runtime.queryNetwork(input)),
30835
+ detail: (recordId, options2) => wrapSdkError(
30836
+ "network.detail",
30837
+ () => this.runtime.getNetworkDetail({ recordId, ...options2 })
30838
+ )
30450
30839
  };
30840
+ } catch (error) {
30841
+ if (isOpensteerProtocolError(error)) {
30842
+ throw error;
30843
+ }
30844
+ throw createSdkProtocolError(error, "Failed to initialize Opensteer");
30451
30845
  }
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
30846
  }
30463
30847
  async open(input = {}) {
30464
- return this.runtime.open(typeof input === "string" ? { url: input } : input);
30848
+ return wrapSdkError(
30849
+ "session.open",
30850
+ () => this.runtime.open(typeof input === "string" ? { url: input } : input)
30851
+ );
30465
30852
  }
30466
30853
  async info() {
30467
- return this.runtime.info();
30854
+ return wrapSdkError("session.info", () => this.runtime.info());
30468
30855
  }
30469
30856
  async listPages(input = {}) {
30470
- return this.runtime.listPages(input);
30857
+ return wrapSdkError("page.list", () => this.runtime.listPages(input));
30471
30858
  }
30472
30859
  async newPage(input = {}) {
30473
- return this.runtime.newPage(input);
30860
+ return wrapSdkError("page.new", () => this.runtime.newPage(input));
30474
30861
  }
30475
30862
  async activatePage(input) {
30476
- return this.runtime.activatePage(input);
30863
+ return wrapSdkError("page.activate", () => this.runtime.activatePage(input));
30477
30864
  }
30478
30865
  async closePage(input = {}) {
30479
- return this.runtime.closePage(input);
30866
+ return wrapSdkError("page.close", () => this.runtime.closePage(input));
30480
30867
  }
30481
30868
  async goto(url, options = {}) {
30482
- return this.runtime.goto({
30483
- url,
30484
- ...options
30485
- });
30869
+ return wrapSdkError(
30870
+ "page.goto",
30871
+ () => this.runtime.goto({
30872
+ url,
30873
+ ...options
30874
+ })
30875
+ );
30486
30876
  }
30487
30877
  async evaluate(input) {
30488
- const normalized = typeof input === "string" ? {
30489
- script: input
30490
- } : input;
30491
- return (await this.runtime.evaluate(normalized)).value;
30878
+ return wrapSdkError("page.evaluate", async () => {
30879
+ const normalized = typeof input === "string" ? {
30880
+ script: input
30881
+ } : input;
30882
+ return (await this.runtime.evaluate(normalized)).value;
30883
+ });
30492
30884
  }
30493
30885
  async addInitScript(input) {
30494
- return this.runtime.addInitScript(
30495
- typeof input === "string" ? {
30496
- script: input
30497
- } : input
30886
+ return wrapSdkError(
30887
+ "page.addInitScript",
30888
+ () => this.runtime.addInitScript(
30889
+ typeof input === "string" ? {
30890
+ script: input
30891
+ } : input
30892
+ )
30498
30893
  );
30499
30894
  }
30500
30895
  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 }
30896
+ return wrapSdkError("dom.click", () => {
30897
+ const { button, clickCount, modifiers, ...target } = input;
30898
+ return this.runtime.click({
30899
+ ...normalizeTargetOptions(target),
30900
+ ...button === void 0 ? {} : { button },
30901
+ ...clickCount === void 0 ? {} : { clickCount },
30902
+ ...modifiers === void 0 ? {} : { modifiers }
30903
+ });
30507
30904
  });
30508
30905
  }
30509
30906
  async hover(input) {
30510
- return this.runtime.hover(normalizeTargetOptions(input));
30907
+ return wrapSdkError("dom.hover", () => this.runtime.hover(normalizeTargetOptions(input)));
30511
30908
  }
30512
30909
  async input(input) {
30513
- return this.runtime.input({
30514
- ...normalizeTargetOptions(input),
30515
- text: input.text,
30516
- ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30517
- });
30910
+ return wrapSdkError(
30911
+ "dom.input",
30912
+ () => this.runtime.input({
30913
+ ...normalizeTargetOptions(input),
30914
+ text: input.text,
30915
+ ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
30916
+ })
30917
+ );
30518
30918
  }
30519
30919
  async scroll(input) {
30520
- return this.runtime.scroll({
30521
- ...normalizeTargetOptions(input),
30522
- direction: input.direction,
30523
- amount: input.amount
30524
- });
30920
+ return wrapSdkError(
30921
+ "dom.scroll",
30922
+ () => this.runtime.scroll({
30923
+ ...normalizeTargetOptions(input),
30924
+ direction: input.direction,
30925
+ amount: input.amount
30926
+ })
30927
+ );
30525
30928
  }
30526
30929
  async extract(input) {
30527
- return (await this.runtime.extract(input)).data;
30930
+ return wrapSdkError("extract", async () => (await this.runtime.extract(input)).data);
30528
30931
  }
30529
30932
  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;
30933
+ return wrapSdkError("page.waitForPage", async () => {
30934
+ const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
30935
+ const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
30936
+ const pollIntervalMs = input.pollIntervalMs ?? 100;
30937
+ while (true) {
30938
+ const match = (await this.runtime.listPages()).pages.find((page) => {
30939
+ if (baseline.has(page.pageRef)) {
30940
+ return false;
30941
+ }
30942
+ if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
30943
+ return false;
30944
+ }
30945
+ if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30946
+ return false;
30947
+ }
30948
+ return true;
30949
+ });
30950
+ if (match !== void 0) {
30951
+ return match;
30540
30952
  }
30541
- if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
30542
- return false;
30953
+ if (Date.now() >= timeoutAt) {
30954
+ throw new OpensteerProtocolError("timeout", "waitForPage timed out");
30543
30955
  }
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");
30956
+ await delay5(pollIntervalMs);
30551
30957
  }
30552
- await delay5(pollIntervalMs);
30553
- }
30958
+ });
30554
30959
  }
30555
30960
  async cookies(domain) {
30556
- return new SessionCookieJar(
30557
- await this.runtime.getCookies(domain === void 0 ? {} : { domain })
30961
+ return wrapSdkError(
30962
+ "session.cookies",
30963
+ async () => new SessionCookieJar(await this.runtime.getCookies(domain === void 0 ? {} : { domain }))
30558
30964
  );
30559
30965
  }
30560
30966
  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]));
30967
+ return wrapSdkError("session.storage", async () => {
30968
+ const snapshot = await this.runtime.getStorageSnapshot(
30969
+ domain === void 0 ? {} : { domain }
30970
+ );
30971
+ const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
30972
+ if (domainSnapshot === void 0) {
30973
+ return {};
30974
+ }
30975
+ const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
30976
+ return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
30977
+ });
30568
30978
  }
30569
30979
  async state(domain) {
30570
- return this.runtime.getBrowserState(domain === void 0 ? {} : { domain });
30980
+ return wrapSdkError(
30981
+ "session.state",
30982
+ () => this.runtime.getBrowserState(domain === void 0 ? {} : { domain })
30983
+ );
30571
30984
  }
30572
30985
  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);
30986
+ return wrapSdkError("session.fetch", async () => {
30987
+ const input = buildFetchInput(url, options);
30988
+ const result = await this.runtime.fetch(input);
30989
+ if (result.response === void 0) {
30990
+ throw new OpensteerProtocolError(
30991
+ "operation-failed",
30992
+ result.note ?? `session.fetch did not produce a response for ${url}`
30993
+ );
30994
+ }
30995
+ return toResponse(result.response);
30996
+ });
30579
30997
  }
30580
30998
  async computerExecute(input) {
30581
- return this.runtime.computerExecute(input);
30999
+ return wrapSdkError("session.computerExecute", () => this.runtime.computerExecute(input));
30582
31000
  }
30583
31001
  async route(input) {
30584
- return this.requireOwnedInstrumentationRuntime("route").route(input);
31002
+ return wrapSdkError(
31003
+ "session.route",
31004
+ () => this.requireOwnedInstrumentationRuntime("route").route(input)
31005
+ );
30585
31006
  }
30586
31007
  async interceptScript(input) {
30587
- return this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input);
31008
+ return wrapSdkError(
31009
+ "session.interceptScript",
31010
+ () => this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input)
31011
+ );
30588
31012
  }
30589
31013
  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;
31014
+ return wrapSdkError("session.close", async () => {
31015
+ if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
31016
+ return this.runtime.close();
31017
+ }
31018
+ const output = await this.runtime.close();
31019
+ await this.browserManager.close();
31020
+ return output;
31021
+ });
30596
31022
  }
30597
31023
  async disconnect() {
30598
- await this.runtime.disconnect();
31024
+ return wrapSdkError("session.disconnect", () => this.runtime.disconnect());
30599
31025
  }
30600
31026
  requireOwnedInstrumentationRuntime(method) {
30601
31027
  if (typeof this.runtime.route === "function" && typeof this.runtime.interceptScript === "function") {
30602
31028
  return this.runtime;
30603
31029
  }
30604
- throw new Error(`${method}() is not available for this session runtime.`);
31030
+ throw new OpensteerProtocolError(
31031
+ "unsupported-operation",
31032
+ `${method}() is not available for this session runtime.`
31033
+ );
30605
31034
  }
30606
31035
  };
30607
31036
  }
@@ -30609,7 +31038,7 @@ var init_opensteer = __esm({
30609
31038
 
30610
31039
  // package.json
30611
31040
  var package_default = {
30612
- version: "0.9.5"};
31041
+ version: "0.9.7"};
30613
31042
 
30614
31043
  // src/cli/bin.ts
30615
31044
  init_browser_manager();
@@ -30624,6 +31053,110 @@ async function loadCliEnvironment(cwd) {
30624
31053
  loadEnvironment(cwd);
30625
31054
  }
30626
31055
 
31056
+ // src/cli/errors.ts
31057
+ init_src();
31058
+ init_src2();
31059
+ var CliError = class extends Error {
31060
+ type;
31061
+ usage;
31062
+ constructor(type, message, usage) {
31063
+ super(message);
31064
+ this.name = "CliError";
31065
+ this.type = type;
31066
+ this.usage = usage;
31067
+ }
31068
+ format() {
31069
+ return this.usage === void 0 ? this.message : `${this.message}
31070
+ Usage: ${this.usage}`;
31071
+ }
31072
+ };
31073
+ function isCliError(value) {
31074
+ return value instanceof CliError;
31075
+ }
31076
+ function formatCliErrorOutput(error) {
31077
+ if (isCliError(error)) {
31078
+ return {
31079
+ success: false,
31080
+ error: error.format(),
31081
+ type: error.type
31082
+ };
31083
+ }
31084
+ if (isOpensteerProtocolError(error)) {
31085
+ return {
31086
+ success: false,
31087
+ error: error.message,
31088
+ code: error.code,
31089
+ retriable: error.retriable
31090
+ };
31091
+ }
31092
+ if (isBrowserCoreError(error)) {
31093
+ return {
31094
+ success: false,
31095
+ error: error.message,
31096
+ code: error.code,
31097
+ retriable: error.retriable
31098
+ };
31099
+ }
31100
+ if (error instanceof Error && "opensteerError" in error) {
31101
+ const oe = error.opensteerError;
31102
+ return {
31103
+ success: false,
31104
+ error: oe.message,
31105
+ code: oe.code,
31106
+ retriable: oe.retriable
31107
+ };
31108
+ }
31109
+ if (error instanceof SyntaxError) {
31110
+ return {
31111
+ success: false,
31112
+ error: error.message,
31113
+ type: "invalid_value"
31114
+ };
31115
+ }
31116
+ return {
31117
+ success: false,
31118
+ error: error instanceof Error ? error.message : String(error)
31119
+ };
31120
+ }
31121
+ function emitWarning(warning) {
31122
+ process.stderr.write(`${JSON.stringify({ warning })}
31123
+ `);
31124
+ }
31125
+ var CLI_USAGE_HINTS = {
31126
+ "session.open": "opensteer open <url> [--workspace <id>]",
31127
+ "page.goto": "opensteer goto <url>",
31128
+ "page.evaluate": "opensteer evaluate <script>",
31129
+ "page.add-init-script": "opensteer init-script <script>",
31130
+ "dom.click": "opensteer click <element> --persist <key>",
31131
+ "dom.hover": "opensteer hover <element> --persist <key>",
31132
+ "dom.input": "opensteer input <element> <text> --persist <key>",
31133
+ "dom.scroll": "opensteer scroll <direction> <amount> --persist <key>",
31134
+ "dom.extract": "opensteer extract <template> --persist <key>",
31135
+ "network.detail": "opensteer network detail <recordId>",
31136
+ "session.fetch": "opensteer fetch <url>",
31137
+ "captcha.solve": "opensteer captcha solve --provider <name> --api-key <key>",
31138
+ "scripts.capture": "opensteer scripts capture",
31139
+ "scripts.beautify": "opensteer scripts beautify <artifactId>",
31140
+ "scripts.deobfuscate": "opensteer scripts deobfuscate <artifactId>",
31141
+ "scripts.sandbox": "opensteer scripts sandbox <artifactId>",
31142
+ "interaction.capture": "opensteer interaction capture",
31143
+ "interaction.get": "opensteer interaction get <traceId>",
31144
+ "interaction.replay": "opensteer interaction replay <traceId>",
31145
+ "interaction.diff": "opensteer interaction diff <leftTraceId> <rightTraceId>",
31146
+ "artifact.read": "opensteer artifact read <artifactId>",
31147
+ "computer.click": "opensteer computer click <x> <y>",
31148
+ "computer.type": "opensteer computer type <text>",
31149
+ "computer.key": "opensteer computer key <key>",
31150
+ "computer.scroll": "opensteer computer scroll <x> <y> --dx <n> --dy <n>",
31151
+ "computer.move": "opensteer computer move <x> <y>",
31152
+ "computer.drag": "opensteer computer drag <x1> <y1> <x2> <y2>",
31153
+ "computer.screenshot": "opensteer computer screenshot",
31154
+ exec: "opensteer exec <expression>",
31155
+ record: "opensteer record --url <url> --workspace <id>",
31156
+ "browser.inspect": "opensteer browser inspect <endpoint>",
31157
+ "browser.clone": "opensteer browser clone --source-user-data-dir <path>"
31158
+ };
31159
+
30627
31160
  // src/cli/help.ts
30628
31161
  function getHelpText() {
30629
31162
  return `Opensteer CLI
@@ -30885,7 +31418,7 @@ function parseCommandLine(argv) {
30885
31418
  const key = token.slice(2, separator === -1 ? void 0 : separator);
30886
31419
  const spec = CLI_OPTION_SPECS[key];
30887
31420
  if (spec === void 0) {
30888
- throw new Error(`Unknown option: --${key}.`);
31421
+ throw new CliError("unknown_option", `Unknown option: --${key}.`);
30889
31422
  }
30890
31423
  if (separator !== -1) {
30891
31424
  rawOptions.set(key, [...rawOptions.get(key) ?? [], token.slice(separator + 1)]);
@@ -30914,7 +31447,8 @@ function parseCommandLine(argv) {
30914
31447
  continue;
30915
31448
  }
30916
31449
  if (next === void 0 || next.startsWith("--")) {
30917
- throw new Error(
31450
+ throw new CliError(
31451
+ "missing_arguments",
30918
31452
  `Option "--${key}" requires a value.${next?.startsWith("--") === true ? ` Use "--${key}=<value>" when the value begins with "--".` : ``}`
30919
31453
  );
30920
31454
  }
@@ -30960,10 +31494,14 @@ function parseCommandLine(argv) {
30960
31494
  const autoLocalView = readOptionalBoolean(rawOptions, "auto");
30961
31495
  const noAutoLocalView = readOptionalBoolean(rawOptions, "no-auto");
30962
31496
  if (autoLocalView === true && noAutoLocalView === true) {
30963
- throw new Error('Options "--auto" and "--no-auto" cannot be combined.');
31497
+ throw new CliError("invalid_option", 'Options "--auto" and "--no-auto" cannot be combined.');
30964
31498
  }
30965
31499
  if (command[0] !== "view" && (autoLocalView !== void 0 || noAutoLocalView !== void 0)) {
30966
- throw new Error('Options "--auto" and "--no-auto" are only supported with "view".');
31500
+ throw new CliError(
31501
+ "invalid_option",
31502
+ 'Options "--auto" and "--no-auto" are only supported with "view".',
31503
+ "opensteer view --auto"
31504
+ );
30967
31505
  }
30968
31506
  const global = readOptionalBoolean(rawOptions, "global");
30969
31507
  const yes = readOptionalBoolean(rawOptions, "yes");
@@ -31019,7 +31557,7 @@ function parseKeyValueList(values) {
31019
31557
  values.map((entry) => {
31020
31558
  const separator = entry.indexOf("=");
31021
31559
  if (separator <= 0) {
31022
- throw new Error(`Expected NAME=VALUE, received "${entry}".`);
31560
+ throw new CliError("invalid_value", `Expected NAME=VALUE, received "${entry}".`);
31023
31561
  }
31024
31562
  return [entry.slice(0, separator), entry.slice(separator + 1)];
31025
31563
  })
@@ -31050,7 +31588,7 @@ function readOptionalBoolean(options, name) {
31050
31588
  if (value === "false") {
31051
31589
  return false;
31052
31590
  }
31053
- throw new Error(`Option "--${name}" must be true or false.`);
31591
+ throw new CliError("invalid_value", `Option "--${name}" must be true or false.`);
31054
31592
  }
31055
31593
  function readOptionalNumber(options, name) {
31056
31594
  const value = readSingle(options, name);
@@ -31059,13 +31597,20 @@ function readOptionalNumber(options, name) {
31059
31597
  }
31060
31598
  const parsed = Number(value);
31061
31599
  if (!Number.isFinite(parsed)) {
31062
- throw new Error(`Option "--${name}" must be a number.`);
31600
+ throw new CliError("invalid_value", `Option "--${name}" must be a number.`);
31063
31601
  }
31064
31602
  return parsed;
31065
31603
  }
31066
31604
  function readJsonValue(options, name) {
31067
31605
  const value = readSingle(options, name);
31068
- return value === void 0 ? void 0 : JSON.parse(value);
31606
+ if (value === void 0) {
31607
+ return void 0;
31608
+ }
31609
+ try {
31610
+ return JSON.parse(value);
31611
+ } catch {
31612
+ throw new CliError("invalid_value", `Option "--${name}" contains invalid JSON.`);
31613
+ }
31069
31614
  }
31070
31615
  function readJsonObject(options, name) {
31071
31616
  const parsed = readJsonValue(options, name);
@@ -31073,7 +31618,7 @@ function readJsonObject(options, name) {
31073
31618
  return void 0;
31074
31619
  }
31075
31620
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31076
- throw new Error(`Option "--${name}" must be a JSON object.`);
31621
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON object.`);
31077
31622
  }
31078
31623
  return parsed;
31079
31624
  }
@@ -31083,7 +31628,7 @@ function readJsonArray(options, name) {
31083
31628
  return void 0;
31084
31629
  }
31085
31630
  if (!Array.isArray(parsed)) {
31086
- throw new Error(`Option "--${name}" must be a JSON array.`);
31631
+ throw new CliError("invalid_value", `Option "--${name}" must be a JSON array.`);
31087
31632
  }
31088
31633
  return parsed;
31089
31634
  }
@@ -31103,7 +31648,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31103
31648
  case "session.open": {
31104
31649
  const url = parsed.rest[0];
31105
31650
  if (url === void 0) {
31106
- throw new Error("open requires a URL.");
31651
+ throw new CliError("missing_arguments", "open requires a URL.", CLI_USAGE_HINTS[operation]);
31107
31652
  }
31108
31653
  return {
31109
31654
  url,
@@ -31136,7 +31681,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31136
31681
  }
31137
31682
  case "page.goto": {
31138
31683
  if (parsed.rest[0] === void 0) {
31139
- throw new Error("goto requires a URL.");
31684
+ throw new CliError("missing_arguments", "goto requires a URL.", CLI_USAGE_HINTS[operation]);
31140
31685
  }
31141
31686
  const captureNetwork = readSingle(parsed.rawOptions, "capture-network");
31142
31687
  return {
@@ -31148,14 +31693,14 @@ async function buildOperationInput(operation, parsed, runtime) {
31148
31693
  return parsed.rest[0] === void 0 ? {} : { mode: parsed.rest[0] };
31149
31694
  case "page.evaluate":
31150
31695
  if (parsed.rest[0] === void 0) {
31151
- throw new Error("evaluate requires a script.");
31696
+ throw new CliError("missing_arguments", "evaluate requires a script.", CLI_USAGE_HINTS[operation]);
31152
31697
  }
31153
31698
  return {
31154
31699
  script: joinRest(parsed.rest, 0)
31155
31700
  };
31156
31701
  case "page.add-init-script":
31157
31702
  if (parsed.rest[0] === void 0) {
31158
- throw new Error("init-script requires a script.");
31703
+ throw new CliError("missing_arguments", "init-script requires a script.", CLI_USAGE_HINTS[operation]);
31159
31704
  }
31160
31705
  return {
31161
31706
  script: joinRest(parsed.rest, 0)
@@ -31171,7 +31716,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31171
31716
  return buildElementTargetInput(parsed, "hover");
31172
31717
  case "dom.input": {
31173
31718
  if (parsed.rest[1] === void 0) {
31174
- throw new Error("input requires an element number and text.");
31719
+ throw new CliError("missing_arguments", "input requires an element number and text.", CLI_USAGE_HINTS[operation]);
31175
31720
  }
31176
31721
  const pressEnter = readOptionalBoolean(parsed.rawOptions, "press-enter");
31177
31722
  return {
@@ -31202,7 +31747,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31202
31747
  }
31203
31748
  case "dom.extract": {
31204
31749
  if (parsed.rest[0] === void 0) {
31205
- throw new Error("extract requires a template.");
31750
+ throw new CliError("missing_arguments", "extract requires a template.", CLI_USAGE_HINTS[operation]);
31206
31751
  }
31207
31752
  const persist = readPersistKey(parsed, "extract");
31208
31753
  return {
@@ -31238,7 +31783,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31238
31783
  }
31239
31784
  case "network.detail": {
31240
31785
  if (parsed.rest[0] === void 0) {
31241
- throw new Error("network detail requires a record id.");
31786
+ throw new CliError("missing_arguments", "network detail requires a record id.", CLI_USAGE_HINTS[operation]);
31242
31787
  }
31243
31788
  const probeFlag = readOptionalBoolean(parsed.rawOptions, "probe");
31244
31789
  return {
@@ -31249,7 +31794,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31249
31794
  case "session.fetch": {
31250
31795
  const url = parsed.rest[0];
31251
31796
  if (url === void 0) {
31252
- throw new Error("fetch requires a URL.");
31797
+ throw new CliError("missing_arguments", "fetch requires a URL.", CLI_USAGE_HINTS[operation]);
31253
31798
  }
31254
31799
  const bodyJson = readJsonValue(parsed.rawOptions, "body");
31255
31800
  const bodyText = readSingle(parsed.rawOptions, "body-text");
@@ -31257,7 +31802,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31257
31802
  const query = parseKeyValueList(parsed.rawOptions.get("query"));
31258
31803
  const headers = parseKeyValueList(parsed.rawOptions.get("header"));
31259
31804
  if (bodyJson !== void 0 && bodyText !== void 0) {
31260
- throw new Error('Use either "--body" or "--body-text", not both.');
31805
+ throw new CliError("invalid_option", 'Use either "--body" or "--body-text", not both.');
31261
31806
  }
31262
31807
  const transport = readSingle(parsed.rawOptions, "transport");
31263
31808
  const cookies = readOptionalBoolean(parsed.rawOptions, "cookies");
@@ -31288,7 +31833,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31288
31833
  const siteKey = readSingle(parsed.rawOptions, "site-key");
31289
31834
  const pageUrl = readSingle(parsed.rawOptions, "page-url");
31290
31835
  if (provider === void 0 || apiKey === void 0) {
31291
- throw new Error('captcha solve requires "--provider" and "--api-key".');
31836
+ throw new CliError("missing_arguments", 'captcha solve requires "--provider" and "--api-key".', CLI_USAGE_HINTS[operation]);
31292
31837
  }
31293
31838
  return {
31294
31839
  provider: readCaptchaProvider(provider),
@@ -31318,7 +31863,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31318
31863
  case "scripts.beautify":
31319
31864
  case "scripts.deobfuscate": {
31320
31865
  if (parsed.rest[0] === void 0) {
31321
- throw new Error(`${parsed.command.join(" ")} requires an artifact id.`);
31866
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires an artifact id.`, CLI_USAGE_HINTS[operation]);
31322
31867
  }
31323
31868
  const persist = readOptionalBoolean(parsed.rawOptions, "persist");
31324
31869
  return {
@@ -31328,7 +31873,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31328
31873
  }
31329
31874
  case "scripts.sandbox":
31330
31875
  if (parsed.rest[0] === void 0) {
31331
- throw new Error("scripts sandbox requires an artifact id.");
31876
+ throw new CliError("missing_arguments", "scripts sandbox requires an artifact id.", CLI_USAGE_HINTS[operation]);
31332
31877
  }
31333
31878
  {
31334
31879
  const fidelity = readSingle(parsed.rawOptions, "fidelity");
@@ -31377,14 +31922,14 @@ async function buildOperationInput(operation, parsed, runtime) {
31377
31922
  case "interaction.get":
31378
31923
  case "interaction.replay":
31379
31924
  if (parsed.rest[0] === void 0) {
31380
- throw new Error(`${parsed.command.join(" ")} requires a trace id.`);
31925
+ throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires a trace id.`, CLI_USAGE_HINTS[operation]);
31381
31926
  }
31382
31927
  return {
31383
31928
  traceId: parsed.rest[0]
31384
31929
  };
31385
31930
  case "interaction.diff":
31386
31931
  if (parsed.rest[0] === void 0 || parsed.rest[1] === void 0) {
31387
- throw new Error("interaction diff requires two trace ids.");
31932
+ throw new CliError("missing_arguments", "interaction diff requires two trace ids.", CLI_USAGE_HINTS[operation]);
31388
31933
  }
31389
31934
  return {
31390
31935
  leftTraceId: parsed.rest[0],
@@ -31392,7 +31937,7 @@ async function buildOperationInput(operation, parsed, runtime) {
31392
31937
  };
31393
31938
  case "artifact.read":
31394
31939
  if (parsed.rest[0] === void 0) {
31395
- throw new Error("artifact read requires an artifact id.");
31940
+ throw new CliError("missing_arguments", "artifact read requires an artifact id.", CLI_USAGE_HINTS[operation]);
31396
31941
  }
31397
31942
  return {
31398
31943
  artifactId: parsed.rest[0]
@@ -31400,7 +31945,8 @@ async function buildOperationInput(operation, parsed, runtime) {
31400
31945
  case "session.close":
31401
31946
  return {};
31402
31947
  default:
31403
- throw new Error(
31948
+ throw new CliError(
31949
+ "unsupported_operation",
31404
31950
  `${operation} does not have a direct CLI input shape. Use a supported command or the SDK.`
31405
31951
  );
31406
31952
  }
@@ -31447,7 +31993,7 @@ function buildComputerExecuteInput(parsed) {
31447
31993
  }
31448
31994
  case "type":
31449
31995
  if (parsed.rest[0] === void 0) {
31450
- throw new Error("computer type requires text.");
31996
+ throw new CliError("missing_arguments", "computer type requires text.");
31451
31997
  }
31452
31998
  return {
31453
31999
  action: {
@@ -31458,7 +32004,7 @@ function buildComputerExecuteInput(parsed) {
31458
32004
  };
31459
32005
  case "key": {
31460
32006
  if (parsed.rest[0] === void 0) {
31461
- throw new Error("computer key requires a key.");
32007
+ throw new CliError("missing_arguments", "computer key requires a key.");
31462
32008
  }
31463
32009
  const modifiers = readKeyModifiers(readSingle(parsed.rawOptions, "modifiers"));
31464
32010
  return {
@@ -31474,7 +32020,7 @@ function buildComputerExecuteInput(parsed) {
31474
32020
  const dx = readOptionalNumber(parsed.rawOptions, "dx");
31475
32021
  const dy = readOptionalNumber(parsed.rawOptions, "dy");
31476
32022
  if (dx === void 0 || dy === void 0) {
31477
- throw new Error('computer scroll requires "--dx" and "--dy".');
32023
+ throw new CliError("missing_arguments", 'computer scroll requires "--dx" and "--dy".');
31478
32024
  }
31479
32025
  return {
31480
32026
  action: {
@@ -31529,49 +32075,50 @@ function buildComputerExecuteInput(parsed) {
31529
32075
  }
31530
32076
  };
31531
32077
  default:
31532
- throw new Error(`Unknown computer command: ${parsed.command.join(" ")}`);
32078
+ throw new CliError("unknown_command", `Unknown computer command: ${parsed.command.join(" ")}`);
31533
32079
  }
31534
32080
  }
31535
32081
  async function resolvePageRefByIndex(runtime, index) {
31536
32082
  const pages = (await runtime.listPages({})).pages;
31537
32083
  const page = pages[index - 1];
31538
32084
  if (page === void 0) {
31539
- throw new Error(`tab ${String(index)} does not exist.`);
32085
+ throw new CliError("invalid_value", `tab ${String(index)} does not exist.`);
31540
32086
  }
31541
32087
  return page.pageRef;
31542
32088
  }
31543
32089
  function readRequiredPositiveInteger(value, message) {
31544
32090
  const parsed = readRequiredNumber(value, message);
31545
32091
  if (!Number.isInteger(parsed) || parsed < 1) {
31546
- throw new Error(message);
32092
+ throw new CliError("missing_arguments", message);
31547
32093
  }
31548
32094
  return parsed;
31549
32095
  }
31550
32096
  function readRequiredNumber(value, message) {
31551
32097
  if (value === void 0) {
31552
- throw new Error(message);
32098
+ throw new CliError("missing_arguments", message);
31553
32099
  }
31554
32100
  const parsed = Number(value);
31555
32101
  if (!Number.isFinite(parsed)) {
31556
- throw new Error(message);
32102
+ throw new CliError("missing_arguments", message);
31557
32103
  }
31558
32104
  return parsed;
31559
32105
  }
31560
32106
  function readSingleDirection(value) {
31561
32107
  if (value === void 0 || !SCROLL_DIRECTIONS.has(value)) {
31562
- throw new Error("scroll requires a direction: up, down, left, or right.");
32108
+ throw new CliError("missing_arguments", "scroll requires a direction: up, down, left, or right.");
31563
32109
  }
31564
32110
  return value;
31565
32111
  }
31566
32112
  function readClickButton(value) {
31567
32113
  if (value === void 0 || !CLICK_BUTTONS.has(value)) {
31568
- throw new Error('Expected "--button" to be one of: left, middle, right.');
32114
+ throw new CliError("invalid_value", 'Expected "--button" to be one of: left, middle, right.');
31569
32115
  }
31570
32116
  return value;
31571
32117
  }
31572
32118
  function readFetchTransport(value) {
31573
32119
  if (value === void 0 || !FETCH_TRANSPORTS.has(value)) {
31574
- throw new Error(
32120
+ throw new CliError(
32121
+ "invalid_value",
31575
32122
  'Expected "--transport" to be one of: auto, direct, matched-tls, context, page.'
31576
32123
  );
31577
32124
  }
@@ -31579,31 +32126,31 @@ function readFetchTransport(value) {
31579
32126
  }
31580
32127
  function readCaptchaProvider(value) {
31581
32128
  if (value === void 0 || !CAPTCHA_PROVIDERS.has(value)) {
31582
- throw new Error('Expected "--provider" to be one of: 2captcha, capsolver.');
32129
+ throw new CliError("invalid_value", 'Expected "--provider" to be one of: 2captcha, capsolver.');
31583
32130
  }
31584
32131
  return value;
31585
32132
  }
31586
32133
  function readCaptchaType(value) {
31587
32134
  if (value === void 0 || !CAPTCHA_TYPES.has(value)) {
31588
- throw new Error('Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
32135
+ throw new CliError("invalid_value", 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
31589
32136
  }
31590
32137
  return value;
31591
32138
  }
31592
32139
  function readSandboxFidelity(value) {
31593
32140
  if (value === void 0 || !SANDBOX_FIDELITIES.has(value)) {
31594
- throw new Error('Expected "--fidelity" to be one of: minimal, standard, full.');
32141
+ throw new CliError("invalid_value", 'Expected "--fidelity" to be one of: minimal, standard, full.');
31595
32142
  }
31596
32143
  return value;
31597
32144
  }
31598
32145
  function readSandboxClockMode(value) {
31599
32146
  if (value === void 0 || !SANDBOX_CLOCK_MODES.has(value)) {
31600
- throw new Error('Expected "--clock" to be one of: real, manual.');
32147
+ throw new CliError("invalid_value", 'Expected "--clock" to be one of: real, manual.');
31601
32148
  }
31602
32149
  return value;
31603
32150
  }
31604
32151
  function readScreenshotFormat(value) {
31605
32152
  if (value === void 0 || !SCREENSHOT_FORMATS.has(value)) {
31606
- throw new Error('Expected "--format" to be one of: png, jpeg, webp.');
32153
+ throw new CliError("invalid_value", 'Expected "--format" to be one of: png, jpeg, webp.');
31607
32154
  }
31608
32155
  return value;
31609
32156
  }
@@ -31614,7 +32161,7 @@ function readKeyModifiers(value) {
31614
32161
  }
31615
32162
  for (const modifier of modifiers) {
31616
32163
  if (!KEY_MODIFIERS.has(modifier)) {
31617
- throw new Error('Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
32164
+ throw new CliError("invalid_value", 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
31618
32165
  }
31619
32166
  }
31620
32167
  return [...new Set(modifiers)];
@@ -31622,17 +32169,22 @@ function readKeyModifiers(value) {
31622
32169
  function readPersistKey(parsed, verb) {
31623
32170
  const value = readSingle(parsed.rawOptions, "persist");
31624
32171
  if (value === void 0) {
31625
- throw new Error(`${verb} requires "--persist <key>".`);
32172
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31626
32173
  }
31627
32174
  if (value === "true" || value === "false") {
31628
- throw new Error(`${verb} requires "--persist <key>".`);
32175
+ throw new CliError("missing_arguments", `${verb} requires "--persist <key>".`);
31629
32176
  }
31630
32177
  return value;
31631
32178
  }
31632
32179
  function parseRequiredJsonObjectArgument(value, label) {
31633
- const parsed = JSON.parse(value);
32180
+ let parsed;
32181
+ try {
32182
+ parsed = JSON.parse(value);
32183
+ } catch {
32184
+ throw new CliError("invalid_value", `${label} contains invalid JSON.`);
32185
+ }
31634
32186
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31635
- throw new Error(`${label} must be a JSON object.`);
32187
+ throw new CliError("invalid_value", `${label} must be a JSON object.`);
31636
32188
  }
31637
32189
  return parsed;
31638
32190
  }
@@ -31644,6 +32196,7 @@ function joinRest(rest, startIndex) {
31644
32196
  function renderOperationOutput(operation, result, input) {
31645
32197
  switch (operation) {
31646
32198
  case "session.open":
32199
+ case "page.activate":
31647
32200
  case "page.goto":
31648
32201
  return renderJson(formatNavigationOutput(result));
31649
32202
  case "page.snapshot":
@@ -31652,14 +32205,14 @@ function renderOperationOutput(operation, result, input) {
31652
32205
  case "dom.hover":
31653
32206
  case "dom.input":
31654
32207
  case "dom.scroll":
31655
- return renderJson(formatActionOutput(result, input));
32208
+ return renderJson(formatActionOutput(result));
31656
32209
  case "dom.extract":
31657
32210
  return renderJson(formatExtractOutput(result));
31658
32211
  case "page.list":
31659
- case "page.new":
31660
- case "page.activate":
31661
32212
  case "page.close":
31662
32213
  return formatTabOutput(result);
32214
+ case "page.new":
32215
+ return renderJson(formatCreatedPageOutput(result));
31663
32216
  case "network.query":
31664
32217
  return formatNetworkQueryOutput(result, input);
31665
32218
  case "network.detail":
@@ -31697,6 +32250,12 @@ function formatNavigationOutput(result) {
31697
32250
  ...readStringField(result, "title") === void 0 ? {} : { title: readStringField(result, "title") }
31698
32251
  };
31699
32252
  }
32253
+ function formatCreatedPageOutput(result) {
32254
+ return {
32255
+ ...readStringField(result, "pageRef") === void 0 ? {} : { pageRef: readStringField(result, "pageRef") },
32256
+ ...formatNavigationOutput(result)
32257
+ };
32258
+ }
31700
32259
  function formatSnapshotOutput(result) {
31701
32260
  if (result !== null && typeof result === "object" && typeof result.html === "string") {
31702
32261
  return `${result.html}
@@ -31704,36 +32263,11 @@ function formatSnapshotOutput(result) {
31704
32263
  }
31705
32264
  return renderJson(result);
31706
32265
  }
31707
- function formatActionOutput(result, input) {
31708
- const target = readObjectField(result, "target");
31709
- const output = {
31710
- ...readStringField(target, "tagName") === void 0 ? {} : { tagName: readStringField(target, "tagName") },
31711
- ...readStringField(target, "pathHint") === void 0 ? {} : { pathHint: readStringField(target, "pathHint") }
32266
+ function formatActionOutput(result) {
32267
+ return {
32268
+ ...readStringField(result, "tagName") === void 0 ? {} : { tagName: readStringField(result, "tagName") },
32269
+ ...readStringField(result, "persist") === void 0 ? {} : { persist: readStringField(result, "persist") }
31712
32270
  };
31713
- const point = readObjectField(result, "point");
31714
- if (point !== void 0) {
31715
- output.point = {
31716
- ...readNumberField(point, "x") === void 0 ? {} : { x: readNumberField(point, "x") },
31717
- ...readNumberField(point, "y") === void 0 ? {} : { y: readNumberField(point, "y") }
31718
- };
31719
- }
31720
- const persist = readStringField(target, "persist");
31721
- if (persist !== void 0) {
31722
- output.persist = persist;
31723
- }
31724
- const text = readStringField(input, "text");
31725
- if (text !== void 0) {
31726
- output.text = text;
31727
- }
31728
- const direction = readStringField(input, "direction");
31729
- if (direction !== void 0) {
31730
- output.direction = direction;
31731
- }
31732
- const amount = readNumberField(input, "amount");
31733
- if (amount !== void 0) {
31734
- output.amount = amount;
31735
- }
31736
- return output;
31737
32271
  }
31738
32272
  function formatExtractOutput(result) {
31739
32273
  const data = readUnknownField(result, "data");
@@ -31965,20 +32499,18 @@ function formatStateOutput(result) {
31965
32499
  `;
31966
32500
  }
31967
32501
  function formatComputerOutput(result) {
31968
- const action = readObjectField(result, "action");
31969
32502
  const screenshot = readObjectField(result, "screenshot");
31970
32503
  const payload = readObjectField(screenshot, "payload");
31971
- const timing = readObjectField(result, "timing");
31972
32504
  return {
31973
- ...action === void 0 ? {} : { action },
32505
+ ...readStringField(result, "url") === void 0 ? {} : { url: readStringField(result, "url") },
32506
+ ...readStringField(result, "title") === void 0 ? {} : { title: readStringField(result, "title") },
31974
32507
  ...payload === void 0 ? {} : {
31975
32508
  screenshot: {
31976
32509
  ...readStringField(payload, "uri") === void 0 ? {} : { uri: readStringField(payload, "uri") },
31977
32510
  ...readStringField(screenshot, "format") === void 0 ? {} : { format: readStringField(screenshot, "format") },
31978
32511
  ...readObjectField(screenshot, "size") === void 0 ? {} : { size: readObjectField(screenshot, "size") }
31979
32512
  }
31980
- },
31981
- ...timing === void 0 ? {} : { timingMs: timing }
32513
+ }
31982
32514
  };
31983
32515
  }
31984
32516
  function formatScriptsCaptureOutput(result) {
@@ -32640,7 +33172,7 @@ async function collectOpensteerStatus(input) {
32640
33172
  ...output,
32641
33173
  rootPath,
32642
33174
  lanes: {
32643
- local: describeLocalLane(localRecord, input.provider.mode === "local"),
33175
+ local: await describeLocalLane(localRecord, input.provider.mode === "local"),
32644
33176
  cloud: await describeCloudLane({
32645
33177
  record: cloudRecord,
32646
33178
  current: input.provider.mode === "cloud",
@@ -32692,8 +33224,38 @@ async function readWorkspaceCloudRecord(rootPath) {
32692
33224
  }
32693
33225
  return readPersistedCloudSessionRecord(rootPath);
32694
33226
  }
32695
- function describeLocalLane(record, current) {
32696
- if (record === void 0 || !isProcessRunning(record.pid)) {
33227
+ async function describeLocalLane(record, current) {
33228
+ if (record === void 0) {
33229
+ return {
33230
+ provider: "local",
33231
+ status: "idle",
33232
+ current,
33233
+ summary: "none"
33234
+ };
33235
+ }
33236
+ if (getPersistedLocalBrowserSessionOwnership(record) === "attached") {
33237
+ if (!await isAttachedLocalBrowserSessionReachable(record)) {
33238
+ return {
33239
+ provider: "local",
33240
+ status: "stale",
33241
+ current,
33242
+ summary: "attached browser unavailable",
33243
+ detail: record.engine,
33244
+ engine: record.engine
33245
+ };
33246
+ }
33247
+ const browser2 = record.executablePath ? path10__default.default.basename(record.executablePath).replace(/\.[A-Za-z0-9]+$/u, "") : void 0;
33248
+ return {
33249
+ provider: "local",
33250
+ status: "active",
33251
+ current,
33252
+ summary: "attached browser",
33253
+ detail: browser2 ?? record.engine,
33254
+ engine: record.engine,
33255
+ ...browser2 === void 0 ? {} : { browser: browser2 }
33256
+ };
33257
+ }
33258
+ if (!isProcessRunning(record.pid)) {
32697
33259
  return {
32698
33260
  provider: "local",
32699
33261
  status: "idle",
@@ -32844,13 +33406,13 @@ async function resolveSessionSummary(manifest) {
32844
33406
  return {
32845
33407
  sessionId: manifest.sessionId,
32846
33408
  label: manifest.workspace ?? (path10__default.default.basename(manifest.rootPath) || manifest.sessionId),
32847
- status: isProcessRunning(record.pid) ? "live" : "stale",
33409
+ status: getPersistedLocalBrowserSessionOwnership(record) === "attached" || isProcessRunning(record.pid) ? "live" : "stale",
32848
33410
  ...manifest.workspace === void 0 ? {} : { workspace: manifest.workspace },
32849
33411
  rootPath: manifest.rootPath,
32850
33412
  engine: record.engine,
32851
33413
  ownership: manifest.ownership,
32852
- pid: record.pid,
32853
33414
  startedAt: record.startedAt,
33415
+ ...record.pid > 0 ? { pid: record.pid } : {},
32854
33416
  ...browserName === void 0 ? {} : { browserName }
32855
33417
  };
32856
33418
  }
@@ -32878,7 +33440,16 @@ async function readLiveRecord(manifest) {
32878
33440
  if (!record) {
32879
33441
  return void 0;
32880
33442
  }
32881
- if (record.pid !== manifest.pid || record.startedAt !== manifest.startedAt || !isProcessRunning(record.pid)) {
33443
+ if (buildLocalViewSessionIdForRecord({
33444
+ rootPath: manifest.rootPath,
33445
+ live: record
33446
+ }) !== manifest.sessionId || record.startedAt !== manifest.startedAt || record.engine !== manifest.engine || getPersistedLocalBrowserSessionOwnership(record) !== manifest.ownership) {
33447
+ return void 0;
33448
+ }
33449
+ if (getPersistedLocalBrowserSessionOwnership(record) === "attached") {
33450
+ return await isAttachedLocalBrowserSessionReachable(record) ? record : void 0;
33451
+ }
33452
+ if (record.pid !== manifest.pid || !isProcessRunning(record.pid)) {
32882
33453
  return void 0;
32883
33454
  }
32884
33455
  return record;
@@ -34610,7 +35181,7 @@ async function handleViewCommand(parsed, options = {}) {
34610
35181
  return;
34611
35182
  }
34612
35183
  if (subcommand !== void 0) {
34613
- throw new Error(`Unknown view command: view ${subcommand}`);
35184
+ throw new CliError("unknown_command", `Unknown view command: view ${subcommand}`);
34614
35185
  }
34615
35186
  if (parsed.options.localViewMode !== void 0) {
34616
35187
  const preference = await setLocalViewMode(parsed.options.localViewMode);
@@ -34647,18 +35218,27 @@ async function resolveWorkspaceSessionId(input) {
34647
35218
  workspace: input.workspace
34648
35219
  });
34649
35220
  const live = await readPersistedLocalBrowserSessionRecord(rootPath);
34650
- if (!live || !isProcessRunning(live.pid)) {
35221
+ if (!live) {
34651
35222
  return void 0;
34652
35223
  }
34653
- return buildLocalViewSessionId({
35224
+ if (getPersistedLocalBrowserSessionOwnership(live) === "attached") {
35225
+ if (!await isAttachedLocalBrowserSessionReachable(live)) {
35226
+ return void 0;
35227
+ }
35228
+ } else if (!isProcessRunning(live.pid)) {
35229
+ return void 0;
35230
+ }
35231
+ return buildLocalViewSessionIdForRecord({
34654
35232
  rootPath,
34655
- pid: live.pid,
34656
- startedAt: live.startedAt
35233
+ live
34657
35234
  });
34658
35235
  }
34659
35236
  function assertNoViewPreferenceFlag(parsed) {
34660
35237
  if (parsed.options.localViewMode !== void 0) {
34661
- throw new Error("View preference flags cannot be combined with this subcommand.");
35238
+ throw new CliError(
35239
+ "invalid_option",
35240
+ "View preference flags cannot be combined with this subcommand."
35241
+ );
34662
35242
  }
34663
35243
  }
34664
35244
  function writeViewOutput(parsed, value) {
@@ -34743,10 +35323,10 @@ async function main() {
34743
35323
  }
34744
35324
  const operation = resolveOperation(parsed.command);
34745
35325
  if (!operation) {
34746
- throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
35326
+ throw new CliError("unknown_command", `Unknown command: ${parsed.command.join(" ")}`);
34747
35327
  }
34748
35328
  if (parsed.options.workspace === void 0) {
34749
- throw new Error('Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
35329
+ throw new CliError("missing_workspace", 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
34750
35330
  }
34751
35331
  const { engineName, provider, runtimeProvider } = resolveCliRuntimeSelection(parsed);
34752
35332
  if (operation === "session.close") {
@@ -34768,24 +35348,39 @@ async function main() {
34768
35348
  let renderOperation = operation;
34769
35349
  try {
34770
35350
  const input = await buildOperationInput(operation, parsed, runtime);
34771
- result = await dispatchSemanticOperation(runtime, operation, input);
35351
+ const rawResult = await dispatchSemanticOperation(runtime, operation, input);
34772
35352
  if (parsed.command[0] === "tab" && operation !== "page.list") {
34773
35353
  renderOperation = "page.list";
34774
35354
  result = await runtime.listPages({});
35355
+ } else {
35356
+ result = rawResult;
35357
+ }
35358
+ let warning;
35359
+ if (result !== null && typeof result === "object" && "warning" in result) {
35360
+ const { warning: w, ...rest } = result;
35361
+ if (typeof w === "string") {
35362
+ warning = w;
35363
+ result = rest;
35364
+ }
34775
35365
  }
34776
35366
  process4__default.default.stdout.write(renderOperationOutput(renderOperation, result, input));
35367
+ if (warning !== void 0) {
35368
+ emitWarning(warning);
35369
+ }
34777
35370
  } finally {
34778
35371
  await runtime.disconnect().catch(() => void 0);
34779
35372
  }
34780
35373
  }
34781
35374
  async function handleExecCommand(parsed) {
34782
35375
  if (parsed.options.workspace === void 0) {
34783
- throw new Error('exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35376
+ throw new CliError("missing_workspace", 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34784
35377
  }
34785
35378
  const expression = parsed.rest.join(" ");
34786
35379
  if (!expression) {
34787
- throw new Error(
34788
- `exec requires an expression. Example: exec "await this.evaluate('document.title')"`
35380
+ throw new CliError(
35381
+ "missing_arguments",
35382
+ "exec requires an expression.",
35383
+ "opensteer exec <expression>"
34789
35384
  );
34790
35385
  }
34791
35386
  const { engineName, runtimeProvider } = resolveCliRuntimeSelection(parsed);
@@ -34821,8 +35416,10 @@ async function handleBrowserCommand(parsed) {
34821
35416
  if (subcommand === "inspect") {
34822
35417
  const endpoint = parsed.options.attachEndpoint ?? parsed.rest[0];
34823
35418
  if (!endpoint) {
34824
- throw new Error(
34825
- 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.'
35419
+ throw new CliError(
35420
+ "missing_arguments",
35421
+ 'browser inspect requires "--attach-endpoint <url>" or a positional endpoint.',
35422
+ "opensteer browser inspect <endpoint>"
34826
35423
  );
34827
35424
  }
34828
35425
  const result = await inspectCdpEndpoint({
@@ -34835,7 +35432,8 @@ async function handleBrowserCommand(parsed) {
34835
35432
  return;
34836
35433
  }
34837
35434
  if (parsed.options.workspace === void 0) {
34838
- throw new Error(
35435
+ throw new CliError(
35436
+ "missing_workspace",
34839
35437
  'Browser workspace commands require "--workspace <id>" or OPENSTEER_WORKSPACE.'
34840
35438
  );
34841
35439
  }
@@ -34858,7 +35456,11 @@ async function handleBrowserCommand(parsed) {
34858
35456
  case "clone": {
34859
35457
  const sourceUserDataDir = parsed.options.sourceUserDataDir;
34860
35458
  if (!sourceUserDataDir) {
34861
- throw new Error('browser clone requires "--source-user-data-dir <path>".');
35459
+ throw new CliError(
35460
+ "missing_arguments",
35461
+ 'browser clone requires "--source-user-data-dir <path>".',
35462
+ "opensteer browser clone --source-user-data-dir <path>"
35463
+ );
34862
35464
  }
34863
35465
  const result = await manager.clonePersistentBrowser({
34864
35466
  sourceUserDataDir,
@@ -34881,7 +35483,7 @@ async function handleBrowserCommand(parsed) {
34881
35483
  return;
34882
35484
  }
34883
35485
  default:
34884
- throw new Error(`Unknown browser command: ${parsed.command.join(" ")}`);
35486
+ throw new CliError("unknown_command", `Unknown browser command: ${parsed.command.join(" ")}`);
34885
35487
  }
34886
35488
  }
34887
35489
  async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvider) {
@@ -34914,23 +35516,27 @@ async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvi
34914
35516
  }
34915
35517
  async function handleRecordCommandEntry(parsed) {
34916
35518
  if (parsed.options.workspace === void 0) {
34917
- throw new Error('record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35519
+ throw new CliError("missing_workspace", 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
34918
35520
  }
34919
35521
  const url = parsed.options.url ?? parsed.rest[0];
34920
35522
  if (url === void 0) {
34921
- throw new Error('record requires "--url <value>" or a positional URL.');
35523
+ throw new CliError(
35524
+ "missing_arguments",
35525
+ 'record requires "--url <value>" or a positional URL.',
35526
+ "opensteer record --url <url> --workspace <id>"
35527
+ );
34922
35528
  }
34923
35529
  const provider = resolveCliProvider(parsed);
34924
35530
  assertCloudCliOptionsMatchProvider(parsed, provider.mode);
34925
35531
  const engineName = resolveCliEngineName(parsed);
34926
35532
  if (engineName !== "playwright") {
34927
- throw new Error("record requires engine=playwright.");
35533
+ throw new CliError("config_conflict", "record requires engine=playwright.");
34928
35534
  }
34929
35535
  const rootDir = process4__default.default.cwd();
34930
35536
  const recordBrowser = parsed.options.browser;
34931
35537
  if (provider.mode === "cloud") {
34932
35538
  if (typeof recordBrowser === "object") {
34933
- throw new Error('record does not support browser.mode="attach".');
35539
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34934
35540
  }
34935
35541
  const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
34936
35542
  const runtimeConfig = resolveOpensteerRuntimeConfig({
@@ -34950,10 +35556,10 @@ async function handleRecordCommandEntry(parsed) {
34950
35556
  return;
34951
35557
  }
34952
35558
  if (parsed.options.launch?.headless === true) {
34953
- throw new Error('record requires a headed browser. Remove "--headless true".');
35559
+ throw new CliError("config_conflict", 'record requires a headed browser. Remove "--headless true".');
34954
35560
  }
34955
35561
  if (typeof recordBrowser === "object") {
34956
- throw new Error('record does not support browser.mode="attach".');
35562
+ throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
34957
35563
  }
34958
35564
  const launch = {
34959
35565
  ...parsed.options.launch ?? {},
@@ -35022,7 +35628,7 @@ function resolveCliBootstrapAction(argv) {
35022
35628
  }
35023
35629
  function buildCliBrowserProfile(parsed) {
35024
35630
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
35025
- throw new Error('"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
35631
+ throw new CliError("invalid_option", '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
35026
35632
  }
35027
35633
  return parsed.options.cloudProfileId === void 0 ? void 0 : {
35028
35634
  profileId: parsed.options.cloudProfileId,
@@ -35083,7 +35689,8 @@ function buildCliRuntimeProvider(parsed, providerMode) {
35083
35689
  }
35084
35690
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
35085
35691
  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(
35692
+ throw new CliError(
35693
+ "config_conflict",
35087
35694
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
35088
35695
  );
35089
35696
  }
@@ -35117,17 +35724,7 @@ function printVersion() {
35117
35724
  `);
35118
35725
  }
35119
35726
  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
- };
35727
+ const payload = formatCliErrorOutput(error);
35131
35728
  process4__default.default.stderr.write(`${JSON.stringify(payload)}
35132
35729
  `);
35133
35730
  process4__default.default.exitCode = 1;