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/index.cjs CHANGED
@@ -102,6 +102,11 @@ function stableJsonString(value) {
102
102
 
103
103
  // ../runtime-core/src/internal/filesystem.ts
104
104
  var LOCK_RETRY_DELAYS_MS = [1, 2, 5, 10, 20, 50];
105
+ var LOCK_METADATA_FILENAME = "owner.json";
106
+ var LOCK_METADATA_VERSION = 1;
107
+ var LOCK_HEARTBEAT_INTERVAL_MS = 1e3;
108
+ var LOCK_ORPHAN_GRACE_MS = 2e3;
109
+ var LOCK_METADATALESS_STALE_MS = 3e4;
105
110
  function normalizeNonEmptyString(name, value) {
106
111
  const normalized = value.trim();
107
112
  if (normalized.length === 0) {
@@ -207,24 +212,119 @@ function isAlreadyExistsError(error) {
207
212
  }
208
213
  async function withFilesystemLock(lockPath, task) {
209
214
  await ensureDirectory(path10__default.default.dirname(lockPath));
215
+ const ownerToken = crypto.randomUUID();
210
216
  let attempt = 0;
211
217
  while (true) {
212
218
  try {
213
219
  await promises.mkdir(lockPath);
220
+ const acquiredAt = Date.now();
221
+ await writeLockMetadata(lockPath, {
222
+ version: LOCK_METADATA_VERSION,
223
+ ownerToken,
224
+ pid: process.pid,
225
+ acquiredAt,
226
+ heartbeatAt: acquiredAt
227
+ });
214
228
  break;
215
229
  } catch (error) {
216
230
  if (!isAlreadyExistsError(error)) {
217
231
  throw error;
218
232
  }
233
+ if (await tryRecoverFilesystemLock(lockPath)) {
234
+ attempt = 0;
235
+ continue;
236
+ }
219
237
  const delayMs = LOCK_RETRY_DELAYS_MS[Math.min(attempt, LOCK_RETRY_DELAYS_MS.length - 1)];
220
238
  attempt += 1;
221
239
  await new Promise((resolve4) => setTimeout(resolve4, delayMs));
222
240
  }
223
241
  }
242
+ const heartbeatTimer = setInterval(() => {
243
+ void touchLockMetadata(lockPath, ownerToken);
244
+ }, LOCK_HEARTBEAT_INTERVAL_MS);
245
+ heartbeatTimer.unref?.();
224
246
  try {
225
247
  return await task();
226
248
  } finally {
227
- await promises.rm(lockPath, { recursive: true, force: true });
249
+ clearInterval(heartbeatTimer);
250
+ const metadata = await readLockMetadata(lockPath);
251
+ if (metadata?.ownerToken === ownerToken) {
252
+ await promises.rm(lockPath, { recursive: true, force: true });
253
+ }
254
+ }
255
+ }
256
+ async function tryRecoverFilesystemLock(lockPath) {
257
+ if (!await shouldRecoverFilesystemLock(lockPath)) {
258
+ return false;
259
+ }
260
+ await promises.rm(lockPath, { recursive: true, force: true });
261
+ return true;
262
+ }
263
+ async function shouldRecoverFilesystemLock(lockPath) {
264
+ const metadata = await readLockMetadata(lockPath);
265
+ if (metadata !== void 0) {
266
+ if (isProcessRunning(metadata.pid)) {
267
+ return false;
268
+ }
269
+ return Date.now() - metadata.heartbeatAt >= LOCK_ORPHAN_GRACE_MS;
270
+ }
271
+ const lockStat = await promises.stat(lockPath).catch(() => void 0);
272
+ if (lockStat === void 0) {
273
+ return false;
274
+ }
275
+ return Date.now() - lockStat.mtimeMs >= LOCK_METADATALESS_STALE_MS;
276
+ }
277
+ async function readLockMetadata(lockPath) {
278
+ const metadataPath = path10__default.default.join(lockPath, LOCK_METADATA_FILENAME);
279
+ if (!await pathExists(metadataPath)) {
280
+ return void 0;
281
+ }
282
+ try {
283
+ const parsed = await readJsonFile(metadataPath);
284
+ const pid = parsed.pid;
285
+ const acquiredAt = parsed.acquiredAt;
286
+ const heartbeatAt = parsed.heartbeatAt;
287
+ 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)) {
288
+ return void 0;
289
+ }
290
+ return {
291
+ version: LOCK_METADATA_VERSION,
292
+ ownerToken: parsed.ownerToken,
293
+ pid,
294
+ acquiredAt,
295
+ heartbeatAt
296
+ };
297
+ } catch {
298
+ return void 0;
299
+ }
300
+ }
301
+ async function writeLockMetadata(lockPath, metadata) {
302
+ try {
303
+ await writeJsonFileAtomic(path10__default.default.join(lockPath, LOCK_METADATA_FILENAME), metadata);
304
+ } catch (error) {
305
+ await promises.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
306
+ throw error;
307
+ }
308
+ }
309
+ async function touchLockMetadata(lockPath, ownerToken) {
310
+ const metadata = await readLockMetadata(lockPath);
311
+ if (metadata === void 0 || metadata.ownerToken !== ownerToken) {
312
+ return;
313
+ }
314
+ await writeJsonFileAtomic(path10__default.default.join(lockPath, LOCK_METADATA_FILENAME), {
315
+ ...metadata,
316
+ heartbeatAt: Date.now()
317
+ }).catch(() => void 0);
318
+ }
319
+ function isProcessRunning(pid) {
320
+ if (!Number.isInteger(pid) || pid <= 0) {
321
+ return false;
322
+ }
323
+ try {
324
+ process.kill(pid, 0);
325
+ return true;
326
+ } catch (error) {
327
+ return error?.code === "EPERM";
228
328
  }
229
329
  }
230
330
 
@@ -814,7 +914,7 @@ function isJsonValueEqual(expected, actual) {
814
914
 
815
915
  // ../protocol/src/version.ts
816
916
  var OPENSTEER_PROTOCOL_NAME = "opensteer";
817
- var OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 2;
917
+ var OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 3;
818
918
  var OPENSTEER_PROTOCOL_VERSION = `0.${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}.0`;
819
919
  var OPENSTEER_PROTOCOL_REST_BASE_PATH = `/api/v${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}`;
820
920
  objectSchema(
@@ -1037,9 +1137,6 @@ function isBrowserCoreError(value) {
1037
1137
  return value instanceof BrowserCoreError;
1038
1138
  }
1039
1139
 
1040
- // ../browser-core/src/cdp-visual-stability.ts
1041
- var DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
1042
-
1043
1140
  // ../browser-core/src/post-load-tracker.ts
1044
1141
  var DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS = 400;
1045
1142
 
@@ -1191,7 +1288,7 @@ var visualViewportSchema = objectSchema(
1191
1288
  required: ["origin", "offsetWithinLayoutViewport", "size"]
1192
1289
  }
1193
1290
  );
1194
- var viewportMetricsSchema = objectSchema(
1291
+ objectSchema(
1195
1292
  {
1196
1293
  layoutViewport: layoutViewportSchema,
1197
1294
  visualViewport: visualViewportSchema,
@@ -2642,7 +2739,7 @@ var domSnapshotSchema = objectSchema(
2642
2739
  ]
2643
2740
  }
2644
2741
  );
2645
- var hitTestResultSchema = objectSchema(
2742
+ objectSchema(
2646
2743
  {
2647
2744
  inputPoint: pointSchema,
2648
2745
  inputCoordinateSpace: coordinateSpaceSchema,
@@ -4095,39 +4192,14 @@ var opensteerTargetInputSchema = oneOfSchema(
4095
4192
  title: "OpensteerTargetInput"
4096
4193
  }
4097
4194
  );
4098
- var opensteerResolvedTargetSchema = objectSchema(
4099
- {
4100
- pageRef: pageRefSchema,
4101
- frameRef: frameRefSchema,
4102
- documentRef: documentRefSchema,
4103
- documentEpoch: documentEpochSchema,
4104
- nodeRef: nodeRefSchema,
4105
- tagName: stringSchema(),
4106
- pathHint: stringSchema(),
4107
- persist: stringSchema(),
4108
- selectorUsed: stringSchema()
4109
- },
4110
- {
4111
- title: "OpensteerResolvedTarget",
4112
- required: [
4113
- "pageRef",
4114
- "frameRef",
4115
- "documentRef",
4116
- "documentEpoch",
4117
- "nodeRef",
4118
- "tagName",
4119
- "pathHint"
4120
- ]
4121
- }
4122
- );
4123
4195
  var opensteerActionResultSchema = objectSchema(
4124
4196
  {
4125
- target: opensteerResolvedTargetSchema,
4126
- point: pointSchema
4197
+ tagName: stringSchema({ minLength: 1 }),
4198
+ persist: stringSchema({ minLength: 1 })
4127
4199
  },
4128
4200
  {
4129
4201
  title: "OpensteerActionResult",
4130
- required: ["target"]
4202
+ required: ["tagName"]
4131
4203
  }
4132
4204
  );
4133
4205
  var opensteerSnapshotCounterSchema = objectSchema(
@@ -4173,16 +4245,14 @@ var opensteerSnapshotCounterSchema = objectSchema(
4173
4245
  ]
4174
4246
  }
4175
4247
  );
4176
- var opensteerSessionStateSchema = objectSchema(
4248
+ var opensteerNavigationSummarySchema = objectSchema(
4177
4249
  {
4178
- sessionRef: sessionRefSchema,
4179
- pageRef: pageRefSchema,
4180
4250
  url: stringSchema(),
4181
4251
  title: stringSchema()
4182
4252
  },
4183
4253
  {
4184
- title: "OpensteerSessionState",
4185
- required: ["sessionRef", "pageRef", "url", "title"]
4254
+ title: "OpensteerNavigationSummary",
4255
+ required: ["url", "title"]
4186
4256
  }
4187
4257
  );
4188
4258
  var opensteerOpenInputSchema = objectSchema(
@@ -4250,6 +4320,17 @@ var opensteerPageCloseOutputSchema = objectSchema(
4250
4320
  required: ["closedPageRef", "pages"]
4251
4321
  }
4252
4322
  );
4323
+ var opensteerPageNewOutputSchema = objectSchema(
4324
+ {
4325
+ pageRef: pageRefSchema,
4326
+ url: stringSchema(),
4327
+ title: stringSchema()
4328
+ },
4329
+ {
4330
+ title: "OpensteerPageNewOutput",
4331
+ required: ["pageRef", "url", "title"]
4332
+ }
4333
+ );
4253
4334
  var opensteerPageGotoInputSchema = objectSchema(
4254
4335
  {
4255
4336
  url: stringSchema(),
@@ -4636,72 +4717,28 @@ var opensteerComputerExecuteInputSchema = objectSchema(
4636
4717
  required: ["action"]
4637
4718
  }
4638
4719
  );
4639
- var opensteerComputerTracePointSchema = objectSchema(
4640
- {
4641
- role: enumSchema(["point", "start", "end"]),
4642
- point: pointSchema,
4643
- hitTest: hitTestResultSchema,
4644
- target: opensteerResolvedTargetSchema
4645
- },
4646
- {
4647
- title: "OpensteerComputerTracePoint",
4648
- required: ["role", "point"]
4649
- }
4650
- );
4651
- var opensteerComputerTraceEnrichmentSchema = objectSchema(
4652
- {
4653
- points: arraySchema(opensteerComputerTracePointSchema)
4654
- },
4655
- {
4656
- title: "OpensteerComputerTraceEnrichment",
4657
- required: ["points"]
4658
- }
4659
- );
4660
- var opensteerComputerExecuteTimingSchema = objectSchema(
4720
+ var opensteerScreenshotSummarySchema = objectSchema(
4661
4721
  {
4662
- actionMs: integerSchema({ minimum: 0 }),
4663
- waitMs: integerSchema({ minimum: 0 }),
4664
- totalMs: integerSchema({ minimum: 0 })
4665
- },
4666
- {
4667
- title: "OpensteerComputerExecuteTiming",
4668
- required: ["actionMs", "waitMs", "totalMs"]
4669
- }
4670
- );
4671
- var opensteerComputerDisplayScaleSchema = objectSchema(
4672
- {
4673
- x: numberSchema({ exclusiveMinimum: 0 }),
4674
- y: numberSchema({ exclusiveMinimum: 0 })
4722
+ payload: externalBinaryLocationSchema,
4723
+ format: screenshotFormatSchema,
4724
+ size: sizeSchema,
4725
+ coordinateSpace: coordinateSpaceSchema,
4726
+ clip: rectSchema
4675
4727
  },
4676
4728
  {
4677
- title: "OpensteerComputerDisplayScale",
4678
- required: ["x", "y"]
4729
+ title: "OpensteerScreenshotSummary",
4730
+ required: ["payload", "format", "size", "coordinateSpace"]
4679
4731
  }
4680
4732
  );
4681
4733
  var opensteerComputerExecuteOutputSchema = objectSchema(
4682
4734
  {
4683
- action: opensteerComputerActionSchema,
4684
- pageRef: pageRefSchema,
4685
- screenshot: screenshotArtifactSchema,
4686
- displayViewport: viewportMetricsSchema,
4687
- nativeViewport: viewportMetricsSchema,
4688
- displayScale: opensteerComputerDisplayScaleSchema,
4689
- events: arraySchema(opensteerEventSchema),
4690
- timing: opensteerComputerExecuteTimingSchema,
4691
- trace: opensteerComputerTraceEnrichmentSchema
4735
+ url: stringSchema(),
4736
+ title: stringSchema(),
4737
+ screenshot: opensteerScreenshotSummarySchema
4692
4738
  },
4693
4739
  {
4694
4740
  title: "OpensteerComputerExecuteOutput",
4695
- required: [
4696
- "action",
4697
- "pageRef",
4698
- "screenshot",
4699
- "displayViewport",
4700
- "nativeViewport",
4701
- "displayScale",
4702
- "events",
4703
- "timing"
4704
- ]
4741
+ required: ["url", "title", "screenshot"]
4705
4742
  }
4706
4743
  );
4707
4744
  function assertValidSemanticOperationInput(name, input) {
@@ -4727,7 +4764,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4727
4764
  name: "session.open",
4728
4765
  description: "Open or resume the current Opensteer session and primary page.",
4729
4766
  inputSchema: opensteerOpenInputSchema,
4730
- outputSchema: opensteerSessionStateSchema,
4767
+ outputSchema: opensteerNavigationSummarySchema,
4731
4768
  requiredCapabilities: ["sessions.manage", "pages.manage"],
4732
4769
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["sessions.manage", "pages.manage"] : ["sessions.manage", "pages.manage", "pages.navigate"]
4733
4770
  }),
@@ -4742,7 +4779,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4742
4779
  name: "page.new",
4743
4780
  description: "Create and optionally navigate a new top-level page in the current session.",
4744
4781
  inputSchema: opensteerPageNewInputSchema,
4745
- outputSchema: opensteerSessionStateSchema,
4782
+ outputSchema: opensteerPageNewOutputSchema,
4746
4783
  requiredCapabilities: ["pages.manage"],
4747
4784
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["pages.manage"] : ["pages.manage", "pages.navigate"]
4748
4785
  }),
@@ -4750,7 +4787,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4750
4787
  name: "page.activate",
4751
4788
  description: "Activate an existing top-level page in the current session.",
4752
4789
  inputSchema: opensteerPageActivateInputSchema,
4753
- outputSchema: opensteerSessionStateSchema,
4790
+ outputSchema: opensteerNavigationSummarySchema,
4754
4791
  requiredCapabilities: ["pages.manage", "inspect.pages"]
4755
4792
  }),
4756
4793
  defineSemanticOperationSpec({
@@ -4764,7 +4801,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4764
4801
  name: "page.goto",
4765
4802
  description: "Navigate the current Opensteer page to a new URL.",
4766
4803
  inputSchema: opensteerPageGotoInputSchema,
4767
- outputSchema: opensteerSessionStateSchema,
4804
+ outputSchema: opensteerNavigationSummarySchema,
4768
4805
  requiredCapabilities: ["pages.navigate"]
4769
4806
  }),
4770
4807
  defineSemanticOperationSpec({
@@ -6845,30 +6882,15 @@ var DEFAULT_SETTLE_DELAYS = {
6845
6882
  "dom-action": 100,
6846
6883
  snapshot: 0
6847
6884
  };
6848
- var defaultSnapshotSettleObserver = {
6849
- async settle(input) {
6850
- if (input.trigger !== "snapshot") {
6851
- return false;
6852
- }
6853
- await input.engine.waitForVisualStability({
6854
- pageRef: input.pageRef,
6855
- ...input.remainingMs === void 0 ? {} : { timeoutMs: input.remainingMs },
6856
- settleMs: DEFAULT_VISUAL_STABILITY_SETTLE_MS,
6857
- scope: "visible-frames"
6858
- });
6859
- return true;
6860
- }
6861
- };
6862
- Object.freeze(defaultSnapshotSettleObserver);
6863
6885
  var DOM_ACTION_VISUAL_STABILITY_PROFILES = {
6864
- "dom.click": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
6865
- "dom.input": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
6866
- "dom.scroll": { settleMs: 600, scope: "visible-frames", timeoutMs: 7e3 },
6886
+ "dom.click": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
6887
+ "dom.input": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
6888
+ "dom.scroll": { settleMs: 600, scope: "main-frame", timeoutMs: 7e3 },
6867
6889
  "dom.hover": { settleMs: 200, scope: "main-frame", timeoutMs: 2500 }
6868
6890
  };
6869
6891
  var DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE = {
6870
6892
  settleMs: 750,
6871
- scope: "visible-frames",
6893
+ scope: "main-frame",
6872
6894
  timeoutMs: 7e3
6873
6895
  };
6874
6896
  var NAVIGATION_VISUAL_STABILITY_PROFILE = {
@@ -6892,6 +6914,7 @@ var defaultDomActionSettleObserver = {
6892
6914
  pageRef: input.pageRef,
6893
6915
  timeoutMs: effectiveTimeout,
6894
6916
  settleMs: profile.settleMs,
6917
+ ...input.observedMutationQuietMs === void 0 ? {} : { initialQuietMs: input.observedMutationQuietMs },
6895
6918
  scope: profile.scope
6896
6919
  });
6897
6920
  return true;
@@ -6912,15 +6935,20 @@ var defaultNavigationSettleObserver = {
6912
6935
  return false;
6913
6936
  }
6914
6937
  try {
6915
- const startedAt = Date.now();
6916
- await input.engine.waitForPostLoadQuiet({
6917
- pageRef: input.pageRef,
6918
- timeoutMs: effectiveTimeout,
6919
- quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
6920
- captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
6921
- signal: input.signal
6922
- });
6923
- const visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
6938
+ let visualTimeout = effectiveTimeout;
6939
+ let initialQuietMs = input.observedMutationQuietMs ?? 0;
6940
+ if (!input.postLoadHandled) {
6941
+ const startedAt = Date.now();
6942
+ await input.engine.waitForPostLoadQuiet({
6943
+ pageRef: input.pageRef,
6944
+ timeoutMs: effectiveTimeout,
6945
+ quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
6946
+ captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
6947
+ signal: input.signal
6948
+ });
6949
+ visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
6950
+ initialQuietMs = Math.max(initialQuietMs, DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS);
6951
+ }
6924
6952
  if (visualTimeout <= 0) {
6925
6953
  return true;
6926
6954
  }
@@ -6928,6 +6956,7 @@ var defaultNavigationSettleObserver = {
6928
6956
  pageRef: input.pageRef,
6929
6957
  timeoutMs: visualTimeout,
6930
6958
  settleMs: profile.settleMs,
6959
+ ...initialQuietMs <= 0 ? {} : { initialQuietMs },
6931
6960
  scope: profile.scope
6932
6961
  });
6933
6962
  return true;
@@ -6938,7 +6967,6 @@ var defaultNavigationSettleObserver = {
6938
6967
  };
6939
6968
  Object.freeze(defaultNavigationSettleObserver);
6940
6969
  var DEFAULT_SETTLE_OBSERVERS = Object.freeze([
6941
- defaultSnapshotSettleObserver,
6942
6970
  defaultDomActionSettleObserver,
6943
6971
  defaultNavigationSettleObserver
6944
6972
  ]);
@@ -7391,6 +7419,9 @@ function buildClauseSelector(node, clause) {
7391
7419
  if (!clause || typeof clause !== "object") {
7392
7420
  return "";
7393
7421
  }
7422
+ if (clause.kind === "text") {
7423
+ return "";
7424
+ }
7394
7425
  if (clause.kind === "position") {
7395
7426
  if (clause.axis === "nthOfType") {
7396
7427
  return `:nth-of-type(${Math.max(1, Number(node.position?.nthOfType || 1))})`;
@@ -7515,7 +7546,7 @@ function resolveExtractedValueInContext(normalizedValue, options) {
7515
7546
  function stripPositionClauses(nodes) {
7516
7547
  return (nodes || []).map((node) => ({
7517
7548
  ...node,
7518
- match: (node.match || []).filter((clause) => clause.kind !== "position")
7549
+ match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
7519
7550
  }));
7520
7551
  }
7521
7552
  function dedupeSelectors(selectors) {
@@ -8092,9 +8123,17 @@ function resolveDomPathInScope(index, domPath, scope) {
8092
8123
  if (!candidates.length) {
8093
8124
  return null;
8094
8125
  }
8126
+ const lastNode = domPath[domPath.length - 1];
8127
+ const textClauses = lastNode?.match.filter((c) => c.kind === "text") ?? [];
8095
8128
  let fallback = null;
8096
8129
  for (const selector of candidates) {
8097
- const matches = querySelectorAllInScope(index, selector, scope);
8130
+ let matches = querySelectorAllInScope(index, selector, scope);
8131
+ if (textClauses.length > 0 && matches.length > 1) {
8132
+ const filtered = matches.filter((node) => matchesTextClauses(node, textClauses));
8133
+ if (filtered.length > 0) {
8134
+ matches = filtered;
8135
+ }
8136
+ }
8098
8137
  if (matches.length === 1) {
8099
8138
  return {
8100
8139
  node: matches[0],
@@ -8114,6 +8153,10 @@ function resolveDomPathInScope(index, domPath, scope) {
8114
8153
  }
8115
8154
  return fallback;
8116
8155
  }
8156
+ function matchesTextClauses(node, clauses) {
8157
+ const text = (node.textContent ?? "").replace(/\s+/g, " ").trim();
8158
+ return clauses.every((clause) => text.includes(clause.value));
8159
+ }
8117
8160
  function queryAllDomPathInScope(index, domPath, scope) {
8118
8161
  const selectors = buildPathCandidates(domPath);
8119
8162
  for (const selector of selectors) {
@@ -8272,7 +8315,13 @@ function clonePathNode(node) {
8272
8315
  };
8273
8316
  }
8274
8317
  function cloneMatchClause(clause) {
8275
- return clause.kind === "position" ? { kind: "position", axis: clause.axis } : {
8318
+ if (clause.kind === "position") {
8319
+ return { kind: "position", axis: clause.axis };
8320
+ }
8321
+ if (clause.kind === "text") {
8322
+ return { kind: "text", value: clause.value };
8323
+ }
8324
+ return {
8276
8325
  kind: "attr",
8277
8326
  key: clause.key,
8278
8327
  ...clause.op === void 0 ? {} : { op: clause.op },
@@ -8327,6 +8376,13 @@ function normalizeMatch(rawMatch, attrs, position, tag) {
8327
8376
  op,
8328
8377
  ...value === void 0 ? {} : { value }
8329
8378
  });
8379
+ continue;
8380
+ }
8381
+ if (record.kind === "text") {
8382
+ const textValue = typeof record.value === "string" ? record.value.trim() : "";
8383
+ if (textValue) {
8384
+ push({ kind: "text", value: textValue.slice(0, 80) });
8385
+ }
8330
8386
  }
8331
8387
  }
8332
8388
  }
@@ -8694,7 +8750,7 @@ function isTimeoutError(error) {
8694
8750
  }
8695
8751
 
8696
8752
  // ../runtime-core/src/runtimes/dom/executor.ts
8697
- var MAX_DOM_ACTION_ATTEMPTS = 3;
8753
+ var MAX_DOM_ACTION_ATTEMPTS = 2;
8698
8754
  var DEFAULT_SCROLL_OPTIONS = {
8699
8755
  block: "center",
8700
8756
  inline: "center"
@@ -8984,7 +9040,7 @@ var DomActionExecutor = class {
8984
9040
  ...snapshot === void 0 ? {} : { snapshot },
8985
9041
  signal: timeout.signal,
8986
9042
  remainingMs: () => timeout.remainingMs(),
8987
- policySettle: async (targetPageRef, trigger) => {
9043
+ policySettle: async (targetPageRef, trigger, boundary2) => {
8988
9044
  try {
8989
9045
  await settleWithPolicy(this.options.policy.settle, {
8990
9046
  operation,
@@ -8992,7 +9048,9 @@ var DomActionExecutor = class {
8992
9048
  engine: this.options.engine,
8993
9049
  pageRef: targetPageRef,
8994
9050
  signal: timeout.signal,
8995
- remainingMs: timeout.remainingMs()
9051
+ remainingMs: timeout.remainingMs(),
9052
+ ...boundary2?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary2.observedMutationQuietMs },
9053
+ ...boundary2?.postLoadHandled === true ? { postLoadHandled: true } : {}
8996
9054
  });
8997
9055
  } catch (error) {
8998
9056
  if (snapshot !== void 0 && isSoftSettleTimeoutError(error, timeout.signal)) {
@@ -9181,11 +9239,12 @@ var DomActionExecutor = class {
9181
9239
  throw this.createActionabilityError(
9182
9240
  operation,
9183
9241
  "obscured",
9184
- `hit test resolved ${hit.nodeRef} outside the target subtree rooted at ${resolved.nodeRef}`,
9242
+ `target is obscured by ${assessment.blockingDescription ?? "another element"} at the click point`,
9185
9243
  {
9186
9244
  ...details,
9187
9245
  hitRelation: assessment.relation,
9188
9246
  ...assessment.ambiguous === void 0 ? {} : { hitAmbiguous: assessment.ambiguous },
9247
+ ...assessment.blockingDescription === void 0 ? {} : { blockingDescription: assessment.blockingDescription },
9189
9248
  ...assessment.canonicalTarget === void 0 ? {} : {
9190
9249
  canonicalNodeRef: assessment.canonicalTarget.nodeRef,
9191
9250
  canonicalDocumentRef: assessment.canonicalTarget.documentRef,
@@ -9199,8 +9258,7 @@ var DomActionExecutor = class {
9199
9258
  hitMissingFromSnapshot: !resolved.snapshot.nodes.some(
9200
9259
  (node) => node.nodeRef === hit.nodeRef
9201
9260
  )
9202
- },
9203
- true
9261
+ }
9204
9262
  );
9205
9263
  }
9206
9264
  async resolveActionablePointerTarget(session, operation, resolved) {
@@ -9412,7 +9470,12 @@ var DefaultDomRuntime = class {
9412
9470
  });
9413
9471
  }
9414
9472
  async buildPath(input) {
9415
- return sanitizeReplayElementPath(await this.requireBridge().buildReplayPath(input.locator));
9473
+ return sanitizeReplayElementPath(
9474
+ await this.requireBridge().buildReplayPath(
9475
+ input.locator,
9476
+ input.enableTextMatch ? { enableTextMatch: true } : void 0
9477
+ )
9478
+ );
9416
9479
  }
9417
9480
  async resolveTarget(input) {
9418
9481
  return this.withSnapshotSession((session) => this.resolveTargetWithSession(session, input));
@@ -9632,7 +9695,7 @@ var DefaultDomRuntime = class {
9632
9695
  if (resolvedByLocator) {
9633
9696
  const { snapshot, node } = resolvedByLocator;
9634
9697
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
9635
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
9698
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch: true });
9636
9699
  return this.createResolvedTarget("live", snapshot, node, anchor, {
9637
9700
  ...target.persist === void 0 ? {} : { persist: target.persist },
9638
9701
  ...replayPath === void 0 ? {} : { replayPath }
@@ -9650,11 +9713,12 @@ var DefaultDomRuntime = class {
9650
9713
  const { snapshot, node } = resolved;
9651
9714
  const anchor = await this.buildAnchorFromSnapshotNode(session, snapshot, node);
9652
9715
  const writeDescriptor = descriptorWriter ?? ((input) => this.descriptors.write(input));
9653
- const replayPath = await this.tryBuildPathFromNode(snapshot, node);
9716
+ const enableTextMatch = method !== "extract";
9717
+ const replayPath = await this.tryBuildPathFromNode(snapshot, node, { enableTextMatch });
9654
9718
  const descriptor = target.persist === void 0 ? void 0 : await writeDescriptor({
9655
9719
  method,
9656
9720
  persist: target.persist,
9657
- path: replayPath ?? await this.buildPathForNode(snapshot, node),
9721
+ path: replayPath ?? await this.buildPathForNode(snapshot, node, { enableTextMatch }),
9658
9722
  sourceUrl: snapshot.url
9659
9723
  });
9660
9724
  return this.createResolvedTarget("selector", snapshot, node, anchor, {
@@ -9754,7 +9818,7 @@ var DefaultDomRuntime = class {
9754
9818
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
9755
9819
  );
9756
9820
  }
9757
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node);
9821
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
9758
9822
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
9759
9823
  ...persist === void 0 ? {} : { persist },
9760
9824
  ...replayPath === void 0 ? {} : { replayPath }
@@ -9925,19 +9989,20 @@ var DefaultDomRuntime = class {
9925
9989
  }
9926
9990
  return this.bridge;
9927
9991
  }
9928
- async buildPathForNode(snapshot, node) {
9992
+ async buildPathForNode(snapshot, node, options) {
9929
9993
  if (node.nodeRef === void 0) {
9930
9994
  throw new Error(
9931
9995
  `snapshot node ${String(node.snapshotNodeId)} does not expose a live node reference`
9932
9996
  );
9933
9997
  }
9934
9998
  return this.buildPath({
9935
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef)
9999
+ locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
10000
+ ...options?.enableTextMatch ? { enableTextMatch: true } : {}
9936
10001
  });
9937
10002
  }
9938
- async tryBuildPathFromNode(snapshot, node) {
10003
+ async tryBuildPathFromNode(snapshot, node, options) {
9939
10004
  try {
9940
- return await this.buildPathForNode(snapshot, node);
10005
+ return await this.buildPathForNode(snapshot, node, options);
9941
10006
  } catch {
9942
10007
  return void 0;
9943
10008
  }
@@ -10373,29 +10438,29 @@ function buildVariantDescriptorFromCluster(descriptors) {
10373
10438
  const keyStats = /* @__PURE__ */ new Map();
10374
10439
  for (const descriptor of descriptors) {
10375
10440
  for (const field of descriptor.fields) {
10376
- const stat2 = keyStats.get(field.path) ?? {
10441
+ const stat3 = keyStats.get(field.path) ?? {
10377
10442
  indices: /* @__PURE__ */ new Set(),
10378
10443
  pathNodes: [],
10379
10444
  attributes: [],
10380
10445
  sources: []
10381
10446
  };
10382
- stat2.indices.add(descriptor.index);
10447
+ stat3.indices.add(descriptor.index);
10383
10448
  if (isPersistedOpensteerExtractionValueNode(field.node)) {
10384
- stat2.pathNodes.push(field.node.$path);
10385
- stat2.attributes.push(field.node.attribute);
10449
+ stat3.pathNodes.push(field.node.$path);
10450
+ stat3.attributes.push(field.node.attribute);
10386
10451
  } else if (isPersistedOpensteerExtractionSourceNode(field.node)) {
10387
- stat2.sources.push("current_url");
10452
+ stat3.sources.push("current_url");
10388
10453
  }
10389
- keyStats.set(field.path, stat2);
10454
+ keyStats.set(field.path, stat3);
10390
10455
  }
10391
10456
  }
10392
10457
  const mergedFields = [];
10393
- for (const [fieldPath, stat2] of keyStats) {
10394
- if (stat2.indices.size < threshold) {
10458
+ for (const [fieldPath, stat3] of keyStats) {
10459
+ if (stat3.indices.size < threshold) {
10395
10460
  continue;
10396
10461
  }
10397
- if (stat2.pathNodes.length >= threshold) {
10398
- let mergedFieldPath = stat2.pathNodes.length === 1 ? sanitizeElementPath(stat2.pathNodes[0]) : mergeElementPathsByMajority(stat2.pathNodes);
10462
+ if (stat3.pathNodes.length >= threshold) {
10463
+ let mergedFieldPath = stat3.pathNodes.length === 1 ? sanitizeElementPath(stat3.pathNodes[0]) : mergeElementPathsByMajority(stat3.pathNodes);
10399
10464
  if (!mergedFieldPath) {
10400
10465
  continue;
10401
10466
  }
@@ -10403,8 +10468,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
10403
10468
  mergedFieldPath = relaxPathForSingleSample(mergedFieldPath, "field");
10404
10469
  }
10405
10470
  mergedFieldPath = minimizePathMatchClauses(mergedFieldPath, "field");
10406
- const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
10407
- const attribute = pickModeString(stat2.attributes, attrThreshold);
10471
+ const attrThreshold = stat3.pathNodes.length === 1 ? 1 : majorityThreshold(stat3.pathNodes.length);
10472
+ const attribute = pickModeString(stat3.attributes, attrThreshold);
10408
10473
  mergedFields.push({
10409
10474
  path: fieldPath,
10410
10475
  node: createValueNode({
@@ -10414,7 +10479,7 @@ function buildVariantDescriptorFromCluster(descriptors) {
10414
10479
  });
10415
10480
  continue;
10416
10481
  }
10417
- const dominantSource = pickModeString(stat2.sources, threshold);
10482
+ const dominantSource = pickModeString(stat3.sources, threshold);
10418
10483
  if (dominantSource === "current_url") {
10419
10484
  mergedFields.push({
10420
10485
  path: fieldPath,
@@ -10551,6 +10616,9 @@ function relaxPathForSingleSample(path18, mode) {
10551
10616
  }
10552
10617
  return !isLast;
10553
10618
  }
10619
+ if (clause.kind === "text") {
10620
+ return false;
10621
+ }
10554
10622
  const key = String(clause.key || "").trim().toLowerCase();
10555
10623
  if (!key || !shouldKeepAttrForSingleSample(key)) {
10556
10624
  return false;
@@ -10653,9 +10721,11 @@ function buildNodeStructure(node) {
10653
10721
  }
10654
10722
  structuralAttrs[key] = value;
10655
10723
  }
10656
- const matchClauses = (node.match || []).map(
10657
- (clause) => clause.kind === "position" ? `position:${clause.axis}` : `attr:${String(clause.key || "").trim().toLowerCase()}`
10658
- ).sort();
10724
+ const matchClauses = (node.match || []).map((clause) => {
10725
+ if (clause.kind === "position") return `position:${clause.axis}`;
10726
+ if (clause.kind === "text") return `text:${clause.value}`;
10727
+ return `attr:${String(clause.key || "").trim().toLowerCase()}`;
10728
+ }).sort();
10659
10729
  return {
10660
10730
  tag,
10661
10731
  attrs: structuralAttrs,
@@ -11061,6 +11131,9 @@ function mergeMatchByMajority(matchLists, attrs, threshold, positionFlags = {
11061
11131
  });
11062
11132
  continue;
11063
11133
  }
11134
+ if (clause.kind === "text") {
11135
+ continue;
11136
+ }
11064
11137
  if (clause.axis === "nthOfType") {
11065
11138
  if (positionFlags.hasNthOfType) {
11066
11139
  merged.push({ kind: "position", axis: "nthOfType" });
@@ -11989,9 +12062,9 @@ async function getProcessLiveness(owner) {
11989
12062
  if (typeof startedAtMs === "number") {
11990
12063
  return hasMatchingProcessStartTime(owner.processStartedAtMs, startedAtMs) ? "live" : "dead";
11991
12064
  }
11992
- return isProcessRunning(owner.pid) ? "unknown" : "dead";
12065
+ return isProcessRunning2(owner.pid) ? "unknown" : "dead";
11993
12066
  }
11994
- function isProcessRunning(pid) {
12067
+ function isProcessRunning2(pid) {
11995
12068
  try {
11996
12069
  process.kill(pid, 0);
11997
12070
  return true;
@@ -13411,11 +13484,31 @@ async function writePersistedSessionRecord(rootPath, record) {
13411
13484
  async function clearPersistedSessionRecord(rootPath, provider) {
13412
13485
  await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
13413
13486
  }
13487
+ function getPersistedLocalBrowserSessionOwnership(record) {
13488
+ return record.ownership === "attached" ? "attached" : "owned";
13489
+ }
13490
+ async function isAttachedLocalBrowserSessionReachable(record) {
13491
+ if (getPersistedLocalBrowserSessionOwnership(record) !== "attached") {
13492
+ return false;
13493
+ }
13494
+ if (record.engine !== "playwright" || record.endpoint === void 0) {
13495
+ return false;
13496
+ }
13497
+ try {
13498
+ await inspectCdpEndpoint({
13499
+ endpoint: record.endpoint,
13500
+ timeoutMs: 1500
13501
+ });
13502
+ return true;
13503
+ } catch {
13504
+ return false;
13505
+ }
13506
+ }
13414
13507
  function isPersistedCloudSessionRecord(value) {
13415
13508
  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);
13416
13509
  }
13417
13510
  function isPersistedLocalBrowserSessionRecord(value) {
13418
- 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;
13511
+ 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;
13419
13512
  }
13420
13513
  function resolveOpensteerStateDir() {
13421
13514
  const explicit = process.env.OPENSTEER_HOME?.trim();
@@ -13804,19 +13897,40 @@ function delay(ms) {
13804
13897
  var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
13805
13898
  var OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
13806
13899
  function buildLocalViewSessionId(input) {
13900
+ const ownership = input.ownership ?? "owned";
13901
+ const identity = ownership === "attached" ? input.endpoint ?? input.remoteDebuggingUrl ?? input.baseUrl ?? "attached" : `pid:${String(input.pid ?? 0)}`;
13807
13902
  const hash = crypto.createHash("sha256").update(`${input.rootPath}
13808
- ${String(input.pid)}
13903
+ ${ownership}
13904
+ ${identity}
13809
13905
  ${String(input.startedAt)}`).digest("hex");
13810
13906
  return `local_${hash.slice(0, 24)}`;
13811
13907
  }
13908
+ function buildLocalViewSessionIdForRecord(input) {
13909
+ const ownership = getPersistedLocalBrowserSessionOwnership(input.live);
13910
+ if (ownership === "attached") {
13911
+ return buildLocalViewSessionId({
13912
+ rootPath: input.rootPath,
13913
+ ownership,
13914
+ startedAt: input.live.startedAt,
13915
+ ...input.live.endpoint === void 0 ? {} : { endpoint: input.live.endpoint },
13916
+ ...input.live.baseUrl === void 0 ? {} : { baseUrl: input.live.baseUrl },
13917
+ ...input.live.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: input.live.remoteDebuggingUrl }
13918
+ });
13919
+ }
13920
+ return buildLocalViewSessionId({
13921
+ rootPath: input.rootPath,
13922
+ ownership,
13923
+ startedAt: input.live.startedAt,
13924
+ pid: input.live.pid
13925
+ });
13926
+ }
13812
13927
  function createLocalViewSessionManifest(input) {
13813
13928
  return {
13814
13929
  layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
13815
13930
  version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
13816
- sessionId: buildLocalViewSessionId({
13931
+ sessionId: buildLocalViewSessionIdForRecord({
13817
13932
  rootPath: input.rootPath,
13818
- pid: input.live.pid,
13819
- startedAt: input.live.startedAt
13933
+ live: input.live
13820
13934
  }),
13821
13935
  rootPath: input.rootPath,
13822
13936
  ...input.workspace === void 0 ? {} : { workspace: input.workspace },
@@ -13875,7 +13989,8 @@ function normalizeOpensteerEngineName(value, source = "engine") {
13875
13989
  if (normalized === "playwright" || normalized === "abp") {
13876
13990
  return normalized;
13877
13991
  }
13878
- throw new Error(
13992
+ throw new OpensteerProtocolError(
13993
+ "invalid-argument",
13879
13994
  `${source} must be one of ${OPENSTEER_ENGINE_NAMES.join(", ")}; received "${value}".`
13880
13995
  );
13881
13996
  }
@@ -13884,7 +13999,8 @@ function assertSupportedEngineOptions(input) {
13884
13999
  return;
13885
14000
  }
13886
14001
  if (typeof input.browser === "object" && input.browser !== null && input.browser.mode === "attach") {
13887
- throw new Error(
14002
+ throw new OpensteerProtocolError(
14003
+ "invalid-argument",
13888
14004
  'ABP engine does not support browser.mode="attach". Use the Playwright engine for attach flows.'
13889
14005
  );
13890
14006
  }
@@ -13892,7 +14008,8 @@ function assertSupportedEngineOptions(input) {
13892
14008
  if (unsupportedContextOptionNames.length === 0) {
13893
14009
  return;
13894
14010
  }
13895
- throw new Error(
14011
+ throw new OpensteerProtocolError(
14012
+ "invalid-argument",
13896
14013
  `ABP engine does not support ${unsupportedContextOptionNames.join(", ")}. Supported ABP context options: context.viewport.`
13897
14014
  );
13898
14015
  }
@@ -14014,7 +14131,7 @@ var OpensteerBrowserManager = class {
14014
14131
  }
14015
14132
  const liveRecord = await this.readLivePersistentBrowser(await this.ensureWorkspaceStore());
14016
14133
  return {
14017
- mode: this.mode,
14134
+ mode: liveRecord?.ownership === "attached" ? "attach" : this.mode,
14018
14135
  engine: liveRecord?.engine ?? this.engineName,
14019
14136
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14020
14137
  live: liveRecord !== void 0
@@ -14142,6 +14259,7 @@ var OpensteerBrowserManager = class {
14142
14259
  });
14143
14260
  const liveRecord = {
14144
14261
  mode: "persistent",
14262
+ ownership: "owned",
14145
14263
  engine: "abp",
14146
14264
  baseUrl: launched.baseUrl,
14147
14265
  remoteDebuggingUrl: launched.remoteDebuggingUrl,
@@ -14228,11 +14346,78 @@ var OpensteerBrowserManager = class {
14228
14346
  }
14229
14347
  async createAttachEngine() {
14230
14348
  const endpoint = await resolveAttachEndpoint(this.browserOptions);
14231
- return this.createAttachedEngine({
14232
- endpoint,
14233
- ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14234
- freshTab: this.browserOptions?.freshTab ?? true,
14235
- onDispose: async () => void 0
14349
+ if (this.workspace === void 0) {
14350
+ return this.createAttachedEngine({
14351
+ endpoint,
14352
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14353
+ freshTab: this.browserOptions?.freshTab ?? true,
14354
+ onDispose: async () => void 0
14355
+ });
14356
+ }
14357
+ const workspace = await this.ensureWorkspaceStore();
14358
+ return workspace.lock(async () => {
14359
+ const live = await this.readLivePersistentBrowser(workspace);
14360
+ if (live) {
14361
+ if (live.engine !== "playwright") {
14362
+ throw new Error(
14363
+ `workspace "${this.workspace}" already has a live ${live.engine} browser. Close it before attaching a Playwright browser.`
14364
+ );
14365
+ }
14366
+ if (live.ownership !== "attached") {
14367
+ throw new Error(
14368
+ `workspace "${this.workspace}" already has a live Opensteer-owned browser. Close it before attaching another browser.`
14369
+ );
14370
+ }
14371
+ if (live.endpoint === void 0) {
14372
+ throw new Error("workspace live browser record is missing a DevTools endpoint.");
14373
+ }
14374
+ if (live.endpoint !== endpoint) {
14375
+ throw new Error(
14376
+ `workspace "${this.workspace}" is already attached to a different browser endpoint. Close it before reattaching.`
14377
+ );
14378
+ }
14379
+ await bestEffortRegisterLocalViewSession({
14380
+ rootPath: workspace.rootPath,
14381
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14382
+ live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
14383
+ ownership: "attached"
14384
+ });
14385
+ return this.createAttachedEngine({
14386
+ endpoint: live.endpoint,
14387
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14388
+ freshTab: this.browserOptions?.freshTab ?? true,
14389
+ onDispose: async () => void 0
14390
+ });
14391
+ }
14392
+ const liveRecord = {
14393
+ mode: "persistent",
14394
+ ownership: "attached",
14395
+ engine: "playwright",
14396
+ endpoint,
14397
+ pid: 0,
14398
+ startedAt: Date.now(),
14399
+ userDataDir: workspace.browserUserDataDir
14400
+ };
14401
+ await this.writeLivePersistentBrowser(workspace, liveRecord);
14402
+ const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
14403
+ await bestEffortRegisterLocalViewSession({
14404
+ rootPath: workspace.rootPath,
14405
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14406
+ live: persistedLiveRecord,
14407
+ ownership: "attached"
14408
+ });
14409
+ try {
14410
+ return await this.createAttachedEngine({
14411
+ endpoint,
14412
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14413
+ freshTab: this.browserOptions?.freshTab ?? true,
14414
+ onDispose: async () => void 0
14415
+ });
14416
+ } catch (error) {
14417
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
14418
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14419
+ throw error;
14420
+ }
14236
14421
  });
14237
14422
  }
14238
14423
  async createPersistentEngine() {
@@ -14252,7 +14437,7 @@ var OpensteerBrowserManager = class {
14252
14437
  rootPath: workspace.rootPath,
14253
14438
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14254
14439
  live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
14255
- ownership: "owned"
14440
+ ownership: live.ownership
14256
14441
  });
14257
14442
  return this.createAttachedEngine({
14258
14443
  endpoint: live.endpoint,
@@ -14268,6 +14453,7 @@ var OpensteerBrowserManager = class {
14268
14453
  });
14269
14454
  const liveRecord = {
14270
14455
  mode: "persistent",
14456
+ ownership: "owned",
14271
14457
  engine: "playwright",
14272
14458
  endpoint: launched.endpoint,
14273
14459
  pid: launched.pid,
@@ -14397,7 +14583,20 @@ var OpensteerBrowserManager = class {
14397
14583
  if (live === void 0) {
14398
14584
  return void 0;
14399
14585
  }
14400
- if (!isProcessRunning(live.pid)) {
14586
+ if (live.ownership === "attached") {
14587
+ const attachedRecord = toPersistedLocalBrowserSessionRecord(this.workspace, live);
14588
+ if (!await isAttachedLocalBrowserSessionReachable(attachedRecord)) {
14589
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, attachedRecord);
14590
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14591
+ return void 0;
14592
+ }
14593
+ return live;
14594
+ }
14595
+ if (!isProcessRunning2(live.pid)) {
14596
+ await this.unregisterLocalViewSessionForRecord(
14597
+ workspace.rootPath,
14598
+ toPersistedLocalBrowserSessionRecord(this.workspace, live)
14599
+ );
14401
14600
  await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14402
14601
  return void 0;
14403
14602
  }
@@ -14445,6 +14644,10 @@ var OpensteerBrowserManager = class {
14445
14644
  workspace.rootPath,
14446
14645
  toPersistedLocalBrowserSessionRecord(this.workspace, live)
14447
14646
  );
14647
+ if (live.ownership === "attached") {
14648
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14649
+ return;
14650
+ }
14448
14651
  if (live.engine === "playwright") {
14449
14652
  if (live.endpoint !== void 0) {
14450
14653
  await requestBrowserClose(live.endpoint).catch(() => void 0);
@@ -14473,10 +14676,18 @@ var OpensteerBrowserManager = class {
14473
14676
  }
14474
14677
  async unregisterLocalViewSessionForRecord(rootPath, record) {
14475
14678
  await bestEffortUnregisterLocalViewSession(
14476
- buildLocalViewSessionId({
14679
+ getPersistedLocalBrowserSessionOwnership(record) === "attached" ? buildLocalViewSessionId({
14680
+ rootPath,
14681
+ startedAt: record.startedAt,
14682
+ ownership: "attached",
14683
+ ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
14684
+ ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
14685
+ ...record.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: record.remoteDebuggingUrl }
14686
+ }) : buildLocalViewSessionId({
14477
14687
  rootPath,
14478
- pid: record.pid,
14479
- startedAt: record.startedAt
14688
+ startedAt: record.startedAt,
14689
+ ownership: "owned",
14690
+ pid: record.pid
14480
14691
  })
14481
14692
  );
14482
14693
  }
@@ -14511,6 +14722,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
14511
14722
  version: 1,
14512
14723
  provider: "local",
14513
14724
  ...workspace === void 0 ? {} : { workspace },
14725
+ ownership: live.ownership,
14514
14726
  engine: live.engine,
14515
14727
  ...live.endpoint === void 0 ? {} : { endpoint: live.endpoint },
14516
14728
  ...live.baseUrl === void 0 ? {} : { baseUrl: live.baseUrl },
@@ -14526,6 +14738,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
14526
14738
  function toWorkspaceLiveBrowserRecord(record) {
14527
14739
  return {
14528
14740
  mode: "persistent",
14741
+ ownership: getPersistedLocalBrowserSessionOwnership(record),
14529
14742
  engine: record.engine,
14530
14743
  ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
14531
14744
  ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
@@ -14552,7 +14765,12 @@ function isAttachBrowserOptions(browser) {
14552
14765
  async function resolveAttachEndpoint(browser) {
14553
14766
  const endpoint = browser?.endpoint?.trim();
14554
14767
  if (endpoint && endpoint.length > 0) {
14555
- return endpoint;
14768
+ const inspected = await inspectCdpEndpoint({
14769
+ endpoint,
14770
+ ...browser?.headers === void 0 ? {} : { headers: browser.headers },
14771
+ timeoutMs: DEFAULT_TIMEOUT_MS
14772
+ });
14773
+ return inspected.endpoint;
14556
14774
  }
14557
14775
  const selection = await selectAttachBrowserCandidate({
14558
14776
  timeoutMs: DEFAULT_TIMEOUT_MS
@@ -14816,12 +15034,12 @@ async function waitForProcessExit(pid, timeoutMs) {
14816
15034
  }
14817
15035
  const deadline = Date.now() + timeoutMs;
14818
15036
  while (Date.now() < deadline) {
14819
- if (!isProcessRunning(pid)) {
15037
+ if (!isProcessRunning2(pid)) {
14820
15038
  return true;
14821
15039
  }
14822
15040
  await sleep2(50);
14823
15041
  }
14824
- return !isProcessRunning(pid);
15042
+ return !isProcessRunning2(pid);
14825
15043
  }
14826
15044
  function resolveAbpSessionDir(workspace) {
14827
15045
  return path10__default.default.join(workspace.livePath, "abp-session");
@@ -14994,15 +15212,58 @@ function isOpensteerEnvironmentKey(key) {
14994
15212
  return key.startsWith(OPENSTEER_ENV_PREFIX);
14995
15213
  }
14996
15214
 
14997
- // src/provider/config.ts
14998
- var OPENSTEER_PROVIDER_MODES = ["local", "cloud"];
14999
- function assertProviderSupportsEngine(provider, engine) {
15000
- if (engine !== "abp") {
15001
- return;
15215
+ // src/internal/errors.ts
15216
+ function normalizeThrownOpensteerError(error, fallbackMessage) {
15217
+ if (isOpensteerProtocolError(error)) {
15218
+ return toOpensteerError(error);
15002
15219
  }
15003
- if (provider === "cloud") {
15004
- throw new Error(
15005
- "ABP is not supported for provider=cloud. Cloud provider currently requires Playwright."
15220
+ if (isBrowserCoreError(error)) {
15221
+ return createOpensteerError(error.code, error.message, {
15222
+ retriable: error.retriable,
15223
+ ...error.details === void 0 ? {} : { details: error.details }
15224
+ });
15225
+ }
15226
+ if (error instanceof OpensteerAttachAmbiguousError) {
15227
+ return createOpensteerError("conflict", error.message, {
15228
+ details: {
15229
+ candidates: error.candidates,
15230
+ code: error.code,
15231
+ name: error.name
15232
+ }
15233
+ });
15234
+ }
15235
+ if (error instanceof Error && "opensteerError" in error && typeof error.opensteerError === "object" && error.opensteerError !== null) {
15236
+ const oe = error.opensteerError;
15237
+ return createOpensteerError(oe.code, oe.message, {
15238
+ retriable: oe.retriable,
15239
+ ...oe.capability === void 0 ? {} : { capability: oe.capability },
15240
+ ...oe.details === void 0 ? {} : { details: oe.details }
15241
+ });
15242
+ }
15243
+ if (error instanceof Error) {
15244
+ return createOpensteerError("operation-failed", error.message, {
15245
+ details: {
15246
+ name: error.name
15247
+ }
15248
+ });
15249
+ }
15250
+ return createOpensteerError("internal", fallbackMessage, {
15251
+ details: {
15252
+ value: error
15253
+ }
15254
+ });
15255
+ }
15256
+
15257
+ // src/provider/config.ts
15258
+ var OPENSTEER_PROVIDER_MODES = ["local", "cloud"];
15259
+ function assertProviderSupportsEngine(provider, engine) {
15260
+ if (engine !== "abp") {
15261
+ return;
15262
+ }
15263
+ if (provider === "cloud") {
15264
+ throw new OpensteerProtocolError(
15265
+ "invalid-argument",
15266
+ "ABP is not supported for provider=cloud. Cloud provider currently requires Playwright."
15006
15267
  );
15007
15268
  }
15008
15269
  }
@@ -15011,7 +15272,8 @@ function normalizeOpensteerProviderMode(value, source = "OPENSTEER_PROVIDER") {
15011
15272
  if (normalized === OPENSTEER_PROVIDER_MODES[0] || normalized === OPENSTEER_PROVIDER_MODES[1]) {
15012
15273
  return normalized;
15013
15274
  }
15014
- throw new Error(
15275
+ throw new OpensteerProtocolError(
15276
+ "invalid-argument",
15015
15277
  `${source} must be one of ${OPENSTEER_PROVIDER_MODES.join(", ")}; received "${value}".`
15016
15278
  );
15017
15279
  }
@@ -15451,6 +15713,24 @@ async function sleep3(ms) {
15451
15713
  // src/cloud/client.ts
15452
15714
  var CLOUD_CLOSE_TIMEOUT_MS = 6e4;
15453
15715
  var CLOUD_CLOSE_POLL_INTERVAL_MS = 250;
15716
+ var OpensteerCloudRequestError = class extends Error {
15717
+ statusCode;
15718
+ code;
15719
+ details;
15720
+ method;
15721
+ pathname;
15722
+ url;
15723
+ constructor(args) {
15724
+ super(args.message);
15725
+ this.name = "OpensteerCloudRequestError";
15726
+ this.statusCode = args.statusCode;
15727
+ this.code = args.code;
15728
+ this.details = args.details;
15729
+ this.method = args.method;
15730
+ this.pathname = args.pathname;
15731
+ this.url = args.url;
15732
+ }
15733
+ };
15454
15734
  var OpensteerCloudClient = class {
15455
15735
  constructor(config) {
15456
15736
  this.config = config;
@@ -15626,7 +15906,11 @@ var OpensteerCloudClient = class {
15626
15906
  });
15627
15907
  }
15628
15908
  if (!response.ok) {
15629
- throw new Error(`${init.method} ${pathname} failed with ${String(response.status)}.`);
15909
+ throw await createCloudRequestError(response, {
15910
+ method: init.method,
15911
+ pathname,
15912
+ url
15913
+ });
15630
15914
  }
15631
15915
  return response;
15632
15916
  }
@@ -15674,6 +15958,36 @@ function wrapCloudFetchError(error, input) {
15674
15958
  wrapped.name = error.name;
15675
15959
  return wrapped;
15676
15960
  }
15961
+ async function createCloudRequestError(response, input) {
15962
+ const payload = await readCloudErrorPayload(response);
15963
+ return new OpensteerCloudRequestError({
15964
+ statusCode: response.status,
15965
+ method: input.method,
15966
+ pathname: input.pathname,
15967
+ url: input.url,
15968
+ message: payload?.error ?? `${input.method} ${input.pathname} failed with ${String(response.status)}.`,
15969
+ ...payload?.code === void 0 ? {} : { code: payload.code },
15970
+ ...payload?.details === void 0 ? {} : { details: payload.details }
15971
+ });
15972
+ }
15973
+ async function readCloudErrorPayload(response) {
15974
+ try {
15975
+ return asCloudErrorPayload(await response.json());
15976
+ } catch {
15977
+ return void 0;
15978
+ }
15979
+ }
15980
+ function asCloudErrorPayload(value) {
15981
+ if (value === null || typeof value !== "object") {
15982
+ return void 0;
15983
+ }
15984
+ const payload = value;
15985
+ return {
15986
+ ...typeof payload.error === "string" ? { error: payload.error } : {},
15987
+ ...typeof payload.code === "string" ? { code: payload.code } : {},
15988
+ ..."details" in payload ? { details: payload.details } : {}
15989
+ };
15990
+ }
15677
15991
 
15678
15992
  // src/cloud/config.ts
15679
15993
  function resolveCloudConfig(input = {}) {
@@ -15704,13 +16018,13 @@ function resolveCloudConfig(input = {}) {
15704
16018
 
15705
16019
  // ../runtime-core/package.json
15706
16020
  var package_default = {
15707
- version: "0.2.4"};
16021
+ version: "0.2.6"};
15708
16022
 
15709
16023
  // ../runtime-core/src/version.ts
15710
16024
  var OPENSTEER_RUNTIME_CORE_VERSION = package_default.version;
15711
16025
 
15712
16026
  // ../runtime-core/src/internal/errors.ts
15713
- function normalizeThrownOpensteerError(error, fallbackMessage) {
16027
+ function normalizeThrownOpensteerError2(error, fallbackMessage) {
15714
16028
  if (isOpensteerProtocolError(error)) {
15715
16029
  return toOpensteerError(error);
15716
16030
  }
@@ -15975,12 +16289,16 @@ function toOpensteerResolvedTarget(target) {
15975
16289
  documentRef: target.documentRef,
15976
16290
  documentEpoch: target.documentEpoch,
15977
16291
  nodeRef: target.nodeRef,
15978
- tagName: target.node.nodeName.toUpperCase(),
16292
+ tagName: toOpensteerTagName(target.node.nodeName),
15979
16293
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
15980
16294
  ...target.persist === void 0 ? {} : { persist: target.persist },
15981
16295
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
15982
16296
  };
15983
16297
  }
16298
+ function toOpensteerTagName(nodeName) {
16299
+ const tagName = String(nodeName).trim().toLowerCase();
16300
+ return tagName.length === 0 ? "element" : tagName;
16301
+ }
15984
16302
 
15985
16303
  // ../runtime-core/src/runtimes/computer-use/runtime.ts
15986
16304
  function createComputerUseRuntime(options) {
@@ -16014,7 +16332,7 @@ var DefaultComputerUseRuntime = class {
16014
16332
  screenshot,
16015
16333
  signal: input.timeout.signal,
16016
16334
  remainingMs: () => input.timeout.remainingMs(),
16017
- policySettle: async (pageRef, trigger) => {
16335
+ policySettle: async (pageRef, trigger, boundary) => {
16018
16336
  try {
16019
16337
  await settleWithPolicy(this.options.policy.settle, {
16020
16338
  operation: "computer.execute",
@@ -16022,7 +16340,9 @@ var DefaultComputerUseRuntime = class {
16022
16340
  engine: this.options.engine,
16023
16341
  pageRef,
16024
16342
  signal: input.timeout.signal,
16025
- remainingMs: input.timeout.remainingMs()
16343
+ remainingMs: input.timeout.remainingMs(),
16344
+ ...boundary?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary.observedMutationQuietMs },
16345
+ ...boundary?.postLoadHandled === true ? { postLoadHandled: true } : {}
16026
16346
  });
16027
16347
  } catch (error) {
16028
16348
  if (pageRef === input.pageRef && isSoftSettleTimeoutError(error, input.timeout.signal)) {
@@ -17200,28 +17520,24 @@ function restoreBoundedAttr(el, attr, value) {
17200
17520
  }
17201
17521
  setBoundedAttr(el, attr, value);
17202
17522
  }
17203
- function deduplicateImages(html) {
17523
+ function deduplicateImagesInDom($) {
17204
17524
  const seen = /* @__PURE__ */ new Set();
17205
- return html.replace(/<img\b([^>]*)>/gi, (full, attrContent) => {
17206
- if (/\bc\s*=/.test(attrContent)) {
17207
- return full;
17208
- }
17209
- const srcMatch = attrContent.match(/\bsrc\s*=\s*(["']?)(.*?)\1/);
17210
- const srcsetMatch = attrContent.match(/\bsrcset\s*=\s*(["'])(.*?)\1/);
17211
- let src = null;
17212
- if (srcMatch && srcMatch[2]) {
17213
- src = srcMatch[2].trim();
17214
- } else if (srcsetMatch && srcsetMatch[2]) {
17215
- src = srcsetMatch[2].split(",")[0]?.trim().split(" ")[0] ?? null;
17525
+ $("img").each(function deduplicateDomImage() {
17526
+ const el = $(this);
17527
+ if (el.attr("c") !== void 0) {
17528
+ return;
17216
17529
  }
17530
+ const srcValue = el.attr("src")?.trim();
17531
+ const srcsetValue = el.attr("srcset");
17532
+ const src = srcValue && srcValue.length > 0 ? srcValue : srcsetValue?.split(",")[0]?.trim().split(/\s+/u)[0];
17217
17533
  if (!src) {
17218
- return full;
17534
+ return;
17219
17535
  }
17220
17536
  if (seen.has(src)) {
17221
- return "";
17537
+ el.remove();
17538
+ return;
17222
17539
  }
17223
17540
  seen.add(src);
17224
- return full;
17225
17541
  });
17226
17542
  }
17227
17543
  function hasAttribute2(node, attr) {
@@ -17279,23 +17595,6 @@ function isPreservedImageElement(node) {
17279
17595
  function getElementsInReverseDocumentOrder($) {
17280
17596
  return $.root().find("*").toArray().reverse().filter((node) => node.type === "tag");
17281
17597
  }
17282
- function getNodeDepth(node) {
17283
- let depth = 0;
17284
- let current = node.parent;
17285
- while (current) {
17286
- depth++;
17287
- current = current.parent;
17288
- }
17289
- return depth;
17290
- }
17291
- function getElementsByDepthDescending($) {
17292
- const elements = $.root().find("*").toArray().filter((node) => node.type === "tag");
17293
- const depths = /* @__PURE__ */ new Map();
17294
- for (const el of elements) {
17295
- depths.set(el, getNodeDepth(el));
17296
- }
17297
- return elements.sort((a, b) => (depths.get(b) ?? 0) - (depths.get(a) ?? 0));
17298
- }
17299
17598
  function flattenExtractionTree($) {
17300
17599
  for (const node of getElementsInReverseDocumentOrder($)) {
17301
17600
  const el = $(node);
@@ -17313,19 +17612,6 @@ function flattenExtractionTree($) {
17313
17612
  el.replaceWith(el.contents());
17314
17613
  }
17315
17614
  }
17316
- function hasMarkedAncestor(el, attr) {
17317
- let current = el[0]?.parent;
17318
- while (current) {
17319
- if (!isElementLikeNode(current)) {
17320
- return false;
17321
- }
17322
- if (current.attribs?.[attr] !== void 0) {
17323
- return true;
17324
- }
17325
- current = current.parent;
17326
- }
17327
- return false;
17328
- }
17329
17615
  function isIndicatorImage(node) {
17330
17616
  return (node?.tagName || "").toLowerCase() === "img" && (hasAttribute2(node, "alt") || hasAttribute2(node, "src") || hasAttribute2(node, "srcset"));
17331
17617
  }
@@ -17443,7 +17729,7 @@ function serializeForExtraction($, root) {
17443
17729
  traverse(root, 0);
17444
17730
  return lines.map((l) => l.trim()).filter((l) => l.length > 0).join("");
17445
17731
  }
17446
- function isClickable($, el, context) {
17732
+ function isClickable(el, context) {
17447
17733
  if (context.hasPreMarked) {
17448
17734
  return el.attr(OPENSTEER_INTERACTIVE_ATTR) !== void 0;
17449
17735
  }
@@ -17477,21 +17763,17 @@ function isClickable($, el, context) {
17477
17763
  }
17478
17764
  return false;
17479
17765
  }
17480
- function cleanForExtraction(html) {
17766
+ function prepareExtractionSnapshotDom(html) {
17481
17767
  if (!html.trim()) {
17482
- return "";
17768
+ return void 0;
17483
17769
  }
17484
17770
  const $ = cheerio__namespace.load(html, { xmlMode: false });
17485
17771
  removeNoise($);
17486
17772
  removeComments($);
17487
17773
  markInlineSelfHiddenFallback($);
17488
17774
  pruneSelfHiddenNodes($);
17489
- const $clean = cheerio__namespace.load(
17490
- $.html().replace(/\n{2,}/g, "\n").trim(),
17491
- { xmlMode: false }
17492
- );
17493
- $clean("*").each(function stripAndRestoreExtractionAttrs() {
17494
- const el = $clean(this);
17775
+ $("*").each(function stripAndRestoreExtractionAttrs() {
17776
+ const el = $(this);
17495
17777
  const node = el[0];
17496
17778
  if (!node) {
17497
17779
  return;
@@ -17532,16 +17814,20 @@ function cleanForExtraction(html) {
17532
17814
  restoreBoundedAttr(el, "href", hrefValue);
17533
17815
  }
17534
17816
  });
17535
- flattenExtractionTree($clean);
17536
- const root = $clean.root()[0];
17817
+ flattenExtractionTree($);
17818
+ deduplicateImagesInDom($);
17819
+ return $;
17820
+ }
17821
+ function serializePreparedExtractionSnapshot($) {
17822
+ const root = $.root()[0];
17537
17823
  if (root === void 0) {
17538
17824
  return "";
17539
17825
  }
17540
- return deduplicateImages(serializeForExtraction($clean, root));
17826
+ return serializeForExtraction($, root);
17541
17827
  }
17542
- function cleanForAction(html) {
17828
+ function prepareActionSnapshotDom(html) {
17543
17829
  if (!html.trim()) {
17544
- return "";
17830
+ return void 0;
17545
17831
  }
17546
17832
  const $ = cheerio__namespace.load(html, { xmlMode: false });
17547
17833
  removeNoise($);
@@ -17550,13 +17836,12 @@ function cleanForAction(html) {
17550
17836
  pruneSelfHiddenNodes($);
17551
17837
  const clickableMark = "data-clickable-marker";
17552
17838
  const indicatorMark = "data-keep-indicator";
17553
- const branchMark = "data-keep-branch";
17554
17839
  const context = {
17555
17840
  hasPreMarked: $(`[${OPENSTEER_INTERACTIVE_ATTR}]`).length > 0
17556
17841
  };
17557
17842
  $("*").each(function markClickables() {
17558
17843
  const el = $(this);
17559
- if (isClickable($, el, context)) {
17844
+ if (isClickable(el, context)) {
17560
17845
  el.attr(clickableMark, "1");
17561
17846
  }
17562
17847
  });
@@ -17586,25 +17871,7 @@ function cleanForAction(html) {
17586
17871
  el.remove();
17587
17872
  }
17588
17873
  });
17589
- $(`[${clickableMark}], [${indicatorMark}]`).each(function markBranches() {
17590
- let current = $(this).parent();
17591
- while (current.length > 0) {
17592
- const node = current[0];
17593
- if (!node || node.type !== "tag") {
17594
- break;
17595
- }
17596
- const ancestor = current;
17597
- const tag = (node.tagName || "").toLowerCase();
17598
- if (ROOT_TAGS.has(tag) || ancestor.attr(clickableMark) !== void 0) {
17599
- break;
17600
- }
17601
- if (!isBoundaryTag(tag)) {
17602
- ancestor.attr(branchMark, "1");
17603
- }
17604
- current = ancestor.parent();
17605
- }
17606
- });
17607
- for (const node of getElementsByDepthDescending($)) {
17874
+ for (const node of getElementsInReverseDocumentOrder($)) {
17608
17875
  const el = $(node);
17609
17876
  const tag = (node.tagName || "").toLowerCase();
17610
17877
  if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
@@ -17613,17 +17880,7 @@ function cleanForAction(html) {
17613
17880
  if (el.attr(clickableMark) !== void 0 || el.attr(indicatorMark) !== void 0) {
17614
17881
  continue;
17615
17882
  }
17616
- const insideClickable = hasMarkedAncestor(el, clickableMark);
17617
- const preserveBranch = el.attr(branchMark) !== void 0;
17618
17883
  const hasContent = hasElementChildren(node) || hasDirectText(node);
17619
- if (insideClickable || preserveBranch) {
17620
- if (!hasContent) {
17621
- el.remove();
17622
- } else {
17623
- unwrapActionNode($, el);
17624
- }
17625
- continue;
17626
- }
17627
17884
  if (!hasContent) {
17628
17885
  el.remove();
17629
17886
  continue;
@@ -17724,13 +17981,20 @@ function cleanForAction(html) {
17724
17981
  }
17725
17982
  el.removeAttr(clickableMark);
17726
17983
  el.removeAttr(indicatorMark);
17727
- el.removeAttr(branchMark);
17728
17984
  el.removeAttr(OPENSTEER_INTERACTIVE_ATTR);
17729
17985
  el.removeAttr(OPENSTEER_HIDDEN_ATTR);
17730
17986
  el.removeAttr(OPENSTEER_SCROLLABLE_ATTR);
17731
17987
  el.removeAttr(OPENSTEER_SELF_HIDDEN_ATTR);
17732
17988
  });
17733
- return compactHtml(deduplicateImages($.html()));
17989
+ deduplicateImagesInDom($);
17990
+ return $;
17991
+ }
17992
+ function serializePreparedActionSnapshot($) {
17993
+ const normalized = compactHtml($.html());
17994
+ if (normalized.length === 0) {
17995
+ return "";
17996
+ }
17997
+ return cheerio__namespace.load(normalized, { xmlMode: false }).html();
17734
17998
  }
17735
17999
  var VOID_TAGS2 = /* @__PURE__ */ new Set([
17736
18000
  "area",
@@ -17953,27 +18217,32 @@ async function markLiveSnapshotSemantics(options) {
17953
18217
  const frames = await options.engine.listFrames({
17954
18218
  pageRef: options.pageRef
17955
18219
  });
17956
- for (const frame of frames) {
17957
- await evaluateFrameBestEffort(
17958
- options.engine,
17959
- frame.frameRef,
17960
- MARK_SNAPSHOT_SEMANTICS_SCRIPT,
17961
- SNAPSHOT_SEMANTIC_ARGS
17962
- );
17963
- }
17964
- return async () => {
17965
- for (const frame of frames) {
17966
- await evaluateFrameBestEffort(
18220
+ await Promise.all(
18221
+ frames.map(
18222
+ (frame) => evaluateFrameBestEffort(
17967
18223
  options.engine,
17968
18224
  frame.frameRef,
17969
- CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
17970
- CLEAR_SNAPSHOT_SEMANTIC_ARGS
17971
- );
17972
- }
18225
+ MARK_SNAPSHOT_SEMANTICS_SCRIPT,
18226
+ SNAPSHOT_SEMANTIC_ARGS
18227
+ )
18228
+ )
18229
+ );
18230
+ return async () => {
18231
+ await Promise.all(
18232
+ frames.map(
18233
+ (frame) => evaluateFrameBestEffort(
18234
+ options.engine,
18235
+ frame.frameRef,
18236
+ CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
18237
+ CLEAR_SNAPSHOT_SEMANTIC_ARGS
18238
+ )
18239
+ )
18240
+ );
17973
18241
  };
17974
18242
  }
17975
18243
 
17976
18244
  // ../runtime-core/src/sdk/snapshot/compiler.ts
18245
+ var EXTRACTION_SKIPPED_COUNTER_TAGS = /* @__PURE__ */ new Set(["html", "head", "body"]);
17977
18246
  var INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
17978
18247
  "c",
17979
18248
  OPENSTEER_BOUNDARY_ATTR,
@@ -17988,7 +18257,7 @@ var INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
17988
18257
  var MAX_LIVE_COUNTER_SYNC_ATTEMPTS = 4;
17989
18258
  var CLEAR_LIVE_COUNTERS_SCRIPT = `(({ sparseCounterAttr }) => {
17990
18259
  const walk = (root) => {
17991
- for (const child of root.children) {
18260
+ for (const child of Array.from(root?.children || [])) {
17992
18261
  child.removeAttribute("c");
17993
18262
  child.removeAttribute(sparseCounterAttr);
17994
18263
  walk(child);
@@ -18004,7 +18273,7 @@ var CLEAR_LIVE_COUNTERS_SCRIPT = `(({ sparseCounterAttr }) => {
18004
18273
  var ASSIGN_SPARSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, startCounter }) => {
18005
18274
  let counter = startCounter;
18006
18275
  const walk = (root) => {
18007
- for (const child of root.children) {
18276
+ for (const child of Array.from(root?.children || [])) {
18008
18277
  child.setAttribute(sparseCounterAttr, String(counter++));
18009
18278
  walk(child);
18010
18279
  if (child.shadowRoot) {
@@ -18018,7 +18287,7 @@ var ASSIGN_SPARSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, startCounter }) => {
18018
18287
  })`;
18019
18288
  var APPLY_DENSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, mapping }) => {
18020
18289
  const walk = (root) => {
18021
- for (const child of root.children) {
18290
+ for (const child of Array.from(root?.children || [])) {
18022
18291
  child.removeAttribute("c");
18023
18292
  const sparse = child.getAttribute(sparseCounterAttr);
18024
18293
  if (sparse !== null) {
@@ -18081,20 +18350,22 @@ function ensureSparseCountersForAllRecords(counterRecords) {
18081
18350
  async function clearOpensteerLiveCounters(engine, pageRef) {
18082
18351
  const frames = await engine.listFrames({ pageRef });
18083
18352
  const failures = [];
18084
- for (const frame of frames) {
18085
- try {
18086
- await engine.evaluateFrame({
18087
- frameRef: frame.frameRef,
18088
- script: CLEAR_LIVE_COUNTERS_SCRIPT,
18089
- args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
18090
- });
18091
- } catch (error) {
18092
- if (isDetachedFrameSyncError(error)) {
18093
- continue;
18353
+ await Promise.all(
18354
+ frames.map(async (frame) => {
18355
+ try {
18356
+ await engine.evaluateFrame({
18357
+ frameRef: frame.frameRef,
18358
+ script: CLEAR_LIVE_COUNTERS_SCRIPT,
18359
+ args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
18360
+ });
18361
+ } catch (error) {
18362
+ if (isDetachedFrameSyncError(error)) {
18363
+ return;
18364
+ }
18365
+ failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
18094
18366
  }
18095
- failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
18096
- }
18097
- }
18367
+ })
18368
+ );
18098
18369
  if (failures.length > 0) {
18099
18370
  throw buildLiveCounterSyncError("clear live counters", failures);
18100
18371
  }
@@ -18143,25 +18414,29 @@ async function syncDenseCountersToLiveDom(engine, pageRef, sparseToDirectMapping
18143
18414
  denseCounter
18144
18415
  ])
18145
18416
  );
18146
- for (const frame of frames) {
18147
- try {
18148
- await engine.evaluateFrame({
18149
- frameRef: frame.frameRef,
18150
- script: APPLY_DENSE_COUNTERS_SCRIPT,
18151
- args: [
18152
- {
18153
- sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
18154
- mapping: mappingObj
18155
- }
18156
- ]
18157
- });
18158
- } catch (error) {
18159
- if (isDetachedFrameSyncError(error)) {
18160
- continue;
18417
+ await Promise.all(
18418
+ frames.map(async (frame) => {
18419
+ try {
18420
+ await engine.evaluateFrame({
18421
+ frameRef: frame.frameRef,
18422
+ script: APPLY_DENSE_COUNTERS_SCRIPT,
18423
+ args: [
18424
+ {
18425
+ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
18426
+ mapping: mappingObj
18427
+ }
18428
+ ]
18429
+ });
18430
+ } catch (error) {
18431
+ if (isDetachedFrameSyncError(error)) {
18432
+ return;
18433
+ }
18434
+ failures.push(
18435
+ `frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`
18436
+ );
18161
18437
  }
18162
- failures.push(`frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`);
18163
- }
18164
- }
18438
+ })
18439
+ );
18165
18440
  if (failures.length > 0) {
18166
18441
  throw buildLiveCounterSyncError("synchronize dense counters", failures);
18167
18442
  }
@@ -18179,26 +18454,26 @@ async function compileOpensteerSnapshot(options) {
18179
18454
  await clearOpensteerLiveCounters(options.engine, options.pageRef);
18180
18455
  await assignSparseCountersToLiveDom(options.engine, options.pageRef);
18181
18456
  const pageInfo = await options.engine.getPageInfo({ pageRef: options.pageRef });
18182
- const mainSnapshot = await getMainDocumentSnapshot(options.engine, options.pageRef);
18183
- const snapshotsByDocumentRef = await collectDocumentSnapshots(options.engine, mainSnapshot);
18457
+ const { mainSnapshot, snapshotsByDocumentRef } = await getPageDocumentSnapshots(
18458
+ options.engine,
18459
+ options.pageRef
18460
+ );
18184
18461
  await cleanupLiveSemantics();
18185
18462
  cleanupLiveSemantics = async () => {
18186
18463
  };
18187
- const snapshotIndices = /* @__PURE__ */ new Map();
18188
18464
  const renderedNodes = /* @__PURE__ */ new Map();
18189
18465
  const rawHtml = renderDocumentSnapshot(
18190
18466
  mainSnapshot.documentRef,
18191
18467
  snapshotsByDocumentRef,
18192
- snapshotIndices,
18193
18468
  renderedNodes,
18194
18469
  {
18195
18470
  iframeDepth: 0,
18196
18471
  shadowDepth: 0
18197
18472
  }
18198
18473
  );
18199
- const cleanedHtml = options.mode === "extraction" ? cleanForExtraction(rawHtml) : cleanForAction(rawHtml);
18200
- const compiledHtml = assignCounters(cleanedHtml, renderedNodes);
18201
- const finalHtml = options.mode === "extraction" ? unwrapExtractionHtml(compiledHtml.html) : compiledHtml.html;
18474
+ const preparedSnapshotDom = options.mode === "extraction" ? prepareExtractionSnapshotDom(rawHtml) : prepareActionSnapshotDom(rawHtml);
18475
+ const compiledHtml = assignCountersInDom(preparedSnapshotDom, renderedNodes, options.mode);
18476
+ const finalHtml = preparedSnapshotDom === void 0 ? "" : options.mode === "extraction" ? serializePreparedExtractionSnapshot(preparedSnapshotDom) : serializePreparedActionSnapshot(preparedSnapshotDom);
18202
18477
  ensureSparseCountersForAllRecords(compiledHtml.counterRecords);
18203
18478
  await syncDenseCountersToLiveDom(
18204
18479
  options.engine,
@@ -18233,6 +18508,25 @@ async function getMainDocumentSnapshot(engine, pageRef) {
18233
18508
  }
18234
18509
  return engine.getDomSnapshot({ frameRef: mainFrame.frameRef });
18235
18510
  }
18511
+ async function getPageDocumentSnapshots(engine, pageRef) {
18512
+ const bundleEngine = engine;
18513
+ const bundledSnapshots = await bundleEngine.getPageDomSnapshots?.({ pageRef });
18514
+ if (bundledSnapshots && bundledSnapshots.length > 0) {
18515
+ const mainSnapshot2 = bundledSnapshots.find((snapshot) => snapshot.parentDocumentRef === void 0) ?? bundledSnapshots[0];
18516
+ return {
18517
+ mainSnapshot: mainSnapshot2,
18518
+ snapshotsByDocumentRef: new Map(
18519
+ bundledSnapshots.map((snapshot) => [snapshot.documentRef, snapshot])
18520
+ )
18521
+ };
18522
+ }
18523
+ const mainSnapshot = await getMainDocumentSnapshot(engine, pageRef);
18524
+ const snapshotsByDocumentRef = await collectDocumentSnapshots(engine, mainSnapshot);
18525
+ return {
18526
+ mainSnapshot,
18527
+ snapshotsByDocumentRef
18528
+ };
18529
+ }
18236
18530
  async function collectDocumentSnapshots(engine, mainSnapshot) {
18237
18531
  const snapshotsByDocumentRef = /* @__PURE__ */ new Map([
18238
18532
  [mainSnapshot.documentRef, mainSnapshot]
@@ -18251,7 +18545,7 @@ async function collectDocumentSnapshots(engine, mainSnapshot) {
18251
18545
  }
18252
18546
  return snapshotsByDocumentRef;
18253
18547
  }
18254
- function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18548
+ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, renderedNodes, depth) {
18255
18549
  const snapshot = snapshotsByDocumentRef.get(documentRef);
18256
18550
  if (!snapshot) {
18257
18551
  return "";
@@ -18263,17 +18557,9 @@ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotInd
18263
18557
  `snapshot ${snapshot.documentRef} is missing root node ${String(snapshot.rootSnapshotNodeId)}`
18264
18558
  );
18265
18559
  }
18266
- return renderNode(
18267
- snapshot,
18268
- rootNode,
18269
- nodesById,
18270
- snapshotsByDocumentRef,
18271
- snapshotIndices,
18272
- renderedNodes,
18273
- depth
18274
- );
18560
+ return renderNode(snapshot, rootNode, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18275
18561
  }
18276
- function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18562
+ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
18277
18563
  if (node.nodeType === 3) {
18278
18564
  return escapeHtml(node.nodeValue || node.textContent || "");
18279
18565
  }
@@ -18281,56 +18567,26 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18281
18567
  return "";
18282
18568
  }
18283
18569
  if (node.nodeType === 9 || node.nodeType === 11) {
18284
- return renderChildren(
18285
- snapshot,
18286
- node,
18287
- nodesById,
18288
- snapshotsByDocumentRef,
18289
- snapshotIndices,
18290
- renderedNodes,
18291
- depth
18292
- );
18570
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18293
18571
  }
18294
18572
  if (node.nodeType !== 1) {
18295
- return renderChildren(
18296
- snapshot,
18297
- node,
18298
- nodesById,
18299
- snapshotsByDocumentRef,
18300
- snapshotIndices,
18301
- renderedNodes,
18302
- depth
18303
- );
18573
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18304
18574
  }
18305
18575
  const tagName = normalizeTagName(node.nodeName);
18306
18576
  if (isPseudoElementTagName(tagName)) {
18307
- return renderChildren(
18308
- snapshot,
18309
- node,
18310
- nodesById,
18311
- snapshotsByDocumentRef,
18312
- snapshotIndices,
18313
- renderedNodes,
18314
- depth
18315
- );
18577
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18316
18578
  }
18317
18579
  if ((depth.iframeDepth > 0 || depth.shadowDepth > 0) && (tagName === "html" || tagName === "head" || tagName === "body")) {
18318
- return renderChildren(
18319
- snapshot,
18320
- node,
18321
- nodesById,
18322
- snapshotsByDocumentRef,
18323
- snapshotIndices,
18324
- renderedNodes,
18325
- depth
18326
- );
18580
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18327
18581
  }
18328
18582
  const snapshotAttributes = normalizeNodeAttributes(node.attributes);
18583
+ const snapshotAttributeIndex = indexNodeAttributes(snapshotAttributes);
18329
18584
  const authoredAttributes = stripInternalSnapshotAttributes(snapshotAttributes);
18585
+ const authoredAttributeIndex = indexNodeAttributes(authoredAttributes);
18330
18586
  const attributes = [...authoredAttributes];
18331
- const subtreeHidden = hasAttribute3(snapshotAttributes, OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
18332
- const selfHidden = !subtreeHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
18333
- const interactive = !subtreeHidden && !selfHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes));
18587
+ const subtreeHidden = snapshotAttributeIndex.has(OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
18588
+ const selfHidden = !subtreeHidden && (snapshotAttributeIndex.has(OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
18589
+ const interactive = !subtreeHidden && !selfHidden && (snapshotAttributeIndex.has(OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes, authoredAttributeIndex));
18334
18590
  if (interactive) {
18335
18591
  attributes.push({ name: OPENSTEER_INTERACTIVE_ATTR, value: "1" });
18336
18592
  }
@@ -18339,7 +18595,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18339
18595
  } else if (selfHidden) {
18340
18596
  attributes.push({ name: OPENSTEER_SELF_HIDDEN_ATTR, value: "1" });
18341
18597
  }
18342
- const sparseCounter = findAttributeValue(snapshotAttributes, OPENSTEER_SPARSE_COUNTER_ATTR);
18598
+ const sparseCounter = snapshotAttributeIndex.get(OPENSTEER_SPARSE_COUNTER_ATTR);
18343
18599
  if (sparseCounter !== void 0) {
18344
18600
  attributes.push({ name: OPENSTEER_SPARSE_COUNTER_ATTR, value: sparseCounter });
18345
18601
  }
@@ -18350,21 +18606,18 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18350
18606
  const syntheticNodeId = buildSyntheticNodeId(snapshot, node);
18351
18607
  attributes.push({ name: OPENSTEER_NODE_ID_ATTR, value: syntheticNodeId });
18352
18608
  renderedNodes.set(syntheticNodeId, {
18353
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
18354
- anchor: buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices),
18355
18609
  pageRef: snapshot.pageRef,
18356
18610
  frameRef: snapshot.frameRef,
18357
18611
  documentRef: snapshot.documentRef,
18358
18612
  documentEpoch: snapshot.documentEpoch,
18359
18613
  nodeRef: node.nodeRef,
18360
18614
  tagName: tagName.toUpperCase(),
18361
- pathHint: buildPathHint(tagName, authoredAttributes),
18362
- ...buildTextSnippet(node.textContent) === void 0 ? {} : { text: buildTextSnippet(node.textContent) },
18363
18615
  ...authoredAttributes.length === 0 ? {} : { attributes: authoredAttributes },
18364
18616
  iframeDepth: depth.iframeDepth,
18365
18617
  shadowDepth: depth.shadowDepth,
18366
18618
  interactive,
18367
- liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById)
18619
+ liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById),
18620
+ ...node.textContent === void 0 ? {} : { textContent: node.textContent }
18368
18621
  });
18369
18622
  }
18370
18623
  const attributeText = attributesToHtml(attributes);
@@ -18373,7 +18626,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18373
18626
  node,
18374
18627
  nodesById,
18375
18628
  snapshotsByDocumentRef,
18376
- snapshotIndices,
18377
18629
  renderedNodes,
18378
18630
  depth
18379
18631
  );
@@ -18384,7 +18636,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18384
18636
  const iframeHtml = renderDocumentSnapshot(
18385
18637
  node.contentDocumentRef,
18386
18638
  snapshotsByDocumentRef,
18387
- snapshotIndices,
18388
18639
  renderedNodes,
18389
18640
  {
18390
18641
  iframeDepth: depth.iframeDepth + 1,
@@ -18396,7 +18647,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18396
18647
  }
18397
18648
  return `${elementHtml}<${OPENSTEER_IFRAME_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="iframe">${iframeHtml}</${OPENSTEER_IFRAME_BOUNDARY_TAG}>`;
18398
18649
  }
18399
- function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18650
+ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
18400
18651
  const regularChildren = [];
18401
18652
  const shadowChildren = [];
18402
18653
  for (const childSnapshotNodeId of node.childSnapshotNodeIds) {
@@ -18413,18 +18664,10 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
18413
18664
  const chunks = [];
18414
18665
  if (shadowChildren.length > 0) {
18415
18666
  const shadowHtml = shadowChildren.map(
18416
- (child) => renderNode(
18417
- snapshot,
18418
- child,
18419
- nodesById,
18420
- snapshotsByDocumentRef,
18421
- snapshotIndices,
18422
- renderedNodes,
18423
- {
18424
- iframeDepth: depth.iframeDepth,
18425
- shadowDepth: depth.shadowDepth + 1
18426
- }
18427
- )
18667
+ (child) => renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, {
18668
+ iframeDepth: depth.iframeDepth,
18669
+ shadowDepth: depth.shadowDepth + 1
18670
+ })
18428
18671
  ).join("");
18429
18672
  chunks.push(
18430
18673
  `<${OPENSTEER_SHADOW_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="shadow">${shadowHtml}</${OPENSTEER_SHADOW_BOUNDARY_TAG}>`
@@ -18432,24 +18675,21 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
18432
18675
  }
18433
18676
  for (const child of regularChildren) {
18434
18677
  chunks.push(
18435
- renderNode(
18436
- snapshot,
18437
- child,
18438
- nodesById,
18439
- snapshotsByDocumentRef,
18440
- snapshotIndices,
18441
- renderedNodes,
18442
- depth
18443
- )
18678
+ renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, depth)
18444
18679
  );
18445
18680
  }
18446
18681
  return chunks.join("");
18447
18682
  }
18448
- function assignCounters(cleanedHtml, renderedNodes) {
18449
- const $ = cheerio__namespace.load(cleanedHtml, { xmlMode: false });
18683
+ function assignCountersInDom($, renderedNodes, mode) {
18450
18684
  const counterRecords = /* @__PURE__ */ new Map();
18451
18685
  const sparseToDirectMapping = /* @__PURE__ */ new Map();
18452
18686
  let nextCounter = 1;
18687
+ if (!$) {
18688
+ return {
18689
+ counterRecords,
18690
+ sparseToDirectMapping
18691
+ };
18692
+ }
18453
18693
  $("*").each(function assignElementCounter() {
18454
18694
  const el = $(this);
18455
18695
  const syntheticNodeId = el.attr(OPENSTEER_NODE_ID_ATTR);
@@ -18461,14 +18701,24 @@ function assignCounters(cleanedHtml, renderedNodes) {
18461
18701
  if (!rendered) {
18462
18702
  return;
18463
18703
  }
18704
+ if (mode === "extraction" && EXTRACTION_SKIPPED_COUNTER_TAGS.has(rendered.tagName.toLowerCase())) {
18705
+ el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
18706
+ return;
18707
+ }
18464
18708
  const rawSparseCounter = el.attr(OPENSTEER_SPARSE_COUNTER_ATTR);
18465
18709
  el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
18466
18710
  const sparseCounter = rawSparseCounter ? Number.parseInt(rawSparseCounter, 10) : void 0;
18711
+ const replayableSparseCounter = typeof sparseCounter === "number" && Number.isFinite(sparseCounter) ? sparseCounter : void 0;
18712
+ if (rendered.liveCounterSyncEligible && replayableSparseCounter === void 0) {
18713
+ return;
18714
+ }
18467
18715
  const counter = nextCounter++;
18468
18716
  el.attr("c", String(counter));
18469
- if (sparseCounter !== void 0 && Number.isFinite(sparseCounter)) {
18470
- sparseToDirectMapping.set(sparseCounter, counter);
18717
+ if (replayableSparseCounter !== void 0) {
18718
+ sparseToDirectMapping.set(replayableSparseCounter, counter);
18471
18719
  }
18720
+ const pathHint = buildPathHint(rendered.tagName.toLowerCase(), rendered.attributes ?? []);
18721
+ const text = buildTextSnippet(rendered.textContent);
18472
18722
  counterRecords.set(counter, {
18473
18723
  element: counter,
18474
18724
  pageRef: rendered.pageRef,
@@ -18477,20 +18727,17 @@ function assignCounters(cleanedHtml, renderedNodes) {
18477
18727
  documentEpoch: rendered.documentEpoch,
18478
18728
  nodeRef: rendered.nodeRef,
18479
18729
  tagName: rendered.tagName,
18480
- pathHint: rendered.pathHint,
18481
- ...rendered.text === void 0 ? {} : { text: rendered.text },
18730
+ pathHint,
18731
+ ...text === void 0 ? {} : { text },
18482
18732
  ...rendered.attributes === void 0 ? {} : { attributes: rendered.attributes },
18483
18733
  iframeDepth: rendered.iframeDepth,
18484
18734
  shadowDepth: rendered.shadowDepth,
18485
18735
  interactive: rendered.interactive,
18486
18736
  liveCounterSyncEligible: rendered.liveCounterSyncEligible,
18487
- locator: rendered.locator,
18488
- anchor: rendered.anchor,
18489
- ...sparseCounter !== void 0 && Number.isFinite(sparseCounter) ? { sparseCounter } : {}
18737
+ ...replayableSparseCounter === void 0 ? {} : { sparseCounter: replayableSparseCounter }
18490
18738
  });
18491
18739
  });
18492
18740
  return {
18493
- html: $.html(),
18494
18741
  counterRecords,
18495
18742
  sparseToDirectMapping
18496
18743
  };
@@ -18564,28 +18811,28 @@ function isLikelySelfHidden(node, nodesById) {
18564
18811
  }
18565
18812
  return !hasVisibleOutOfFlowChild(node, nodesById);
18566
18813
  }
18567
- function isLikelyInteractive(tagName, node, attributes) {
18814
+ function isLikelyInteractive(tagName, node, attributes, attributeIndex) {
18568
18815
  if (NATIVE_INTERACTIVE_TAGS.has(tagName)) {
18569
- if (tagName === "input" && findAttributeValue(attributes, "type")?.toLowerCase() === "hidden") {
18816
+ if (tagName === "input" && attributeIndex.get("type")?.toLowerCase() === "hidden") {
18570
18817
  return false;
18571
18818
  }
18572
18819
  if (tagName !== "a") {
18573
18820
  return true;
18574
18821
  }
18575
18822
  }
18576
- if (tagName === "a" && findAttributeValue(attributes, "href") !== void 0) {
18823
+ if (tagName === "a" && attributeIndex.has("href")) {
18577
18824
  return true;
18578
18825
  }
18579
- 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) {
18826
+ if (attributeIndex.has("onclick") || attributeIndex.has("onmousedown") || attributeIndex.has("onmouseup") || attributeIndex.has("data-action") || attributeIndex.has("data-click") || attributeIndex.has("data-toggle")) {
18580
18827
  return true;
18581
18828
  }
18582
- if (hasNonNegativeTabIndex(findAttributeValue(attributes, "tabindex"))) {
18829
+ if (hasNonNegativeTabIndex(attributeIndex.get("tabindex"))) {
18583
18830
  return true;
18584
18831
  }
18585
- if (findAttributeValue(attributes, "contenteditable")?.toLowerCase() === "true") {
18832
+ if (attributeIndex.get("contenteditable")?.toLowerCase() === "true") {
18586
18833
  return true;
18587
18834
  }
18588
- const role = findAttributeValue(attributes, "role")?.toLowerCase();
18835
+ const role = attributeIndex.get("role")?.toLowerCase();
18589
18836
  return role !== void 0 && INTERACTIVE_ROLE_SET.has(role);
18590
18837
  }
18591
18838
  function hasVisibleOutOfFlowChild(node, nodesById) {
@@ -18648,14 +18895,6 @@ function parseOpacity(value) {
18648
18895
  const parsed = Number.parseFloat(value);
18649
18896
  return Number.isFinite(parsed) ? parsed : Number.NaN;
18650
18897
  }
18651
- function hasAttribute3(attributes, name) {
18652
- const normalizedName = name.toLowerCase();
18653
- return attributes.some((attribute) => attribute.name.toLowerCase() === normalizedName);
18654
- }
18655
- function unwrapExtractionHtml(html) {
18656
- const $ = cheerio__namespace.load(html, { xmlMode: false });
18657
- return $("body").html()?.trim() || html;
18658
- }
18659
18898
  function buildSyntheticNodeId(snapshot, node) {
18660
18899
  return `${snapshot.documentRef}:${String(snapshot.documentEpoch)}:${String(node.snapshotNodeId)}`;
18661
18900
  }
@@ -18702,6 +18941,13 @@ function findAttributeValue(attributes, name) {
18702
18941
  const normalizedName = name.toLowerCase();
18703
18942
  return attributes.find((attribute) => attribute.name.toLowerCase() === normalizedName)?.value;
18704
18943
  }
18944
+ function indexNodeAttributes(attributes) {
18945
+ const indexed = /* @__PURE__ */ new Map();
18946
+ for (const attribute of attributes) {
18947
+ indexed.set(attribute.name.toLowerCase(), attribute.value);
18948
+ }
18949
+ return indexed;
18950
+ }
18705
18951
  function attributesToHtml(attributes) {
18706
18952
  if (attributes.length === 0) {
18707
18953
  return "";
@@ -18711,56 +18957,6 @@ function attributesToHtml(attributes) {
18711
18957
  function escapeAttribute(value) {
18712
18958
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
18713
18959
  }
18714
- function buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices) {
18715
- const index = getSnapshotIndex(snapshot.documentRef, snapshotsByDocumentRef, snapshotIndices);
18716
- const localAnchor = buildLocalStructuralElementAnchor(index, node);
18717
- return prefixIframeContext(snapshot, localAnchor, snapshotsByDocumentRef, snapshotIndices);
18718
- }
18719
- function prefixIframeContext(snapshot, localPath, snapshotsByDocumentRef, snapshotIndices) {
18720
- if (snapshot.parentDocumentRef === void 0) {
18721
- return sanitizeStructuralElementAnchor(localPath);
18722
- }
18723
- const parentSnapshot = snapshotsByDocumentRef.get(snapshot.parentDocumentRef);
18724
- if (!parentSnapshot) {
18725
- throw new Error(
18726
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no parent snapshot`
18727
- );
18728
- }
18729
- const parentIndex = getSnapshotIndex(
18730
- parentSnapshot.documentRef,
18731
- snapshotsByDocumentRef,
18732
- snapshotIndices
18733
- );
18734
- const iframeHost = findIframeHostNode(parentIndex, snapshot.documentRef);
18735
- if (!iframeHost) {
18736
- throw new Error(
18737
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no iframe host`
18738
- );
18739
- }
18740
- const hostPath = buildSnapshotElementAnchor(
18741
- parentSnapshot,
18742
- iframeHost,
18743
- snapshotsByDocumentRef,
18744
- snapshotIndices
18745
- );
18746
- return sanitizeStructuralElementAnchor({
18747
- context: [...hostPath.context, { kind: "iframe", host: hostPath.nodes }, ...localPath.context],
18748
- nodes: localPath.nodes
18749
- });
18750
- }
18751
- function getSnapshotIndex(documentRef, snapshotsByDocumentRef, snapshotIndices) {
18752
- const existing = snapshotIndices.get(documentRef);
18753
- if (existing) {
18754
- return existing;
18755
- }
18756
- const snapshot = snapshotsByDocumentRef.get(documentRef);
18757
- if (!snapshot) {
18758
- throw new Error(`missing DOM snapshot for ${documentRef}`);
18759
- }
18760
- const index = createSnapshotIndex(snapshot);
18761
- snapshotIndices.set(documentRef, index);
18762
- return index;
18763
- }
18764
18960
  function escapeHtml(value) {
18765
18961
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18766
18962
  }
@@ -19973,7 +20169,7 @@ var OpensteerSessionRuntime = class {
19973
20169
  options
19974
20170
  );
19975
20171
  }
19976
- return this.readSessionState();
20172
+ return this.readNavigationSummary();
19977
20173
  }
19978
20174
  const startedAt = Date.now();
19979
20175
  const root = await this.ensureRoot();
@@ -19994,7 +20190,8 @@ var OpensteerSessionRuntime = class {
19994
20190
  openedSessionRef = sessionRef;
19995
20191
  const createdPage = await timeout.runStep(
19996
20192
  () => engine.createPage({
19997
- sessionRef
20193
+ sessionRef,
20194
+ ...input.url === void 0 ? {} : { url: input.url }
19998
20195
  })
19999
20196
  );
20000
20197
  openedPageRef = createdPage.data.pageRef;
@@ -20004,18 +20201,19 @@ var OpensteerSessionRuntime = class {
20004
20201
  await timeout.runStep(() => this.ensureSemantics());
20005
20202
  let frameRef2 = createdPage.frameRef;
20006
20203
  if (input.url !== void 0) {
20007
- const navigation = await this.navigatePage(
20008
- {
20204
+ await timeout.runStep(
20205
+ () => settleWithPolicy(this.policy.settle, {
20009
20206
  operation: "session.open",
20207
+ trigger: "navigation",
20208
+ engine: this.requireEngine(),
20010
20209
  pageRef: createdPage.data.pageRef,
20011
- url: input.url
20012
- },
20013
- timeout
20210
+ signal: timeout.signal,
20211
+ remainingMs: timeout.remainingMs()
20212
+ })
20014
20213
  );
20015
- frameRef2 = navigation.data.mainFrame.frameRef;
20016
20214
  }
20017
20215
  return {
20018
- state: await timeout.runStep(() => this.readSessionState()),
20216
+ state: await timeout.runStep(() => this.readNavigationSummary()),
20019
20217
  frameRef: frameRef2
20020
20218
  };
20021
20219
  },
@@ -20108,7 +20306,11 @@ var OpensteerSessionRuntime = class {
20108
20306
  "page.new cannot use openerPageRef before a session exists"
20109
20307
  );
20110
20308
  }
20111
- return this.open(input.url === void 0 ? {} : { url: input.url }, options);
20309
+ const summary = await this.open(input.url === void 0 ? {} : { url: input.url }, options);
20310
+ return {
20311
+ pageRef: await this.ensurePageRef(),
20312
+ ...summary
20313
+ };
20112
20314
  }
20113
20315
  const startedAt = Date.now();
20114
20316
  try {
@@ -20123,7 +20325,7 @@ var OpensteerSessionRuntime = class {
20123
20325
  })
20124
20326
  );
20125
20327
  this.pageRef = created.data.pageRef;
20126
- return this.readSessionState();
20328
+ return this.readCreatedPageOutput(created.data.pageRef);
20127
20329
  },
20128
20330
  options
20129
20331
  );
@@ -20165,7 +20367,7 @@ var OpensteerSessionRuntime = class {
20165
20367
  () => this.requireEngine().activatePage({ pageRef: input.pageRef })
20166
20368
  );
20167
20369
  this.pageRef = input.pageRef;
20168
- return this.readSessionState();
20370
+ return this.readNavigationSummary(input.pageRef);
20169
20371
  },
20170
20372
  options
20171
20373
  );
@@ -20291,7 +20493,7 @@ var OpensteerSessionRuntime = class {
20291
20493
  timeout.throwIfAborted();
20292
20494
  return {
20293
20495
  navigation: navigation2,
20294
- state: await timeout.runStep(() => this.readSessionState())
20496
+ state: await timeout.runStep(() => this.readNavigationSummary(pageRef))
20295
20497
  };
20296
20498
  },
20297
20499
  (diagnostics) => {
@@ -21525,7 +21727,7 @@ var OpensteerSessionRuntime = class {
21525
21727
  let mutationCaptureDiagnostics;
21526
21728
  let boundaryDiagnostics;
21527
21729
  try {
21528
- const { artifacts, output } = await this.runMutationCapturedOperation(
21730
+ const { artifacts, output, result } = await this.runMutationCapturedOperation(
21529
21731
  "computer.execute",
21530
21732
  {
21531
21733
  ...input.captureNetwork === void 0 ? {} : { captureNetwork: input.captureNetwork },
@@ -21543,9 +21745,14 @@ var OpensteerSessionRuntime = class {
21543
21745
  await this.invalidateLiveSnapshotCounters([pageRef, output2.pageRef], timeout);
21544
21746
  this.pageRef = output2.pageRef;
21545
21747
  const artifacts2 = await this.persistComputerArtifacts(output2, timeout);
21748
+ const result2 = {
21749
+ ...await timeout.runStep(() => this.readNavigationSummary(output2.pageRef)),
21750
+ screenshot: artifacts2.screenshot
21751
+ };
21546
21752
  return {
21547
21753
  artifacts: { manifests: artifacts2.manifests },
21548
- output: artifacts2.output
21754
+ output: output2,
21755
+ result: result2
21549
21756
  };
21550
21757
  } catch (error) {
21551
21758
  boundaryDiagnostics ??= takeActionBoundaryDiagnostics(timeout.signal);
@@ -21582,7 +21789,7 @@ var OpensteerSessionRuntime = class {
21582
21789
  documentEpoch: output.screenshot.documentEpoch
21583
21790
  })
21584
21791
  });
21585
- return output;
21792
+ return result;
21586
21793
  } catch (error) {
21587
21794
  await this.appendTrace({
21588
21795
  operation: "computer.execute",
@@ -21730,8 +21937,9 @@ var OpensteerSessionRuntime = class {
21730
21937
  mutationCaptureDiagnostics = diagnostics;
21731
21938
  }
21732
21939
  );
21733
- const output = toOpensteerActionResult(executed.result);
21940
+ const output = toOpensteerActionResult(executed.result.resolved);
21734
21941
  const actionEvents = "events" in executed.result ? executed.result.events : void 0;
21942
+ const resolvedTarget = toOpensteerResolvedTarget2(executed.result.resolved);
21735
21943
  await this.appendTrace({
21736
21944
  operation,
21737
21945
  startedAt,
@@ -21739,8 +21947,13 @@ var OpensteerSessionRuntime = class {
21739
21947
  outcome: "ok",
21740
21948
  ...actionEvents === void 0 ? {} : { events: actionEvents },
21741
21949
  data: {
21742
- target: output.target,
21743
- ...output.point === void 0 ? {} : { point: output.point },
21950
+ target: resolvedTarget,
21951
+ ..."point" in executed.result && executed.result.point !== void 0 ? {
21952
+ point: {
21953
+ x: executed.result.point.x,
21954
+ y: executed.result.point.y
21955
+ }
21956
+ } : {},
21744
21957
  ...boundaryDiagnostics === void 0 ? {} : { settle: boundaryDiagnostics },
21745
21958
  ...buildMutationCaptureTraceData(mutationCaptureDiagnostics)
21746
21959
  },
@@ -23205,20 +23418,20 @@ var OpensteerSessionRuntime = class {
23205
23418
  throw error;
23206
23419
  }
23207
23420
  }
23208
- async readSessionState() {
23209
- const pageRef = await this.ensurePageRef();
23421
+ async readNavigationSummary(targetPageRef) {
23422
+ const pageRef = targetPageRef ?? await this.ensurePageRef();
23210
23423
  const pageInfo = await this.requireEngine().getPageInfo({ pageRef });
23211
- const sessionRef = this.sessionRef;
23212
- if (!sessionRef) {
23213
- throw new Error("Opensteer session is not initialized");
23214
- }
23215
23424
  return {
23216
- sessionRef,
23217
- pageRef,
23218
23425
  url: pageInfo.url,
23219
23426
  title: pageInfo.title
23220
23427
  };
23221
23428
  }
23429
+ async readCreatedPageOutput(pageRef) {
23430
+ return {
23431
+ pageRef,
23432
+ ...await this.readNavigationSummary(pageRef)
23433
+ };
23434
+ }
23222
23435
  async captureSnapshotArtifacts(pageRef, options, timeout) {
23223
23436
  const root = this.requireRoot();
23224
23437
  const mainFrame = await timeout.runStep(() => getMainFrame(this.requireEngine(), pageRef));
@@ -23290,12 +23503,12 @@ var OpensteerSessionRuntime = class {
23290
23503
  const screenshotPayload = manifestToExternalBinaryLocation(root.rootPath, screenshotManifest);
23291
23504
  return {
23292
23505
  manifests,
23293
- output: {
23294
- ...output,
23295
- screenshot: {
23296
- ...output.screenshot,
23297
- payload: screenshotPayload
23298
- }
23506
+ screenshot: {
23507
+ payload: screenshotPayload,
23508
+ format: output.screenshot.format,
23509
+ size: output.screenshot.size,
23510
+ coordinateSpace: output.screenshot.coordinateSpace,
23511
+ ...output.screenshot.clip === void 0 ? {} : { clip: output.screenshot.clip }
23299
23512
  }
23300
23513
  };
23301
23514
  }
@@ -24818,15 +25031,10 @@ function normalizeNamespace2(value) {
24818
25031
  const normalized = String(value ?? "default").trim();
24819
25032
  return normalized.length === 0 ? "default" : normalized;
24820
25033
  }
24821
- function toOpensteerActionResult(result) {
25034
+ function toOpensteerActionResult(target) {
24822
25035
  return {
24823
- target: toOpensteerResolvedTarget2(result.resolved),
24824
- ...result.point === void 0 ? {} : {
24825
- point: {
24826
- x: result.point.x,
24827
- y: result.point.y
24828
- }
24829
- }
25036
+ tagName: toOpensteerTagName2(target.node.nodeName),
25037
+ ...target.persist === void 0 ? {} : { persist: target.persist }
24830
25038
  };
24831
25039
  }
24832
25040
  function toOpensteerResolvedTarget2(target) {
@@ -24836,14 +25044,18 @@ function toOpensteerResolvedTarget2(target) {
24836
25044
  documentRef: target.documentRef,
24837
25045
  documentEpoch: target.documentEpoch,
24838
25046
  nodeRef: target.nodeRef,
24839
- tagName: target.node.nodeName.toUpperCase(),
25047
+ tagName: toOpensteerTagName2(target.node.nodeName),
24840
25048
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
24841
25049
  ...target.persist === void 0 ? {} : { persist: target.persist },
24842
25050
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
24843
25051
  };
24844
25052
  }
25053
+ function toOpensteerTagName2(nodeName) {
25054
+ const tagName = String(nodeName).trim().toLowerCase();
25055
+ return tagName.length === 0 ? "element" : tagName;
25056
+ }
24845
25057
  function normalizeOpensteerError(error) {
24846
- return normalizeThrownOpensteerError(error, "Unknown Opensteer runtime failure");
25058
+ return normalizeThrownOpensteerError2(error, "Unknown Opensteer runtime failure");
24847
25059
  }
24848
25060
  function observationArtifactKindFromManifest(kind) {
24849
25061
  switch (kind) {
@@ -25543,6 +25755,7 @@ function payloadByteLength(value) {
25543
25755
 
25544
25756
  // src/cloud/session-proxy.ts
25545
25757
  var TEMPORARY_CLOUD_WORKSPACE_PREFIX = "opensteer-cloud-workspace-";
25758
+ var CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS = 1e4;
25546
25759
  var CloudSessionProxy = class {
25547
25760
  rootPath;
25548
25761
  workspace;
@@ -25556,6 +25769,8 @@ var CloudSessionProxy = class {
25556
25769
  automation;
25557
25770
  workspaceStore;
25558
25771
  syncWorkspaceOnClose = false;
25772
+ liveSessionStateEstablished = false;
25773
+ storedInstrumentation = [];
25559
25774
  constructor(cloud, options = {}) {
25560
25775
  this.cloud = cloud;
25561
25776
  this.workspace = options.workspace;
@@ -25585,15 +25800,12 @@ var CloudSessionProxy = class {
25585
25800
  if (this.client === void 0 && this.sessionId === void 0 && persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
25586
25801
  this.bindClient(persisted);
25587
25802
  }
25588
- if (this.automation) {
25589
- try {
25590
- const sessionInfo = await this.automation.getSessionInfo();
25591
- return {
25592
- ...sessionInfo,
25593
- ...this.workspace === void 0 ? {} : { workspace: this.workspace }
25594
- };
25595
- } catch {
25596
- }
25803
+ const sessionInfo = this.automation ? await this.automation.getSessionInfo().catch(() => void 0) : void 0;
25804
+ if (sessionInfo !== void 0) {
25805
+ return {
25806
+ ...sessionInfo,
25807
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace }
25808
+ };
25597
25809
  }
25598
25810
  return {
25599
25811
  provider: {
@@ -25705,12 +25917,26 @@ var CloudSessionProxy = class {
25705
25917
  return this.invokeSemanticOperation("session.cookies", input);
25706
25918
  }
25707
25919
  async route(input) {
25708
- await this.ensureSession();
25709
- return this.requireAutomation().route(input);
25920
+ const registration = await this.invokeBootstrapInstrumentationOperation(
25921
+ "instrumentation.route",
25922
+ (automation) => automation.route(input)
25923
+ );
25924
+ this.storedInstrumentation.push({
25925
+ kind: "route",
25926
+ input
25927
+ });
25928
+ return registration;
25710
25929
  }
25711
25930
  async interceptScript(input) {
25712
- await this.ensureSession();
25713
- return this.requireAutomation().interceptScript(input);
25931
+ const registration = await this.invokeBootstrapInstrumentationOperation(
25932
+ "instrumentation.intercept-script",
25933
+ (automation) => automation.interceptScript(input)
25934
+ );
25935
+ this.storedInstrumentation.push({
25936
+ kind: "intercept-script",
25937
+ input
25938
+ });
25939
+ return registration;
25714
25940
  }
25715
25941
  async getStorageSnapshot(input = {}) {
25716
25942
  return this.invokeSemanticOperation("session.storage", input);
@@ -25758,6 +25984,8 @@ var CloudSessionProxy = class {
25758
25984
  this.client = void 0;
25759
25985
  this.sessionId = void 0;
25760
25986
  this.semanticGrant = void 0;
25987
+ this.liveSessionStateEstablished = false;
25988
+ this.storedInstrumentation.length = 0;
25761
25989
  if (this.cleanupRootOnClose) {
25762
25990
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
25763
25991
  }
@@ -25785,6 +26013,8 @@ var CloudSessionProxy = class {
25785
26013
  this.automation = void 0;
25786
26014
  this.sessionId = void 0;
25787
26015
  this.semanticGrant = void 0;
26016
+ this.liveSessionStateEstablished = false;
26017
+ this.storedInstrumentation.length = 0;
25788
26018
  if (syncError !== void 0) {
25789
26019
  throw syncError;
25790
26020
  }
@@ -25832,6 +26062,7 @@ var CloudSessionProxy = class {
25832
26062
  };
25833
26063
  await this.writePersistedSession(record);
25834
26064
  this.bindClient(record, session.initialGrants?.semantic);
26065
+ await this.restoreStoredInstrumentation();
25835
26066
  }
25836
26067
  async syncWorkspaceToCloud() {
25837
26068
  if (this.workspace === void 0) {
@@ -25843,6 +26074,7 @@ var CloudSessionProxy = class {
25843
26074
  bindClient(record, initialSemanticGrant) {
25844
26075
  this.sessionId = record.sessionId;
25845
26076
  this.semanticGrant = initialSemanticGrant?.kind === "semantic" ? initialSemanticGrant : void 0;
26077
+ this.liveSessionStateEstablished = false;
25846
26078
  this.client = new OpensteerSemanticRestClient({
25847
26079
  getBaseUrl: async () => (await this.ensureSemanticGrant()).url,
25848
26080
  getAuthorizationHeader: async () => `Bearer ${(await this.ensureSemanticGrant()).token}`,
@@ -25850,6 +26082,19 @@ var CloudSessionProxy = class {
25850
26082
  });
25851
26083
  this.automation = new OpensteerCloudAutomationClient(this.cloud, record.sessionId);
25852
26084
  }
26085
+ async restoreStoredInstrumentation() {
26086
+ if (this.storedInstrumentation.length === 0) {
26087
+ return;
26088
+ }
26089
+ const automation = this.requireAutomation();
26090
+ for (const registration of this.storedInstrumentation) {
26091
+ if (registration.kind === "route") {
26092
+ await automation.route(registration.input);
26093
+ } else {
26094
+ await automation.interceptScript(registration.input);
26095
+ }
26096
+ }
26097
+ }
25853
26098
  async ensureWorkspaceStore() {
25854
26099
  if (this.workspaceStore !== void 0) {
25855
26100
  return this.workspaceStore;
@@ -25872,13 +26117,22 @@ var CloudSessionProxy = class {
25872
26117
  async clearPersistedSession() {
25873
26118
  await clearPersistedSessionRecord(this.rootPath, "cloud").catch(() => void 0);
25874
26119
  }
26120
+ async invalidateSession() {
26121
+ await this.automation?.close().catch(() => void 0);
26122
+ this.automation = void 0;
26123
+ this.client = void 0;
26124
+ this.sessionId = void 0;
26125
+ this.semanticGrant = void 0;
26126
+ this.liveSessionStateEstablished = false;
26127
+ await this.clearPersistedSession();
26128
+ }
25875
26129
  async isReusableCloudSession(sessionId, timeout) {
25876
26130
  try {
25877
26131
  const session = await this.cloud.getSession(sessionId, {
25878
26132
  signal: timeout?.signal,
25879
26133
  timeoutMs: timeout?.remainingMs()
25880
26134
  });
25881
- return session.status !== "closed" && session.status !== "failed";
26135
+ return isReusableCloudSessionState(session);
25882
26136
  } catch (error) {
25883
26137
  if (isMissingCloudSessionError(error)) {
25884
26138
  return false;
@@ -25927,26 +26181,79 @@ var CloudSessionProxy = class {
25927
26181
  try {
25928
26182
  await this.ensureSemanticGrant(true);
25929
26183
  return true;
25930
- } catch {
26184
+ } catch (refreshError) {
26185
+ if (await this.resetStaleSession(refreshError)) {
26186
+ throw refreshError;
26187
+ }
25931
26188
  return false;
25932
26189
  }
25933
26190
  }
25934
26191
  async invokeSemanticOperation(operation, input, sessionInit = {}) {
25935
- return this.runOperationWithPolicy(operation, async (timeout) => {
26192
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
25936
26193
  await this.ensureSession(sessionInit, timeout);
25937
26194
  await this.ensureSemanticGrant(false, timeout);
25938
- return this.requireClient().invoke(operation, input, {
26195
+ const output = await this.requireClient().invoke(operation, input, {
25939
26196
  signal: timeout.signal,
25940
26197
  timeoutMs: timeout.remainingMs()
25941
26198
  });
26199
+ this.noteSuccessfulLiveOperation(operation);
26200
+ return output;
25942
26201
  });
25943
26202
  }
25944
26203
  async invokeAutomationOperation(operation, invoke, sessionInit = {}) {
25945
- return this.runOperationWithPolicy(operation, async (timeout) => {
26204
+ return this.runOperationWithSessionRecovery(operation, async (timeout) => {
25946
26205
  await this.ensureSession(sessionInit, timeout);
25947
- return invoke(this.requireAutomation());
26206
+ const output = await invoke(this.requireAutomation());
26207
+ this.noteSuccessfulLiveOperation(operation);
26208
+ return output;
26209
+ });
26210
+ }
26211
+ async invokeBootstrapInstrumentationOperation(operation, invoke) {
26212
+ let recovered = false;
26213
+ while (true) {
26214
+ try {
26215
+ await this.ensureSession();
26216
+ return await invoke(this.requireAutomation());
26217
+ } catch (error) {
26218
+ const stale = await this.resetStaleSession(error);
26219
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
26220
+ throw error;
26221
+ }
26222
+ recovered = true;
26223
+ }
26224
+ }
26225
+ }
26226
+ async runOperationWithSessionRecovery(operation, invoke) {
26227
+ return this.runOperationWithPolicy(operation, async (timeout) => {
26228
+ let recovered = false;
26229
+ while (true) {
26230
+ try {
26231
+ return await invoke(timeout);
26232
+ } catch (error) {
26233
+ const stale = await this.resetStaleSession(error);
26234
+ if (!stale || recovered || !this.canRecoverWithFreshSession(operation)) {
26235
+ throw error;
26236
+ }
26237
+ recovered = true;
26238
+ }
26239
+ }
25948
26240
  });
25949
26241
  }
26242
+ async resetStaleSession(error) {
26243
+ if (!isRecoverableCloudSessionError(error)) {
26244
+ return false;
26245
+ }
26246
+ await this.invalidateSession();
26247
+ return true;
26248
+ }
26249
+ canRecoverWithFreshSession(operation) {
26250
+ return !this.liveSessionStateEstablished && isBootstrapRecoveryOperation(operation);
26251
+ }
26252
+ noteSuccessfulLiveOperation(operation) {
26253
+ 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") {
26254
+ this.liveSessionStateEstablished = true;
26255
+ }
26256
+ }
25950
26257
  async runOperationWithPolicy(operation, invoke) {
25951
26258
  return runWithPolicyTimeout(this.policy.timeout, { operation }, invoke);
25952
26259
  }
@@ -25970,8 +26277,29 @@ function assertSupportedCloudBrowserMode(browser) {
25970
26277
  }
25971
26278
  }
25972
26279
  function isMissingCloudSessionError(error) {
26280
+ if (error instanceof OpensteerCloudRequestError) {
26281
+ return error.statusCode === 404 && (error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND");
26282
+ }
25973
26283
  return error instanceof Error && /\b404\b/.test(error.message);
25974
26284
  }
26285
+ function isRecoverableCloudSessionError(error) {
26286
+ if (!(error instanceof OpensteerCloudRequestError)) {
26287
+ return false;
26288
+ }
26289
+ if (error.statusCode === 404) {
26290
+ return error.code === void 0 || error.code === "CLOUD_SESSION_NOT_FOUND";
26291
+ }
26292
+ return error.statusCode === 409 && error.code === "CLOUD_SESSION_STALE";
26293
+ }
26294
+ function isBootstrapRecoveryOperation(operation) {
26295
+ return operation === "session.open" || operation === "instrumentation.route" || operation === "instrumentation.intercept-script";
26296
+ }
26297
+ function isReusableCloudSessionState(session) {
26298
+ if (session.status === "closing" || session.status === "closed" || session.status === "failed") {
26299
+ return false;
26300
+ }
26301
+ return !(typeof session.expiresAt === "number" && session.expiresAt <= Date.now() + CLOUD_SESSION_REUSE_EXPIRY_SKEW_MS);
26302
+ }
25975
26303
  function isLoopbackBaseUrl(baseUrl) {
25976
26304
  let url;
25977
26305
  try {
@@ -26133,6 +26461,25 @@ var SessionCookieJar = class {
26133
26461
  return this.cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
26134
26462
  }
26135
26463
  };
26464
+ function createSdkProtocolError(error, fallbackMessage) {
26465
+ const normalized = normalizeThrownOpensteerError(error, fallbackMessage);
26466
+ return new OpensteerProtocolError(normalized.code, normalized.message, {
26467
+ cause: error,
26468
+ retriable: normalized.retriable,
26469
+ ...normalized.capability === void 0 ? {} : { capability: normalized.capability },
26470
+ ...normalized.details === void 0 ? {} : { details: normalized.details }
26471
+ });
26472
+ }
26473
+ async function wrapSdkError(operation, fn) {
26474
+ try {
26475
+ return await fn();
26476
+ } catch (error) {
26477
+ if (isOpensteerProtocolError(error)) {
26478
+ throw error;
26479
+ }
26480
+ throw createSdkProtocolError(error, `${operation} failed`);
26481
+ }
26482
+ }
26136
26483
  var Opensteer = class {
26137
26484
  runtime;
26138
26485
  browserManager;
@@ -26140,207 +26487,265 @@ var Opensteer = class {
26140
26487
  dom;
26141
26488
  network;
26142
26489
  constructor(options = {}) {
26143
- const environment = resolveOpensteerEnvironment(options.rootDir);
26144
- const { provider, engineName, ...runtimeOptions } = options;
26145
- const runtimeConfig = resolveOpensteerRuntimeConfig({
26146
- ...provider === void 0 ? {} : { provider },
26147
- environment
26148
- });
26149
- if (runtimeConfig.provider.mode === "cloud") {
26150
- this.browserManager = void 0;
26151
- this.runtime = createOpensteerSemanticRuntime({
26152
- ...provider === void 0 ? {} : { provider },
26153
- ...engineName === void 0 ? {} : { engine: engineName },
26154
- environment,
26155
- runtimeOptions
26156
- });
26157
- this.browser = createUnsupportedBrowserController();
26158
- } else {
26159
- this.browserManager = new OpensteerBrowserManager({
26160
- ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
26161
- ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
26162
- ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
26163
- ...engineName === void 0 ? {} : { engineName },
26164
- environment,
26165
- ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
26166
- ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
26167
- ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
26168
- });
26169
- this.runtime = createOpensteerSemanticRuntime({
26490
+ try {
26491
+ const environment = resolveOpensteerEnvironment(options.rootDir);
26492
+ const { provider, engineName, ...runtimeOptions } = options;
26493
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
26170
26494
  ...provider === void 0 ? {} : { provider },
26171
- ...engineName === void 0 ? {} : { engine: engineName },
26172
- environment,
26173
- runtimeOptions: {
26174
- ...runtimeOptions,
26175
- rootPath: this.browserManager.rootPath,
26176
- cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
26177
- }
26495
+ environment
26178
26496
  });
26179
- this.browser = {
26180
- status: () => this.browserManager.status(),
26181
- clone: (input) => this.browserManager.clonePersistentBrowser(input),
26182
- reset: () => this.browserManager.reset(),
26183
- delete: () => this.browserManager.delete()
26497
+ if (runtimeConfig.provider.mode === "cloud") {
26498
+ this.browserManager = void 0;
26499
+ this.runtime = createOpensteerSemanticRuntime({
26500
+ ...provider === void 0 ? {} : { provider },
26501
+ ...engineName === void 0 ? {} : { engine: engineName },
26502
+ environment,
26503
+ runtimeOptions
26504
+ });
26505
+ this.browser = createUnsupportedBrowserController();
26506
+ } else {
26507
+ this.browserManager = new OpensteerBrowserManager({
26508
+ ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
26509
+ ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
26510
+ ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
26511
+ ...engineName === void 0 ? {} : { engineName },
26512
+ environment,
26513
+ ...runtimeOptions.browser === void 0 ? {} : { browser: runtimeOptions.browser },
26514
+ ...runtimeOptions.launch === void 0 ? {} : { launch: runtimeOptions.launch },
26515
+ ...runtimeOptions.context === void 0 ? {} : { context: runtimeOptions.context }
26516
+ });
26517
+ this.runtime = createOpensteerSemanticRuntime({
26518
+ ...provider === void 0 ? {} : { provider },
26519
+ ...engineName === void 0 ? {} : { engine: engineName },
26520
+ environment,
26521
+ runtimeOptions: {
26522
+ ...runtimeOptions,
26523
+ rootPath: this.browserManager.rootPath,
26524
+ cleanupRootOnClose: this.browserManager.cleanupRootOnDisconnect
26525
+ }
26526
+ });
26527
+ this.browser = {
26528
+ status: () => wrapSdkError("browser.status", () => this.browserManager.status()),
26529
+ clone: (input) => wrapSdkError("browser.clone", () => this.browserManager.clonePersistentBrowser(input)),
26530
+ reset: () => wrapSdkError("browser.reset", () => this.browserManager.reset()),
26531
+ delete: () => wrapSdkError("browser.delete", () => this.browserManager.delete())
26532
+ };
26533
+ }
26534
+ this.dom = {
26535
+ click: (input) => this.click(input),
26536
+ hover: (input) => this.hover(input),
26537
+ input: (input) => this.input(input),
26538
+ scroll: (input) => this.scroll(input)
26184
26539
  };
26540
+ this.network = {
26541
+ query: (input = {}) => wrapSdkError("network.query", () => this.runtime.queryNetwork(input)),
26542
+ detail: (recordId, options2) => wrapSdkError(
26543
+ "network.detail",
26544
+ () => this.runtime.getNetworkDetail({ recordId, ...options2 })
26545
+ )
26546
+ };
26547
+ } catch (error) {
26548
+ if (isOpensteerProtocolError(error)) {
26549
+ throw error;
26550
+ }
26551
+ throw createSdkProtocolError(error, "Failed to initialize Opensteer");
26185
26552
  }
26186
- this.dom = {
26187
- click: (input) => this.click(input),
26188
- hover: (input) => this.hover(input),
26189
- input: (input) => this.input(input),
26190
- scroll: (input) => this.scroll(input)
26191
- };
26192
- this.network = {
26193
- query: (input = {}) => this.runtime.queryNetwork(input),
26194
- detail: (recordId, options2) => this.runtime.getNetworkDetail({ recordId, ...options2 })
26195
- };
26196
26553
  }
26197
26554
  async open(input = {}) {
26198
- return this.runtime.open(typeof input === "string" ? { url: input } : input);
26555
+ return wrapSdkError(
26556
+ "session.open",
26557
+ () => this.runtime.open(typeof input === "string" ? { url: input } : input)
26558
+ );
26199
26559
  }
26200
26560
  async info() {
26201
- return this.runtime.info();
26561
+ return wrapSdkError("session.info", () => this.runtime.info());
26202
26562
  }
26203
26563
  async listPages(input = {}) {
26204
- return this.runtime.listPages(input);
26564
+ return wrapSdkError("page.list", () => this.runtime.listPages(input));
26205
26565
  }
26206
26566
  async newPage(input = {}) {
26207
- return this.runtime.newPage(input);
26567
+ return wrapSdkError("page.new", () => this.runtime.newPage(input));
26208
26568
  }
26209
26569
  async activatePage(input) {
26210
- return this.runtime.activatePage(input);
26570
+ return wrapSdkError("page.activate", () => this.runtime.activatePage(input));
26211
26571
  }
26212
26572
  async closePage(input = {}) {
26213
- return this.runtime.closePage(input);
26573
+ return wrapSdkError("page.close", () => this.runtime.closePage(input));
26214
26574
  }
26215
26575
  async goto(url, options = {}) {
26216
- return this.runtime.goto({
26217
- url,
26218
- ...options
26219
- });
26576
+ return wrapSdkError(
26577
+ "page.goto",
26578
+ () => this.runtime.goto({
26579
+ url,
26580
+ ...options
26581
+ })
26582
+ );
26220
26583
  }
26221
26584
  async evaluate(input) {
26222
- const normalized = typeof input === "string" ? {
26223
- script: input
26224
- } : input;
26225
- return (await this.runtime.evaluate(normalized)).value;
26585
+ return wrapSdkError("page.evaluate", async () => {
26586
+ const normalized = typeof input === "string" ? {
26587
+ script: input
26588
+ } : input;
26589
+ return (await this.runtime.evaluate(normalized)).value;
26590
+ });
26226
26591
  }
26227
26592
  async addInitScript(input) {
26228
- return this.runtime.addInitScript(
26229
- typeof input === "string" ? {
26230
- script: input
26231
- } : input
26593
+ return wrapSdkError(
26594
+ "page.addInitScript",
26595
+ () => this.runtime.addInitScript(
26596
+ typeof input === "string" ? {
26597
+ script: input
26598
+ } : input
26599
+ )
26232
26600
  );
26233
26601
  }
26234
26602
  async click(input) {
26235
- const { button, clickCount, modifiers, ...target } = input;
26236
- return this.runtime.click({
26237
- ...normalizeTargetOptions(target),
26238
- ...button === void 0 ? {} : { button },
26239
- ...clickCount === void 0 ? {} : { clickCount },
26240
- ...modifiers === void 0 ? {} : { modifiers }
26603
+ return wrapSdkError("dom.click", () => {
26604
+ const { button, clickCount, modifiers, ...target } = input;
26605
+ return this.runtime.click({
26606
+ ...normalizeTargetOptions(target),
26607
+ ...button === void 0 ? {} : { button },
26608
+ ...clickCount === void 0 ? {} : { clickCount },
26609
+ ...modifiers === void 0 ? {} : { modifiers }
26610
+ });
26241
26611
  });
26242
26612
  }
26243
26613
  async hover(input) {
26244
- return this.runtime.hover(normalizeTargetOptions(input));
26614
+ return wrapSdkError("dom.hover", () => this.runtime.hover(normalizeTargetOptions(input)));
26245
26615
  }
26246
26616
  async input(input) {
26247
- return this.runtime.input({
26248
- ...normalizeTargetOptions(input),
26249
- text: input.text,
26250
- ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
26251
- });
26617
+ return wrapSdkError(
26618
+ "dom.input",
26619
+ () => this.runtime.input({
26620
+ ...normalizeTargetOptions(input),
26621
+ text: input.text,
26622
+ ...input.pressEnter === void 0 ? {} : { pressEnter: input.pressEnter }
26623
+ })
26624
+ );
26252
26625
  }
26253
26626
  async scroll(input) {
26254
- return this.runtime.scroll({
26255
- ...normalizeTargetOptions(input),
26256
- direction: input.direction,
26257
- amount: input.amount
26258
- });
26627
+ return wrapSdkError(
26628
+ "dom.scroll",
26629
+ () => this.runtime.scroll({
26630
+ ...normalizeTargetOptions(input),
26631
+ direction: input.direction,
26632
+ amount: input.amount
26633
+ })
26634
+ );
26259
26635
  }
26260
26636
  async extract(input) {
26261
- return (await this.runtime.extract(input)).data;
26637
+ return wrapSdkError("extract", async () => (await this.runtime.extract(input)).data);
26262
26638
  }
26263
26639
  async waitForPage(input = {}) {
26264
- const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
26265
- const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
26266
- const pollIntervalMs = input.pollIntervalMs ?? 100;
26267
- while (true) {
26268
- const match = (await this.runtime.listPages()).pages.find((page) => {
26269
- if (baseline.has(page.pageRef)) {
26270
- return false;
26271
- }
26272
- if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
26273
- return false;
26640
+ return wrapSdkError("page.waitForPage", async () => {
26641
+ const baseline = new Set((await this.runtime.listPages()).pages.map((page) => page.pageRef));
26642
+ const timeoutAt = Date.now() + (input.timeoutMs ?? 3e4);
26643
+ const pollIntervalMs = input.pollIntervalMs ?? 100;
26644
+ while (true) {
26645
+ const match = (await this.runtime.listPages()).pages.find((page) => {
26646
+ if (baseline.has(page.pageRef)) {
26647
+ return false;
26648
+ }
26649
+ if (input.openerPageRef !== void 0 && page.openerPageRef !== input.openerPageRef) {
26650
+ return false;
26651
+ }
26652
+ if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
26653
+ return false;
26654
+ }
26655
+ return true;
26656
+ });
26657
+ if (match !== void 0) {
26658
+ return match;
26274
26659
  }
26275
- if (input.urlIncludes !== void 0 && !page.url.includes(input.urlIncludes)) {
26276
- return false;
26660
+ if (Date.now() >= timeoutAt) {
26661
+ throw new OpensteerProtocolError("timeout", "waitForPage timed out");
26277
26662
  }
26278
- return true;
26279
- });
26280
- if (match !== void 0) {
26281
- return match;
26282
- }
26283
- if (Date.now() >= timeoutAt) {
26284
- throw new Error("waitForPage timed out");
26663
+ await delay3(pollIntervalMs);
26285
26664
  }
26286
- await delay3(pollIntervalMs);
26287
- }
26665
+ });
26288
26666
  }
26289
26667
  async cookies(domain) {
26290
- return new SessionCookieJar(
26291
- await this.runtime.getCookies(domain === void 0 ? {} : { domain })
26668
+ return wrapSdkError(
26669
+ "session.cookies",
26670
+ async () => new SessionCookieJar(await this.runtime.getCookies(domain === void 0 ? {} : { domain }))
26292
26671
  );
26293
26672
  }
26294
26673
  async storage(domain, type = "local") {
26295
- const snapshot = await this.runtime.getStorageSnapshot(domain === void 0 ? {} : { domain });
26296
- const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
26297
- if (domainSnapshot === void 0) {
26298
- return {};
26299
- }
26300
- const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
26301
- return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
26674
+ return wrapSdkError("session.storage", async () => {
26675
+ const snapshot = await this.runtime.getStorageSnapshot(
26676
+ domain === void 0 ? {} : { domain }
26677
+ );
26678
+ const domainSnapshot = pickStorageDomainSnapshot(snapshot, domain);
26679
+ if (domainSnapshot === void 0) {
26680
+ return {};
26681
+ }
26682
+ const entries = type === "local" ? domainSnapshot.localStorage : domainSnapshot.sessionStorage;
26683
+ return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
26684
+ });
26302
26685
  }
26303
26686
  async state(domain) {
26304
- return this.runtime.getBrowserState(domain === void 0 ? {} : { domain });
26687
+ return wrapSdkError(
26688
+ "session.state",
26689
+ () => this.runtime.getBrowserState(domain === void 0 ? {} : { domain })
26690
+ );
26305
26691
  }
26306
26692
  async fetch(url, options = {}) {
26307
- const input = buildFetchInput(url, options);
26308
- const result = await this.runtime.fetch(input);
26309
- if (result.response === void 0) {
26310
- throw new Error(result.note ?? `session.fetch did not produce a response for ${url}`);
26311
- }
26312
- return toResponse(result.response);
26693
+ return wrapSdkError("session.fetch", async () => {
26694
+ const input = buildFetchInput(url, options);
26695
+ const result = await this.runtime.fetch(input);
26696
+ if (result.response === void 0) {
26697
+ throw new OpensteerProtocolError(
26698
+ "operation-failed",
26699
+ result.note ?? `session.fetch did not produce a response for ${url}`
26700
+ );
26701
+ }
26702
+ return toResponse(result.response);
26703
+ });
26313
26704
  }
26314
26705
  async computerExecute(input) {
26315
- return this.runtime.computerExecute(input);
26706
+ return wrapSdkError("session.computerExecute", () => this.runtime.computerExecute(input));
26316
26707
  }
26317
26708
  async route(input) {
26318
- return this.requireOwnedInstrumentationRuntime("route").route(input);
26709
+ return wrapSdkError(
26710
+ "session.route",
26711
+ () => this.requireOwnedInstrumentationRuntime("route").route(input)
26712
+ );
26319
26713
  }
26320
26714
  async interceptScript(input) {
26321
- return this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input);
26715
+ return wrapSdkError(
26716
+ "session.interceptScript",
26717
+ () => this.requireOwnedInstrumentationRuntime("interceptScript").interceptScript(input)
26718
+ );
26322
26719
  }
26323
26720
  async close() {
26324
- if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
26325
- return this.runtime.close();
26326
- }
26327
- const output = await this.runtime.close();
26328
- await this.browserManager.close();
26329
- return output;
26721
+ return wrapSdkError("session.close", async () => {
26722
+ if (this.browserManager === void 0 || this.browserManager.mode === "temporary") {
26723
+ return this.runtime.close();
26724
+ }
26725
+ const output = await this.runtime.close();
26726
+ await this.browserManager.close();
26727
+ return output;
26728
+ });
26330
26729
  }
26331
26730
  async disconnect() {
26332
- await this.runtime.disconnect();
26731
+ return wrapSdkError("session.disconnect", () => this.runtime.disconnect());
26333
26732
  }
26334
26733
  requireOwnedInstrumentationRuntime(method) {
26335
26734
  if (typeof this.runtime.route === "function" && typeof this.runtime.interceptScript === "function") {
26336
26735
  return this.runtime;
26337
26736
  }
26338
- throw new Error(`${method}() is not available for this session runtime.`);
26737
+ throw new OpensteerProtocolError(
26738
+ "unsupported-operation",
26739
+ `${method}() is not available for this session runtime.`
26740
+ );
26339
26741
  }
26340
26742
  };
26341
26743
  function createUnsupportedBrowserController() {
26342
26744
  const fail = async () => {
26343
- throw new Error("browser.* helpers are only available in local mode.");
26745
+ throw new OpensteerProtocolError(
26746
+ "unsupported-operation",
26747
+ "browser.* helpers are only available in local mode."
26748
+ );
26344
26749
  };
26345
26750
  return {
26346
26751
  status: fail,
@@ -26353,7 +26758,10 @@ function normalizeTargetOptions(input) {
26353
26758
  const hasElement = input.element !== void 0;
26354
26759
  const hasSelector = input.selector !== void 0;
26355
26760
  if (hasElement && hasSelector) {
26356
- throw new Error("Specify exactly one of element, selector, or persist.");
26761
+ throw new OpensteerProtocolError(
26762
+ "invalid-argument",
26763
+ "Specify exactly one of element, selector, or persist."
26764
+ );
26357
26765
  }
26358
26766
  if (hasElement) {
26359
26767
  return {
@@ -26376,7 +26784,10 @@ function normalizeTargetOptions(input) {
26376
26784
  };
26377
26785
  }
26378
26786
  if (input.persist === void 0) {
26379
- throw new Error("Specify exactly one of element, selector, or persist.");
26787
+ throw new OpensteerProtocolError(
26788
+ "invalid-argument",
26789
+ "Specify exactly one of element, selector, or persist."
26790
+ );
26380
26791
  }
26381
26792
  return {
26382
26793
  target: {
@@ -26448,6 +26859,7 @@ exports.Opensteer = Opensteer;
26448
26859
  exports.OpensteerAttachAmbiguousError = OpensteerAttachAmbiguousError;
26449
26860
  exports.OpensteerBrowserManager = OpensteerBrowserManager;
26450
26861
  exports.OpensteerCloudClient = OpensteerCloudClient;
26862
+ exports.OpensteerProtocolError = OpensteerProtocolError;
26451
26863
  exports.STABLE_PRIMARY_ATTR_KEYS = STABLE_PRIMARY_ATTR_KEYS;
26452
26864
  exports.assertProviderSupportsEngine = assertProviderSupportsEngine;
26453
26865
  exports.buildArrayFieldPathCandidates = buildArrayFieldPathCandidates;
@@ -26477,6 +26889,7 @@ exports.discoverLocalCdpBrowsers = discoverLocalCdpBrowsers;
26477
26889
  exports.hashDomDescriptorPersist = hashDomDescriptorPersist;
26478
26890
  exports.inspectCdpEndpoint = inspectCdpEndpoint;
26479
26891
  exports.isCurrentUrlField = isCurrentUrlField;
26892
+ exports.isOpensteerProtocolError = isOpensteerProtocolError;
26480
26893
  exports.isValidCssAttributeKey = isValidCssAttributeKey;
26481
26894
  exports.listLocalChromeProfiles = listLocalChromeProfiles;
26482
26895
  exports.manifestToExternalBinaryLocation = manifestToExternalBinaryLocation;
@@ -26485,6 +26898,7 @@ exports.normalizeObservabilityConfig = normalizeObservabilityConfig;
26485
26898
  exports.normalizeOpensteerEngineName = normalizeOpensteerEngineName;
26486
26899
  exports.normalizeOpensteerProviderMode = normalizeOpensteerProviderMode;
26487
26900
  exports.normalizeWorkspaceId = normalizeWorkspaceId;
26901
+ exports.opensteerErrorCodes = opensteerErrorCodes;
26488
26902
  exports.parseDomDescriptorRecord = parseDomDescriptorRecord;
26489
26903
  exports.parseExtractionDescriptorRecord = parseExtractionDescriptorRecord;
26490
26904
  exports.readPersistedCloudSessionRecord = readPersistedCloudSessionRecord;