opensteer 0.9.6 → 0.9.8

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/README.md +2 -2
  2. package/dist/{chunk-3I3A5OLB.js → chunk-BPGXP3RF.js} +257 -24
  3. package/dist/chunk-BPGXP3RF.js.map +1 -0
  4. package/dist/{chunk-3XBQRZZC.js → chunk-EXXRLPLI.js} +158 -46
  5. package/dist/chunk-EXXRLPLI.js.map +1 -0
  6. package/dist/{chunk-T5P2QGZ3.js → chunk-GKYBP3KD.js} +154 -13
  7. package/dist/chunk-GKYBP3KD.js.map +1 -0
  8. package/dist/{chunk-BVRIPCWA.js → chunk-LFWP5RXF.js} +500 -513
  9. package/dist/chunk-LFWP5RXF.js.map +1 -0
  10. package/dist/{chunk-L4NF74KI.js → chunk-SOJEWKSW.js} +5 -5
  11. package/dist/{chunk-L4NF74KI.js.map → chunk-SOJEWKSW.js.map} +1 -1
  12. package/dist/cli/bin.cjs +1230 -660
  13. package/dist/cli/bin.cjs.map +1 -1
  14. package/dist/cli/bin.js +166 -72
  15. package/dist/cli/bin.js.map +1 -1
  16. package/dist/index.cjs +793 -565
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +36 -51
  19. package/dist/index.d.ts +36 -51
  20. package/dist/index.js +4 -4
  21. package/dist/local-view/public/assets/app.js +10 -1
  22. package/dist/local-view/serve-entry.cjs +1022 -591
  23. package/dist/local-view/serve-entry.cjs.map +1 -1
  24. package/dist/local-view/serve-entry.js +2 -2
  25. package/dist/opensteer-XLPY343Y.js +6 -0
  26. package/dist/{opensteer-UGA6YBRN.js.map → opensteer-XLPY343Y.js.map} +1 -1
  27. package/dist/{session-control-U3L5H2ZI.js → session-control-FVKKD45R.js} +4 -4
  28. package/dist/{session-control-U3L5H2ZI.js.map → session-control-FVKKD45R.js.map} +1 -1
  29. package/package.json +5 -5
  30. package/skills/recorder/SKILL.md +2 -2
  31. package/dist/chunk-3I3A5OLB.js.map +0 -1
  32. package/dist/chunk-3XBQRZZC.js.map +0 -1
  33. package/dist/chunk-BVRIPCWA.js.map +0 -1
  34. package/dist/chunk-T5P2QGZ3.js.map +0 -1
  35. package/dist/opensteer-UGA6YBRN.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(
@@ -1188,7 +1288,7 @@ var visualViewportSchema = objectSchema(
1188
1288
  required: ["origin", "offsetWithinLayoutViewport", "size"]
1189
1289
  }
1190
1290
  );
1191
- var viewportMetricsSchema = objectSchema(
1291
+ objectSchema(
1192
1292
  {
1193
1293
  layoutViewport: layoutViewportSchema,
1194
1294
  visualViewport: visualViewportSchema,
@@ -1223,6 +1323,9 @@ var pageInfoSchema = objectSchema(
1223
1323
  {
1224
1324
  pageRef: pageRefSchema,
1225
1325
  sessionRef: sessionRefSchema,
1326
+ targetId: stringSchema({
1327
+ description: "Underlying browser target identifier when available."
1328
+ }),
1226
1329
  openerPageRef: pageRefSchema,
1227
1330
  url: stringSchema({
1228
1331
  description: "Current main-frame URL."
@@ -2639,7 +2742,7 @@ var domSnapshotSchema = objectSchema(
2639
2742
  ]
2640
2743
  }
2641
2744
  );
2642
- var hitTestResultSchema = objectSchema(
2745
+ objectSchema(
2643
2746
  {
2644
2747
  inputPoint: pointSchema,
2645
2748
  inputCoordinateSpace: coordinateSpaceSchema,
@@ -4092,39 +4195,14 @@ var opensteerTargetInputSchema = oneOfSchema(
4092
4195
  title: "OpensteerTargetInput"
4093
4196
  }
4094
4197
  );
4095
- var opensteerResolvedTargetSchema = objectSchema(
4096
- {
4097
- pageRef: pageRefSchema,
4098
- frameRef: frameRefSchema,
4099
- documentRef: documentRefSchema,
4100
- documentEpoch: documentEpochSchema,
4101
- nodeRef: nodeRefSchema,
4102
- tagName: stringSchema(),
4103
- pathHint: stringSchema(),
4104
- persist: stringSchema(),
4105
- selectorUsed: stringSchema()
4106
- },
4107
- {
4108
- title: "OpensteerResolvedTarget",
4109
- required: [
4110
- "pageRef",
4111
- "frameRef",
4112
- "documentRef",
4113
- "documentEpoch",
4114
- "nodeRef",
4115
- "tagName",
4116
- "pathHint"
4117
- ]
4118
- }
4119
- );
4120
4198
  var opensteerActionResultSchema = objectSchema(
4121
4199
  {
4122
- target: opensteerResolvedTargetSchema,
4123
- point: pointSchema
4200
+ tagName: stringSchema({ minLength: 1 }),
4201
+ persist: stringSchema({ minLength: 1 })
4124
4202
  },
4125
4203
  {
4126
4204
  title: "OpensteerActionResult",
4127
- required: ["target"]
4205
+ required: ["tagName"]
4128
4206
  }
4129
4207
  );
4130
4208
  var opensteerSnapshotCounterSchema = objectSchema(
@@ -4170,16 +4248,14 @@ var opensteerSnapshotCounterSchema = objectSchema(
4170
4248
  ]
4171
4249
  }
4172
4250
  );
4173
- var opensteerSessionStateSchema = objectSchema(
4251
+ var opensteerNavigationSummarySchema = objectSchema(
4174
4252
  {
4175
- sessionRef: sessionRefSchema,
4176
- pageRef: pageRefSchema,
4177
4253
  url: stringSchema(),
4178
4254
  title: stringSchema()
4179
4255
  },
4180
4256
  {
4181
- title: "OpensteerSessionState",
4182
- required: ["sessionRef", "pageRef", "url", "title"]
4257
+ title: "OpensteerNavigationSummary",
4258
+ required: ["url", "title"]
4183
4259
  }
4184
4260
  );
4185
4261
  var opensteerOpenInputSchema = objectSchema(
@@ -4247,6 +4323,17 @@ var opensteerPageCloseOutputSchema = objectSchema(
4247
4323
  required: ["closedPageRef", "pages"]
4248
4324
  }
4249
4325
  );
4326
+ var opensteerPageNewOutputSchema = objectSchema(
4327
+ {
4328
+ pageRef: pageRefSchema,
4329
+ url: stringSchema(),
4330
+ title: stringSchema()
4331
+ },
4332
+ {
4333
+ title: "OpensteerPageNewOutput",
4334
+ required: ["pageRef", "url", "title"]
4335
+ }
4336
+ );
4250
4337
  var opensteerPageGotoInputSchema = objectSchema(
4251
4338
  {
4252
4339
  url: stringSchema(),
@@ -4633,72 +4720,28 @@ var opensteerComputerExecuteInputSchema = objectSchema(
4633
4720
  required: ["action"]
4634
4721
  }
4635
4722
  );
4636
- var opensteerComputerTracePointSchema = objectSchema(
4637
- {
4638
- role: enumSchema(["point", "start", "end"]),
4639
- point: pointSchema,
4640
- hitTest: hitTestResultSchema,
4641
- target: opensteerResolvedTargetSchema
4642
- },
4643
- {
4644
- title: "OpensteerComputerTracePoint",
4645
- required: ["role", "point"]
4646
- }
4647
- );
4648
- var opensteerComputerTraceEnrichmentSchema = objectSchema(
4649
- {
4650
- points: arraySchema(opensteerComputerTracePointSchema)
4651
- },
4652
- {
4653
- title: "OpensteerComputerTraceEnrichment",
4654
- required: ["points"]
4655
- }
4656
- );
4657
- var opensteerComputerExecuteTimingSchema = objectSchema(
4723
+ var opensteerScreenshotSummarySchema = objectSchema(
4658
4724
  {
4659
- actionMs: integerSchema({ minimum: 0 }),
4660
- waitMs: integerSchema({ minimum: 0 }),
4661
- totalMs: integerSchema({ minimum: 0 })
4662
- },
4663
- {
4664
- title: "OpensteerComputerExecuteTiming",
4665
- required: ["actionMs", "waitMs", "totalMs"]
4666
- }
4667
- );
4668
- var opensteerComputerDisplayScaleSchema = objectSchema(
4669
- {
4670
- x: numberSchema({ exclusiveMinimum: 0 }),
4671
- y: numberSchema({ exclusiveMinimum: 0 })
4725
+ payload: externalBinaryLocationSchema,
4726
+ format: screenshotFormatSchema,
4727
+ size: sizeSchema,
4728
+ coordinateSpace: coordinateSpaceSchema,
4729
+ clip: rectSchema
4672
4730
  },
4673
4731
  {
4674
- title: "OpensteerComputerDisplayScale",
4675
- required: ["x", "y"]
4732
+ title: "OpensteerScreenshotSummary",
4733
+ required: ["payload", "format", "size", "coordinateSpace"]
4676
4734
  }
4677
4735
  );
4678
4736
  var opensteerComputerExecuteOutputSchema = objectSchema(
4679
4737
  {
4680
- action: opensteerComputerActionSchema,
4681
- pageRef: pageRefSchema,
4682
- screenshot: screenshotArtifactSchema,
4683
- displayViewport: viewportMetricsSchema,
4684
- nativeViewport: viewportMetricsSchema,
4685
- displayScale: opensteerComputerDisplayScaleSchema,
4686
- events: arraySchema(opensteerEventSchema),
4687
- timing: opensteerComputerExecuteTimingSchema,
4688
- trace: opensteerComputerTraceEnrichmentSchema
4738
+ url: stringSchema(),
4739
+ title: stringSchema(),
4740
+ screenshot: opensteerScreenshotSummarySchema
4689
4741
  },
4690
4742
  {
4691
4743
  title: "OpensteerComputerExecuteOutput",
4692
- required: [
4693
- "action",
4694
- "pageRef",
4695
- "screenshot",
4696
- "displayViewport",
4697
- "nativeViewport",
4698
- "displayScale",
4699
- "events",
4700
- "timing"
4701
- ]
4744
+ required: ["url", "title", "screenshot"]
4702
4745
  }
4703
4746
  );
4704
4747
  function assertValidSemanticOperationInput(name, input) {
@@ -4724,7 +4767,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4724
4767
  name: "session.open",
4725
4768
  description: "Open or resume the current Opensteer session and primary page.",
4726
4769
  inputSchema: opensteerOpenInputSchema,
4727
- outputSchema: opensteerSessionStateSchema,
4770
+ outputSchema: opensteerNavigationSummarySchema,
4728
4771
  requiredCapabilities: ["sessions.manage", "pages.manage"],
4729
4772
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["sessions.manage", "pages.manage"] : ["sessions.manage", "pages.manage", "pages.navigate"]
4730
4773
  }),
@@ -4739,7 +4782,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4739
4782
  name: "page.new",
4740
4783
  description: "Create and optionally navigate a new top-level page in the current session.",
4741
4784
  inputSchema: opensteerPageNewInputSchema,
4742
- outputSchema: opensteerSessionStateSchema,
4785
+ outputSchema: opensteerPageNewOutputSchema,
4743
4786
  requiredCapabilities: ["pages.manage"],
4744
4787
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["pages.manage"] : ["pages.manage", "pages.navigate"]
4745
4788
  }),
@@ -4747,7 +4790,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4747
4790
  name: "page.activate",
4748
4791
  description: "Activate an existing top-level page in the current session.",
4749
4792
  inputSchema: opensteerPageActivateInputSchema,
4750
- outputSchema: opensteerSessionStateSchema,
4793
+ outputSchema: opensteerNavigationSummarySchema,
4751
4794
  requiredCapabilities: ["pages.manage", "inspect.pages"]
4752
4795
  }),
4753
4796
  defineSemanticOperationSpec({
@@ -4761,7 +4804,7 @@ var opensteerSemanticOperationSpecificationsBase = [
4761
4804
  name: "page.goto",
4762
4805
  description: "Navigate the current Opensteer page to a new URL.",
4763
4806
  inputSchema: opensteerPageGotoInputSchema,
4764
- outputSchema: opensteerSessionStateSchema,
4807
+ outputSchema: opensteerNavigationSummarySchema,
4765
4808
  requiredCapabilities: ["pages.navigate"]
4766
4809
  }),
4767
4810
  defineSemanticOperationSpec({
@@ -6163,6 +6206,9 @@ var FilesystemSessionSink = class {
6163
6206
  this.store = store;
6164
6207
  this.sessionId = sessionId;
6165
6208
  }
6209
+ configure(input) {
6210
+ return this.store.configureSession(this.sessionId, input);
6211
+ }
6166
6212
  append(input) {
6167
6213
  return this.store.appendEvent(this.sessionId, input);
6168
6214
  }
@@ -6193,40 +6239,14 @@ var FilesystemObservationStoreImpl = class {
6193
6239
  const sessionId = normalizeNonEmptyString("sessionId", input.sessionId);
6194
6240
  const openedAt = normalizeTimestamp("openedAt", input.openedAt ?? Date.now());
6195
6241
  const config = normalizeObservabilityConfig(input.config);
6196
- const redactor = createObservationRedactor(config);
6197
- this.redactors.set(sessionId, redactor);
6198
- const redactedLabels = redactor.redactLabels(config.labels);
6199
- const redactedTraceContext = redactor.redactTraceContext(config.traceContext);
6200
- await withFilesystemLock(this.sessionLockPath(sessionId), async () => {
6201
- const existing = await this.reconcileSessionManifest(sessionId);
6202
- if (existing === void 0) {
6203
- await ensureDirectory(this.sessionEventsDirectory(sessionId));
6204
- await ensureDirectory(this.sessionArtifactsDirectory(sessionId));
6205
- const session = {
6206
- sessionId,
6207
- profile: config.profile,
6208
- ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
6209
- ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
6210
- openedAt,
6211
- updatedAt: openedAt,
6212
- currentSequence: 0,
6213
- eventCount: 0,
6214
- artifactCount: 0
6215
- };
6216
- await writeJsonFileExclusive(this.sessionManifestPath(sessionId), session);
6217
- return;
6218
- }
6219
- const patched = {
6220
- ...existing,
6221
- profile: config.profile,
6222
- ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
6223
- ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
6224
- updatedAt: Math.max(existing.updatedAt, openedAt)
6225
- };
6226
- await writeJsonFileAtomic(this.sessionManifestPath(sessionId), patched);
6227
- });
6242
+ await this.applySessionConfiguration(sessionId, config, openedAt);
6228
6243
  return new FilesystemSessionSink(this, sessionId);
6229
6244
  }
6245
+ async configureSession(sessionId, input) {
6246
+ const updatedAt = normalizeTimestamp("updatedAt", input.updatedAt ?? Date.now());
6247
+ const config = normalizeObservabilityConfig(input.config);
6248
+ await this.applySessionConfiguration(sessionId, config, updatedAt);
6249
+ }
6230
6250
  async getSession(sessionId) {
6231
6251
  const manifestPath = this.sessionManifestPath(sessionId);
6232
6252
  if (!await pathExists(manifestPath)) {
@@ -6447,6 +6467,40 @@ var FilesystemObservationStoreImpl = class {
6447
6467
  sessionLockPath(sessionId) {
6448
6468
  return path10__default.default.join(this.sessionDirectory(sessionId), ".lock");
6449
6469
  }
6470
+ async applySessionConfiguration(sessionId, config, timestamp) {
6471
+ const redactor = createObservationRedactor(config);
6472
+ this.redactors.set(sessionId, redactor);
6473
+ const redactedLabels = redactor.redactLabels(config.labels);
6474
+ const redactedTraceContext = redactor.redactTraceContext(config.traceContext);
6475
+ await withFilesystemLock(this.sessionLockPath(sessionId), async () => {
6476
+ const existing = await this.reconcileSessionManifest(sessionId);
6477
+ if (existing === void 0) {
6478
+ await ensureDirectory(this.sessionEventsDirectory(sessionId));
6479
+ await ensureDirectory(this.sessionArtifactsDirectory(sessionId));
6480
+ const session = {
6481
+ sessionId,
6482
+ profile: config.profile,
6483
+ ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
6484
+ ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
6485
+ openedAt: timestamp,
6486
+ updatedAt: timestamp,
6487
+ currentSequence: 0,
6488
+ eventCount: 0,
6489
+ artifactCount: 0
6490
+ };
6491
+ await writeJsonFileExclusive(this.sessionManifestPath(sessionId), session);
6492
+ return;
6493
+ }
6494
+ const patched = {
6495
+ ...existing,
6496
+ profile: config.profile,
6497
+ ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
6498
+ ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
6499
+ updatedAt: Math.max(existing.updatedAt, timestamp)
6500
+ };
6501
+ await writeJsonFileAtomic(this.sessionManifestPath(sessionId), patched);
6502
+ });
6503
+ }
6450
6504
  async reconcileSessionManifest(sessionId) {
6451
6505
  const session = await this.getSession(sessionId);
6452
6506
  if (session === void 0) {
@@ -6843,14 +6897,14 @@ var DEFAULT_SETTLE_DELAYS = {
6843
6897
  snapshot: 0
6844
6898
  };
6845
6899
  var DOM_ACTION_VISUAL_STABILITY_PROFILES = {
6846
- "dom.click": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
6847
- "dom.input": { settleMs: 750, scope: "visible-frames", timeoutMs: 7e3 },
6848
- "dom.scroll": { settleMs: 600, scope: "visible-frames", timeoutMs: 7e3 },
6900
+ "dom.click": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
6901
+ "dom.input": { settleMs: 750, scope: "main-frame", timeoutMs: 7e3 },
6902
+ "dom.scroll": { settleMs: 600, scope: "main-frame", timeoutMs: 7e3 },
6849
6903
  "dom.hover": { settleMs: 200, scope: "main-frame", timeoutMs: 2500 }
6850
6904
  };
6851
6905
  var DEFAULT_DOM_ACTION_VISUAL_STABILITY_PROFILE = {
6852
6906
  settleMs: 750,
6853
- scope: "visible-frames",
6907
+ scope: "main-frame",
6854
6908
  timeoutMs: 7e3
6855
6909
  };
6856
6910
  var NAVIGATION_VISUAL_STABILITY_PROFILE = {
@@ -6874,6 +6928,7 @@ var defaultDomActionSettleObserver = {
6874
6928
  pageRef: input.pageRef,
6875
6929
  timeoutMs: effectiveTimeout,
6876
6930
  settleMs: profile.settleMs,
6931
+ ...input.observedMutationQuietMs === void 0 ? {} : { initialQuietMs: input.observedMutationQuietMs },
6877
6932
  scope: profile.scope
6878
6933
  });
6879
6934
  return true;
@@ -6894,15 +6949,20 @@ var defaultNavigationSettleObserver = {
6894
6949
  return false;
6895
6950
  }
6896
6951
  try {
6897
- const startedAt = Date.now();
6898
- await input.engine.waitForPostLoadQuiet({
6899
- pageRef: input.pageRef,
6900
- timeoutMs: effectiveTimeout,
6901
- quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
6902
- captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
6903
- signal: input.signal
6904
- });
6905
- const visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
6952
+ let visualTimeout = effectiveTimeout;
6953
+ let initialQuietMs = input.observedMutationQuietMs ?? 0;
6954
+ if (!input.postLoadHandled) {
6955
+ const startedAt = Date.now();
6956
+ await input.engine.waitForPostLoadQuiet({
6957
+ pageRef: input.pageRef,
6958
+ timeoutMs: effectiveTimeout,
6959
+ quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
6960
+ captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
6961
+ signal: input.signal
6962
+ });
6963
+ visualTimeout = Math.max(0, effectiveTimeout - (Date.now() - startedAt));
6964
+ initialQuietMs = Math.max(initialQuietMs, DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS);
6965
+ }
6906
6966
  if (visualTimeout <= 0) {
6907
6967
  return true;
6908
6968
  }
@@ -6910,6 +6970,7 @@ var defaultNavigationSettleObserver = {
6910
6970
  pageRef: input.pageRef,
6911
6971
  timeoutMs: visualTimeout,
6912
6972
  settleMs: profile.settleMs,
6973
+ ...initialQuietMs <= 0 ? {} : { initialQuietMs },
6913
6974
  scope: profile.scope
6914
6975
  });
6915
6976
  return true;
@@ -7499,7 +7560,9 @@ function resolveExtractedValueInContext(normalizedValue, options) {
7499
7560
  function stripPositionClauses(nodes) {
7500
7561
  return (nodes || []).map((node) => ({
7501
7562
  ...node,
7502
- match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
7563
+ match: (node.match || []).filter(
7564
+ (clause) => clause.kind !== "position" && clause.kind !== "text"
7565
+ )
7503
7566
  }));
7504
7567
  }
7505
7568
  function dedupeSelectors(selectors) {
@@ -8993,7 +9056,7 @@ var DomActionExecutor = class {
8993
9056
  ...snapshot === void 0 ? {} : { snapshot },
8994
9057
  signal: timeout.signal,
8995
9058
  remainingMs: () => timeout.remainingMs(),
8996
- policySettle: async (targetPageRef, trigger) => {
9059
+ policySettle: async (targetPageRef, trigger, boundary2) => {
8997
9060
  try {
8998
9061
  await settleWithPolicy(this.options.policy.settle, {
8999
9062
  operation,
@@ -9001,7 +9064,9 @@ var DomActionExecutor = class {
9001
9064
  engine: this.options.engine,
9002
9065
  pageRef: targetPageRef,
9003
9066
  signal: timeout.signal,
9004
- remainingMs: timeout.remainingMs()
9067
+ remainingMs: timeout.remainingMs(),
9068
+ ...boundary2?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary2.observedMutationQuietMs },
9069
+ ...boundary2?.postLoadHandled === true ? { postLoadHandled: true } : {}
9005
9070
  });
9006
9071
  } catch (error) {
9007
9072
  if (snapshot !== void 0 && isSoftSettleTimeoutError(error, timeout.signal)) {
@@ -9769,7 +9834,9 @@ var DefaultDomRuntime = class {
9769
9834
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
9770
9835
  );
9771
9836
  }
9772
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
9837
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, {
9838
+ enableTextMatch: true
9839
+ });
9773
9840
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
9774
9841
  ...persist === void 0 ? {} : { persist },
9775
9842
  ...replayPath === void 0 ? {} : { replayPath }
@@ -10389,29 +10456,29 @@ function buildVariantDescriptorFromCluster(descriptors) {
10389
10456
  const keyStats = /* @__PURE__ */ new Map();
10390
10457
  for (const descriptor of descriptors) {
10391
10458
  for (const field of descriptor.fields) {
10392
- const stat2 = keyStats.get(field.path) ?? {
10459
+ const stat3 = keyStats.get(field.path) ?? {
10393
10460
  indices: /* @__PURE__ */ new Set(),
10394
10461
  pathNodes: [],
10395
10462
  attributes: [],
10396
10463
  sources: []
10397
10464
  };
10398
- stat2.indices.add(descriptor.index);
10465
+ stat3.indices.add(descriptor.index);
10399
10466
  if (isPersistedOpensteerExtractionValueNode(field.node)) {
10400
- stat2.pathNodes.push(field.node.$path);
10401
- stat2.attributes.push(field.node.attribute);
10467
+ stat3.pathNodes.push(field.node.$path);
10468
+ stat3.attributes.push(field.node.attribute);
10402
10469
  } else if (isPersistedOpensteerExtractionSourceNode(field.node)) {
10403
- stat2.sources.push("current_url");
10470
+ stat3.sources.push("current_url");
10404
10471
  }
10405
- keyStats.set(field.path, stat2);
10472
+ keyStats.set(field.path, stat3);
10406
10473
  }
10407
10474
  }
10408
10475
  const mergedFields = [];
10409
- for (const [fieldPath, stat2] of keyStats) {
10410
- if (stat2.indices.size < threshold) {
10476
+ for (const [fieldPath, stat3] of keyStats) {
10477
+ if (stat3.indices.size < threshold) {
10411
10478
  continue;
10412
10479
  }
10413
- if (stat2.pathNodes.length >= threshold) {
10414
- let mergedFieldPath = stat2.pathNodes.length === 1 ? sanitizeElementPath(stat2.pathNodes[0]) : mergeElementPathsByMajority(stat2.pathNodes);
10480
+ if (stat3.pathNodes.length >= threshold) {
10481
+ let mergedFieldPath = stat3.pathNodes.length === 1 ? sanitizeElementPath(stat3.pathNodes[0]) : mergeElementPathsByMajority(stat3.pathNodes);
10415
10482
  if (!mergedFieldPath) {
10416
10483
  continue;
10417
10484
  }
@@ -10419,8 +10486,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
10419
10486
  mergedFieldPath = relaxPathForSingleSample(mergedFieldPath, "field");
10420
10487
  }
10421
10488
  mergedFieldPath = minimizePathMatchClauses(mergedFieldPath, "field");
10422
- const attrThreshold = stat2.pathNodes.length === 1 ? 1 : majorityThreshold(stat2.pathNodes.length);
10423
- const attribute = pickModeString(stat2.attributes, attrThreshold);
10489
+ const attrThreshold = stat3.pathNodes.length === 1 ? 1 : majorityThreshold(stat3.pathNodes.length);
10490
+ const attribute = pickModeString(stat3.attributes, attrThreshold);
10424
10491
  mergedFields.push({
10425
10492
  path: fieldPath,
10426
10493
  node: createValueNode({
@@ -10430,7 +10497,7 @@ function buildVariantDescriptorFromCluster(descriptors) {
10430
10497
  });
10431
10498
  continue;
10432
10499
  }
10433
- const dominantSource = pickModeString(stat2.sources, threshold);
10500
+ const dominantSource = pickModeString(stat3.sources, threshold);
10434
10501
  if (dominantSource === "current_url") {
10435
10502
  mergedFields.push({
10436
10503
  path: fieldPath,
@@ -12013,9 +12080,9 @@ async function getProcessLiveness(owner) {
12013
12080
  if (typeof startedAtMs === "number") {
12014
12081
  return hasMatchingProcessStartTime(owner.processStartedAtMs, startedAtMs) ? "live" : "dead";
12015
12082
  }
12016
- return isProcessRunning(owner.pid) ? "unknown" : "dead";
12083
+ return isProcessRunning2(owner.pid) ? "unknown" : "dead";
12017
12084
  }
12018
- function isProcessRunning(pid) {
12085
+ function isProcessRunning2(pid) {
12019
12086
  try {
12020
12087
  process.kill(pid, 0);
12021
12088
  return true;
@@ -13435,11 +13502,31 @@ async function writePersistedSessionRecord(rootPath, record) {
13435
13502
  async function clearPersistedSessionRecord(rootPath, provider) {
13436
13503
  await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
13437
13504
  }
13505
+ function getPersistedLocalBrowserSessionOwnership(record) {
13506
+ return record.ownership === "attached" ? "attached" : "owned";
13507
+ }
13508
+ async function isAttachedLocalBrowserSessionReachable(record) {
13509
+ if (getPersistedLocalBrowserSessionOwnership(record) !== "attached") {
13510
+ return false;
13511
+ }
13512
+ if (record.engine !== "playwright" || record.endpoint === void 0) {
13513
+ return false;
13514
+ }
13515
+ try {
13516
+ await inspectCdpEndpoint({
13517
+ endpoint: record.endpoint,
13518
+ timeoutMs: 1500
13519
+ });
13520
+ return true;
13521
+ } catch {
13522
+ return false;
13523
+ }
13524
+ }
13438
13525
  function isPersistedCloudSessionRecord(value) {
13439
- 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);
13526
+ return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "cloud" && typeof value.sessionId === "string" && value.sessionId.length > 0 && (value.activePageUrl === void 0 || typeof value.activePageUrl === "string") && (value.activePageTitle === void 0 || typeof value.activePageTitle === "string") && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
13440
13527
  }
13441
13528
  function isPersistedLocalBrowserSessionRecord(value) {
13442
- 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;
13529
+ 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") && (value.activePageUrl === void 0 || typeof value.activePageUrl === "string") && (value.activePageTitle === void 0 || typeof value.activePageTitle === "string") && 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;
13443
13530
  }
13444
13531
  function resolveOpensteerStateDir() {
13445
13532
  const explicit = process.env.OPENSTEER_HOME?.trim();
@@ -13828,19 +13915,40 @@ function delay(ms) {
13828
13915
  var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
13829
13916
  var OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
13830
13917
  function buildLocalViewSessionId(input) {
13918
+ const ownership = input.ownership ?? "owned";
13919
+ const identity = ownership === "attached" ? input.endpoint ?? input.remoteDebuggingUrl ?? input.baseUrl ?? "attached" : `pid:${String(input.pid ?? 0)}`;
13831
13920
  const hash = crypto.createHash("sha256").update(`${input.rootPath}
13832
- ${String(input.pid)}
13921
+ ${ownership}
13922
+ ${identity}
13833
13923
  ${String(input.startedAt)}`).digest("hex");
13834
13924
  return `local_${hash.slice(0, 24)}`;
13835
13925
  }
13926
+ function buildLocalViewSessionIdForRecord(input) {
13927
+ const ownership = getPersistedLocalBrowserSessionOwnership(input.live);
13928
+ if (ownership === "attached") {
13929
+ return buildLocalViewSessionId({
13930
+ rootPath: input.rootPath,
13931
+ ownership,
13932
+ startedAt: input.live.startedAt,
13933
+ ...input.live.endpoint === void 0 ? {} : { endpoint: input.live.endpoint },
13934
+ ...input.live.baseUrl === void 0 ? {} : { baseUrl: input.live.baseUrl },
13935
+ ...input.live.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: input.live.remoteDebuggingUrl }
13936
+ });
13937
+ }
13938
+ return buildLocalViewSessionId({
13939
+ rootPath: input.rootPath,
13940
+ ownership,
13941
+ startedAt: input.live.startedAt,
13942
+ pid: input.live.pid
13943
+ });
13944
+ }
13836
13945
  function createLocalViewSessionManifest(input) {
13837
13946
  return {
13838
13947
  layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
13839
13948
  version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
13840
- sessionId: buildLocalViewSessionId({
13949
+ sessionId: buildLocalViewSessionIdForRecord({
13841
13950
  rootPath: input.rootPath,
13842
- pid: input.live.pid,
13843
- startedAt: input.live.startedAt
13951
+ live: input.live
13844
13952
  }),
13845
13953
  rootPath: input.rootPath,
13846
13954
  ...input.workspace === void 0 ? {} : { workspace: input.workspace },
@@ -14041,7 +14149,7 @@ var OpensteerBrowserManager = class {
14041
14149
  }
14042
14150
  const liveRecord = await this.readLivePersistentBrowser(await this.ensureWorkspaceStore());
14043
14151
  return {
14044
- mode: this.mode,
14152
+ mode: liveRecord?.ownership === "attached" ? "attach" : this.mode,
14045
14153
  engine: liveRecord?.engine ?? this.engineName,
14046
14154
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14047
14155
  live: liveRecord !== void 0
@@ -14169,6 +14277,7 @@ var OpensteerBrowserManager = class {
14169
14277
  });
14170
14278
  const liveRecord = {
14171
14279
  mode: "persistent",
14280
+ ownership: "owned",
14172
14281
  engine: "abp",
14173
14282
  baseUrl: launched.baseUrl,
14174
14283
  remoteDebuggingUrl: launched.remoteDebuggingUrl,
@@ -14255,11 +14364,78 @@ var OpensteerBrowserManager = class {
14255
14364
  }
14256
14365
  async createAttachEngine() {
14257
14366
  const endpoint = await resolveAttachEndpoint(this.browserOptions);
14258
- return this.createAttachedEngine({
14259
- endpoint,
14260
- ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14261
- freshTab: this.browserOptions?.freshTab ?? true,
14262
- onDispose: async () => void 0
14367
+ if (this.workspace === void 0) {
14368
+ return this.createAttachedEngine({
14369
+ endpoint,
14370
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14371
+ freshTab: this.browserOptions?.freshTab ?? true,
14372
+ onDispose: async () => void 0
14373
+ });
14374
+ }
14375
+ const workspace = await this.ensureWorkspaceStore();
14376
+ return workspace.lock(async () => {
14377
+ const live = await this.readLivePersistentBrowser(workspace);
14378
+ if (live) {
14379
+ if (live.engine !== "playwright") {
14380
+ throw new Error(
14381
+ `workspace "${this.workspace}" already has a live ${live.engine} browser. Close it before attaching a Playwright browser.`
14382
+ );
14383
+ }
14384
+ if (live.ownership !== "attached") {
14385
+ throw new Error(
14386
+ `workspace "${this.workspace}" already has a live Opensteer-owned browser. Close it before attaching another browser.`
14387
+ );
14388
+ }
14389
+ if (live.endpoint === void 0) {
14390
+ throw new Error("workspace live browser record is missing a DevTools endpoint.");
14391
+ }
14392
+ if (live.endpoint !== endpoint) {
14393
+ throw new Error(
14394
+ `workspace "${this.workspace}" is already attached to a different browser endpoint. Close it before reattaching.`
14395
+ );
14396
+ }
14397
+ await bestEffortRegisterLocalViewSession({
14398
+ rootPath: workspace.rootPath,
14399
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14400
+ live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
14401
+ ownership: "attached"
14402
+ });
14403
+ return this.createAttachedEngine({
14404
+ endpoint: live.endpoint,
14405
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14406
+ freshTab: this.browserOptions?.freshTab ?? true,
14407
+ onDispose: async () => void 0
14408
+ });
14409
+ }
14410
+ const liveRecord = {
14411
+ mode: "persistent",
14412
+ ownership: "attached",
14413
+ engine: "playwright",
14414
+ endpoint,
14415
+ pid: 0,
14416
+ startedAt: Date.now(),
14417
+ userDataDir: workspace.browserUserDataDir
14418
+ };
14419
+ await this.writeLivePersistentBrowser(workspace, liveRecord);
14420
+ const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
14421
+ await bestEffortRegisterLocalViewSession({
14422
+ rootPath: workspace.rootPath,
14423
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14424
+ live: persistedLiveRecord,
14425
+ ownership: "attached"
14426
+ });
14427
+ try {
14428
+ return await this.createAttachedEngine({
14429
+ endpoint,
14430
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
14431
+ freshTab: this.browserOptions?.freshTab ?? true,
14432
+ onDispose: async () => void 0
14433
+ });
14434
+ } catch (error) {
14435
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
14436
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14437
+ throw error;
14438
+ }
14263
14439
  });
14264
14440
  }
14265
14441
  async createPersistentEngine() {
@@ -14279,7 +14455,7 @@ var OpensteerBrowserManager = class {
14279
14455
  rootPath: workspace.rootPath,
14280
14456
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
14281
14457
  live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
14282
- ownership: "owned"
14458
+ ownership: live.ownership
14283
14459
  });
14284
14460
  return this.createAttachedEngine({
14285
14461
  endpoint: live.endpoint,
@@ -14295,6 +14471,7 @@ var OpensteerBrowserManager = class {
14295
14471
  });
14296
14472
  const liveRecord = {
14297
14473
  mode: "persistent",
14474
+ ownership: "owned",
14298
14475
  engine: "playwright",
14299
14476
  endpoint: launched.endpoint,
14300
14477
  pid: launched.pid,
@@ -14424,7 +14601,20 @@ var OpensteerBrowserManager = class {
14424
14601
  if (live === void 0) {
14425
14602
  return void 0;
14426
14603
  }
14427
- if (!isProcessRunning(live.pid)) {
14604
+ if (live.ownership === "attached") {
14605
+ const attachedRecord = toPersistedLocalBrowserSessionRecord(this.workspace, live);
14606
+ if (!await isAttachedLocalBrowserSessionReachable(attachedRecord)) {
14607
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, attachedRecord);
14608
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14609
+ return void 0;
14610
+ }
14611
+ return live;
14612
+ }
14613
+ if (!isProcessRunning2(live.pid)) {
14614
+ await this.unregisterLocalViewSessionForRecord(
14615
+ workspace.rootPath,
14616
+ toPersistedLocalBrowserSessionRecord(this.workspace, live)
14617
+ );
14428
14618
  await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14429
14619
  return void 0;
14430
14620
  }
@@ -14472,6 +14662,10 @@ var OpensteerBrowserManager = class {
14472
14662
  workspace.rootPath,
14473
14663
  toPersistedLocalBrowserSessionRecord(this.workspace, live)
14474
14664
  );
14665
+ if (live.ownership === "attached") {
14666
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
14667
+ return;
14668
+ }
14475
14669
  if (live.engine === "playwright") {
14476
14670
  if (live.endpoint !== void 0) {
14477
14671
  await requestBrowserClose(live.endpoint).catch(() => void 0);
@@ -14500,10 +14694,18 @@ var OpensteerBrowserManager = class {
14500
14694
  }
14501
14695
  async unregisterLocalViewSessionForRecord(rootPath, record) {
14502
14696
  await bestEffortUnregisterLocalViewSession(
14503
- buildLocalViewSessionId({
14697
+ getPersistedLocalBrowserSessionOwnership(record) === "attached" ? buildLocalViewSessionId({
14504
14698
  rootPath,
14505
- pid: record.pid,
14506
- startedAt: record.startedAt
14699
+ startedAt: record.startedAt,
14700
+ ownership: "attached",
14701
+ ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
14702
+ ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
14703
+ ...record.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: record.remoteDebuggingUrl }
14704
+ }) : buildLocalViewSessionId({
14705
+ rootPath,
14706
+ startedAt: record.startedAt,
14707
+ ownership: "owned",
14708
+ pid: record.pid
14507
14709
  })
14508
14710
  );
14509
14711
  }
@@ -14538,6 +14740,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
14538
14740
  version: 1,
14539
14741
  provider: "local",
14540
14742
  ...workspace === void 0 ? {} : { workspace },
14743
+ ownership: live.ownership,
14541
14744
  engine: live.engine,
14542
14745
  ...live.endpoint === void 0 ? {} : { endpoint: live.endpoint },
14543
14746
  ...live.baseUrl === void 0 ? {} : { baseUrl: live.baseUrl },
@@ -14553,6 +14756,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
14553
14756
  function toWorkspaceLiveBrowserRecord(record) {
14554
14757
  return {
14555
14758
  mode: "persistent",
14759
+ ownership: getPersistedLocalBrowserSessionOwnership(record),
14556
14760
  engine: record.engine,
14557
14761
  ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
14558
14762
  ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
@@ -14579,7 +14783,12 @@ function isAttachBrowserOptions(browser) {
14579
14783
  async function resolveAttachEndpoint(browser) {
14580
14784
  const endpoint = browser?.endpoint?.trim();
14581
14785
  if (endpoint && endpoint.length > 0) {
14582
- return endpoint;
14786
+ const inspected = await inspectCdpEndpoint({
14787
+ endpoint,
14788
+ ...browser?.headers === void 0 ? {} : { headers: browser.headers },
14789
+ timeoutMs: DEFAULT_TIMEOUT_MS
14790
+ });
14791
+ return inspected.endpoint;
14583
14792
  }
14584
14793
  const selection = await selectAttachBrowserCandidate({
14585
14794
  timeoutMs: DEFAULT_TIMEOUT_MS
@@ -14843,12 +15052,12 @@ async function waitForProcessExit(pid, timeoutMs) {
14843
15052
  }
14844
15053
  const deadline = Date.now() + timeoutMs;
14845
15054
  while (Date.now() < deadline) {
14846
- if (!isProcessRunning(pid)) {
15055
+ if (!isProcessRunning2(pid)) {
14847
15056
  return true;
14848
15057
  }
14849
15058
  await sleep2(50);
14850
15059
  }
14851
- return !isProcessRunning(pid);
15060
+ return !isProcessRunning2(pid);
14852
15061
  }
14853
15062
  function resolveAbpSessionDir(workspace) {
14854
15063
  return path10__default.default.join(workspace.livePath, "abp-session");
@@ -15755,11 +15964,11 @@ function delay2(ms) {
15755
15964
  function wrapCloudFetchError(error, input) {
15756
15965
  if (!(error instanceof Error)) {
15757
15966
  return new Error(
15758
- `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check OPENSTEER_BASE_URL and network reachability from this environment.`
15967
+ `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check the configured Opensteer cloud base URL and network reachability from this environment.`
15759
15968
  );
15760
15969
  }
15761
15970
  const wrapped = new Error(
15762
- `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check OPENSTEER_BASE_URL and network reachability from this environment.`,
15971
+ `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check the configured Opensteer cloud base URL and network reachability from this environment.`,
15763
15972
  {
15764
15973
  cause: error
15765
15974
  }
@@ -15799,6 +16008,7 @@ function asCloudErrorPayload(value) {
15799
16008
  }
15800
16009
 
15801
16010
  // src/cloud/config.ts
16011
+ var DEFAULT_OPENSTEER_CLOUD_BASE_URL = "https://api.opensteer.com";
15802
16012
  function resolveCloudConfig(input = {}) {
15803
16013
  const provider = resolveOpensteerProvider({
15804
16014
  ...input.provider === void 0 ? {} : { provider: input.provider },
@@ -15808,26 +16018,30 @@ function resolveCloudConfig(input = {}) {
15808
16018
  return void 0;
15809
16019
  }
15810
16020
  const cloudProvider = input.provider?.mode === "cloud" ? input.provider : void 0;
15811
- const apiKey = cloudProvider?.apiKey ?? input.environment?.OPENSTEER_API_KEY;
15812
- if (!apiKey || apiKey.trim().length === 0) {
16021
+ const apiKey = normalizeOptionalCloudConfigValue(cloudProvider?.apiKey) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_API_KEY);
16022
+ if (apiKey === void 0) {
15813
16023
  throw new Error("provider=cloud requires OPENSTEER_API_KEY or provider.apiKey.");
15814
16024
  }
15815
- const baseUrl = cloudProvider?.baseUrl ?? input.environment?.OPENSTEER_BASE_URL;
15816
- if (!baseUrl || baseUrl.trim().length === 0) {
15817
- throw new Error("provider=cloud requires OPENSTEER_BASE_URL or provider.baseUrl.");
15818
- }
15819
- const appBaseUrl = cloudProvider?.appBaseUrl ?? input.environment?.OPENSTEER_CLOUD_APP_BASE_URL;
16025
+ const baseUrl = normalizeOptionalCloudConfigValue(cloudProvider?.baseUrl) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_BASE_URL) ?? DEFAULT_OPENSTEER_CLOUD_BASE_URL;
16026
+ const appBaseUrl = normalizeOptionalCloudConfigValue(cloudProvider?.appBaseUrl) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_CLOUD_APP_BASE_URL);
15820
16027
  return {
15821
- apiKey: apiKey.trim(),
15822
- baseUrl: baseUrl.trim().replace(/\/+$/, ""),
15823
- ...appBaseUrl === void 0 || appBaseUrl.trim().length === 0 ? {} : { appBaseUrl: appBaseUrl.trim().replace(/\/+$/, "") },
16028
+ apiKey,
16029
+ baseUrl,
16030
+ ...appBaseUrl === void 0 ? {} : { appBaseUrl },
15824
16031
  ...cloudProvider?.browserProfile === void 0 ? {} : { browserProfile: cloudProvider.browserProfile }
15825
16032
  };
15826
16033
  }
16034
+ function normalizeOptionalCloudConfigValue(value) {
16035
+ if (typeof value !== "string") {
16036
+ return void 0;
16037
+ }
16038
+ const normalized = value.trim().replace(/\/+$/, "");
16039
+ return normalized.length === 0 ? void 0 : normalized;
16040
+ }
15827
16041
 
15828
16042
  // ../runtime-core/package.json
15829
16043
  var package_default = {
15830
- version: "0.2.5"};
16044
+ version: "0.2.7"};
15831
16045
 
15832
16046
  // ../runtime-core/src/version.ts
15833
16047
  var OPENSTEER_RUNTIME_CORE_VERSION = package_default.version;
@@ -16098,12 +16312,16 @@ function toOpensteerResolvedTarget(target) {
16098
16312
  documentRef: target.documentRef,
16099
16313
  documentEpoch: target.documentEpoch,
16100
16314
  nodeRef: target.nodeRef,
16101
- tagName: target.node.nodeName.toUpperCase(),
16315
+ tagName: toOpensteerTagName(target.node.nodeName),
16102
16316
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
16103
16317
  ...target.persist === void 0 ? {} : { persist: target.persist },
16104
16318
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
16105
16319
  };
16106
16320
  }
16321
+ function toOpensteerTagName(nodeName) {
16322
+ const tagName = String(nodeName).trim().toLowerCase();
16323
+ return tagName.length === 0 ? "element" : tagName;
16324
+ }
16107
16325
 
16108
16326
  // ../runtime-core/src/runtimes/computer-use/runtime.ts
16109
16327
  function createComputerUseRuntime(options) {
@@ -16137,7 +16355,7 @@ var DefaultComputerUseRuntime = class {
16137
16355
  screenshot,
16138
16356
  signal: input.timeout.signal,
16139
16357
  remainingMs: () => input.timeout.remainingMs(),
16140
- policySettle: async (pageRef, trigger) => {
16358
+ policySettle: async (pageRef, trigger, boundary) => {
16141
16359
  try {
16142
16360
  await settleWithPolicy(this.options.policy.settle, {
16143
16361
  operation: "computer.execute",
@@ -16145,7 +16363,9 @@ var DefaultComputerUseRuntime = class {
16145
16363
  engine: this.options.engine,
16146
16364
  pageRef,
16147
16365
  signal: input.timeout.signal,
16148
- remainingMs: input.timeout.remainingMs()
16366
+ remainingMs: input.timeout.remainingMs(),
16367
+ ...boundary?.observedMutationQuietMs === void 0 ? {} : { observedMutationQuietMs: boundary.observedMutationQuietMs },
16368
+ ...boundary?.postLoadHandled === true ? { postLoadHandled: true } : {}
16149
16369
  });
16150
16370
  } catch (error) {
16151
16371
  if (pageRef === input.pageRef && isSoftSettleTimeoutError(error, input.timeout.signal)) {
@@ -17323,28 +17543,24 @@ function restoreBoundedAttr(el, attr, value) {
17323
17543
  }
17324
17544
  setBoundedAttr(el, attr, value);
17325
17545
  }
17326
- function deduplicateImages(html) {
17546
+ function deduplicateImagesInDom($) {
17327
17547
  const seen = /* @__PURE__ */ new Set();
17328
- return html.replace(/<img\b([^>]*)>/gi, (full, attrContent) => {
17329
- if (/\bc\s*=/.test(attrContent)) {
17330
- return full;
17331
- }
17332
- const srcMatch = attrContent.match(/\bsrc\s*=\s*(["']?)(.*?)\1/);
17333
- const srcsetMatch = attrContent.match(/\bsrcset\s*=\s*(["'])(.*?)\1/);
17334
- let src = null;
17335
- if (srcMatch && srcMatch[2]) {
17336
- src = srcMatch[2].trim();
17337
- } else if (srcsetMatch && srcsetMatch[2]) {
17338
- src = srcsetMatch[2].split(",")[0]?.trim().split(" ")[0] ?? null;
17548
+ $("img").each(function deduplicateDomImage() {
17549
+ const el = $(this);
17550
+ if (el.attr("c") !== void 0) {
17551
+ return;
17339
17552
  }
17553
+ const srcValue = el.attr("src")?.trim();
17554
+ const srcsetValue = el.attr("srcset");
17555
+ const src = srcValue && srcValue.length > 0 ? srcValue : srcsetValue?.split(",")[0]?.trim().split(/\s+/u)[0];
17340
17556
  if (!src) {
17341
- return full;
17557
+ return;
17342
17558
  }
17343
17559
  if (seen.has(src)) {
17344
- return "";
17560
+ el.remove();
17561
+ return;
17345
17562
  }
17346
17563
  seen.add(src);
17347
- return full;
17348
17564
  });
17349
17565
  }
17350
17566
  function hasAttribute2(node, attr) {
@@ -17402,23 +17618,6 @@ function isPreservedImageElement(node) {
17402
17618
  function getElementsInReverseDocumentOrder($) {
17403
17619
  return $.root().find("*").toArray().reverse().filter((node) => node.type === "tag");
17404
17620
  }
17405
- function getNodeDepth(node) {
17406
- let depth = 0;
17407
- let current = node.parent;
17408
- while (current) {
17409
- depth++;
17410
- current = current.parent;
17411
- }
17412
- return depth;
17413
- }
17414
- function getElementsByDepthDescending($) {
17415
- const elements = $.root().find("*").toArray().filter((node) => node.type === "tag");
17416
- const depths = /* @__PURE__ */ new Map();
17417
- for (const el of elements) {
17418
- depths.set(el, getNodeDepth(el));
17419
- }
17420
- return elements.sort((a, b) => (depths.get(b) ?? 0) - (depths.get(a) ?? 0));
17421
- }
17422
17621
  function flattenExtractionTree($) {
17423
17622
  for (const node of getElementsInReverseDocumentOrder($)) {
17424
17623
  const el = $(node);
@@ -17436,19 +17635,6 @@ function flattenExtractionTree($) {
17436
17635
  el.replaceWith(el.contents());
17437
17636
  }
17438
17637
  }
17439
- function hasMarkedAncestor(el, attr) {
17440
- let current = el[0]?.parent;
17441
- while (current) {
17442
- if (!isElementLikeNode(current)) {
17443
- return false;
17444
- }
17445
- if (current.attribs?.[attr] !== void 0) {
17446
- return true;
17447
- }
17448
- current = current.parent;
17449
- }
17450
- return false;
17451
- }
17452
17638
  function isIndicatorImage(node) {
17453
17639
  return (node?.tagName || "").toLowerCase() === "img" && (hasAttribute2(node, "alt") || hasAttribute2(node, "src") || hasAttribute2(node, "srcset"));
17454
17640
  }
@@ -17566,7 +17752,7 @@ function serializeForExtraction($, root) {
17566
17752
  traverse(root, 0);
17567
17753
  return lines.map((l) => l.trim()).filter((l) => l.length > 0).join("");
17568
17754
  }
17569
- function isClickable($, el, context) {
17755
+ function isClickable(el, context) {
17570
17756
  if (context.hasPreMarked) {
17571
17757
  return el.attr(OPENSTEER_INTERACTIVE_ATTR) !== void 0;
17572
17758
  }
@@ -17600,21 +17786,17 @@ function isClickable($, el, context) {
17600
17786
  }
17601
17787
  return false;
17602
17788
  }
17603
- function cleanForExtraction(html) {
17789
+ function prepareExtractionSnapshotDom(html) {
17604
17790
  if (!html.trim()) {
17605
- return "";
17791
+ return void 0;
17606
17792
  }
17607
17793
  const $ = cheerio__namespace.load(html, { xmlMode: false });
17608
17794
  removeNoise($);
17609
17795
  removeComments($);
17610
17796
  markInlineSelfHiddenFallback($);
17611
17797
  pruneSelfHiddenNodes($);
17612
- const $clean = cheerio__namespace.load(
17613
- $.html().replace(/\n{2,}/g, "\n").trim(),
17614
- { xmlMode: false }
17615
- );
17616
- $clean("*").each(function stripAndRestoreExtractionAttrs() {
17617
- const el = $clean(this);
17798
+ $("*").each(function stripAndRestoreExtractionAttrs() {
17799
+ const el = $(this);
17618
17800
  const node = el[0];
17619
17801
  if (!node) {
17620
17802
  return;
@@ -17655,16 +17837,20 @@ function cleanForExtraction(html) {
17655
17837
  restoreBoundedAttr(el, "href", hrefValue);
17656
17838
  }
17657
17839
  });
17658
- flattenExtractionTree($clean);
17659
- const root = $clean.root()[0];
17840
+ flattenExtractionTree($);
17841
+ deduplicateImagesInDom($);
17842
+ return $;
17843
+ }
17844
+ function serializePreparedExtractionSnapshot($) {
17845
+ const root = $.root()[0];
17660
17846
  if (root === void 0) {
17661
17847
  return "";
17662
17848
  }
17663
- return deduplicateImages(serializeForExtraction($clean, root));
17849
+ return serializeForExtraction($, root);
17664
17850
  }
17665
- function cleanForAction(html) {
17851
+ function prepareActionSnapshotDom(html) {
17666
17852
  if (!html.trim()) {
17667
- return "";
17853
+ return void 0;
17668
17854
  }
17669
17855
  const $ = cheerio__namespace.load(html, { xmlMode: false });
17670
17856
  removeNoise($);
@@ -17673,13 +17859,12 @@ function cleanForAction(html) {
17673
17859
  pruneSelfHiddenNodes($);
17674
17860
  const clickableMark = "data-clickable-marker";
17675
17861
  const indicatorMark = "data-keep-indicator";
17676
- const branchMark = "data-keep-branch";
17677
17862
  const context = {
17678
17863
  hasPreMarked: $(`[${OPENSTEER_INTERACTIVE_ATTR}]`).length > 0
17679
17864
  };
17680
17865
  $("*").each(function markClickables() {
17681
17866
  const el = $(this);
17682
- if (isClickable($, el, context)) {
17867
+ if (isClickable(el, context)) {
17683
17868
  el.attr(clickableMark, "1");
17684
17869
  }
17685
17870
  });
@@ -17709,25 +17894,7 @@ function cleanForAction(html) {
17709
17894
  el.remove();
17710
17895
  }
17711
17896
  });
17712
- $(`[${clickableMark}], [${indicatorMark}]`).each(function markBranches() {
17713
- let current = $(this).parent();
17714
- while (current.length > 0) {
17715
- const node = current[0];
17716
- if (!node || node.type !== "tag") {
17717
- break;
17718
- }
17719
- const ancestor = current;
17720
- const tag = (node.tagName || "").toLowerCase();
17721
- if (ROOT_TAGS.has(tag) || ancestor.attr(clickableMark) !== void 0) {
17722
- break;
17723
- }
17724
- if (!isBoundaryTag(tag)) {
17725
- ancestor.attr(branchMark, "1");
17726
- }
17727
- current = ancestor.parent();
17728
- }
17729
- });
17730
- for (const node of getElementsByDepthDescending($)) {
17897
+ for (const node of getElementsInReverseDocumentOrder($)) {
17731
17898
  const el = $(node);
17732
17899
  const tag = (node.tagName || "").toLowerCase();
17733
17900
  if (ROOT_TAGS.has(tag) || isBoundaryTag(tag)) {
@@ -17736,17 +17903,7 @@ function cleanForAction(html) {
17736
17903
  if (el.attr(clickableMark) !== void 0 || el.attr(indicatorMark) !== void 0) {
17737
17904
  continue;
17738
17905
  }
17739
- const insideClickable = hasMarkedAncestor(el, clickableMark);
17740
- const preserveBranch = el.attr(branchMark) !== void 0;
17741
17906
  const hasContent = hasElementChildren(node) || hasDirectText(node);
17742
- if (insideClickable || preserveBranch) {
17743
- if (!hasContent) {
17744
- el.remove();
17745
- } else {
17746
- unwrapActionNode($, el);
17747
- }
17748
- continue;
17749
- }
17750
17907
  if (!hasContent) {
17751
17908
  el.remove();
17752
17909
  continue;
@@ -17847,13 +18004,20 @@ function cleanForAction(html) {
17847
18004
  }
17848
18005
  el.removeAttr(clickableMark);
17849
18006
  el.removeAttr(indicatorMark);
17850
- el.removeAttr(branchMark);
17851
18007
  el.removeAttr(OPENSTEER_INTERACTIVE_ATTR);
17852
18008
  el.removeAttr(OPENSTEER_HIDDEN_ATTR);
17853
18009
  el.removeAttr(OPENSTEER_SCROLLABLE_ATTR);
17854
18010
  el.removeAttr(OPENSTEER_SELF_HIDDEN_ATTR);
17855
18011
  });
17856
- return compactHtml(deduplicateImages($.html()));
18012
+ deduplicateImagesInDom($);
18013
+ return $;
18014
+ }
18015
+ function serializePreparedActionSnapshot($) {
18016
+ const normalized = compactHtml($.html());
18017
+ if (normalized.length === 0) {
18018
+ return "";
18019
+ }
18020
+ return cheerio__namespace.load(normalized, { xmlMode: false }).html();
17857
18021
  }
17858
18022
  var VOID_TAGS2 = /* @__PURE__ */ new Set([
17859
18023
  "area",
@@ -18076,27 +18240,32 @@ async function markLiveSnapshotSemantics(options) {
18076
18240
  const frames = await options.engine.listFrames({
18077
18241
  pageRef: options.pageRef
18078
18242
  });
18079
- for (const frame of frames) {
18080
- await evaluateFrameBestEffort(
18081
- options.engine,
18082
- frame.frameRef,
18083
- MARK_SNAPSHOT_SEMANTICS_SCRIPT,
18084
- SNAPSHOT_SEMANTIC_ARGS
18085
- );
18086
- }
18087
- return async () => {
18088
- for (const frame of frames) {
18089
- await evaluateFrameBestEffort(
18243
+ await Promise.all(
18244
+ frames.map(
18245
+ (frame) => evaluateFrameBestEffort(
18090
18246
  options.engine,
18091
18247
  frame.frameRef,
18092
- CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
18093
- CLEAR_SNAPSHOT_SEMANTIC_ARGS
18094
- );
18095
- }
18248
+ MARK_SNAPSHOT_SEMANTICS_SCRIPT,
18249
+ SNAPSHOT_SEMANTIC_ARGS
18250
+ )
18251
+ )
18252
+ );
18253
+ return async () => {
18254
+ await Promise.all(
18255
+ frames.map(
18256
+ (frame) => evaluateFrameBestEffort(
18257
+ options.engine,
18258
+ frame.frameRef,
18259
+ CLEAR_SNAPSHOT_SEMANTICS_SCRIPT,
18260
+ CLEAR_SNAPSHOT_SEMANTIC_ARGS
18261
+ )
18262
+ )
18263
+ );
18096
18264
  };
18097
18265
  }
18098
18266
 
18099
18267
  // ../runtime-core/src/sdk/snapshot/compiler.ts
18268
+ var EXTRACTION_SKIPPED_COUNTER_TAGS = /* @__PURE__ */ new Set(["html", "head", "body"]);
18100
18269
  var INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
18101
18270
  "c",
18102
18271
  OPENSTEER_BOUNDARY_ATTR,
@@ -18111,7 +18280,7 @@ var INTERNAL_SNAPSHOT_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
18111
18280
  var MAX_LIVE_COUNTER_SYNC_ATTEMPTS = 4;
18112
18281
  var CLEAR_LIVE_COUNTERS_SCRIPT = `(({ sparseCounterAttr }) => {
18113
18282
  const walk = (root) => {
18114
- for (const child of root.children) {
18283
+ for (const child of Array.from(root?.children || [])) {
18115
18284
  child.removeAttribute("c");
18116
18285
  child.removeAttribute(sparseCounterAttr);
18117
18286
  walk(child);
@@ -18127,7 +18296,7 @@ var CLEAR_LIVE_COUNTERS_SCRIPT = `(({ sparseCounterAttr }) => {
18127
18296
  var ASSIGN_SPARSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, startCounter }) => {
18128
18297
  let counter = startCounter;
18129
18298
  const walk = (root) => {
18130
- for (const child of root.children) {
18299
+ for (const child of Array.from(root?.children || [])) {
18131
18300
  child.setAttribute(sparseCounterAttr, String(counter++));
18132
18301
  walk(child);
18133
18302
  if (child.shadowRoot) {
@@ -18141,7 +18310,7 @@ var ASSIGN_SPARSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, startCounter }) => {
18141
18310
  })`;
18142
18311
  var APPLY_DENSE_COUNTERS_SCRIPT = `(({ sparseCounterAttr, mapping }) => {
18143
18312
  const walk = (root) => {
18144
- for (const child of root.children) {
18313
+ for (const child of Array.from(root?.children || [])) {
18145
18314
  child.removeAttribute("c");
18146
18315
  const sparse = child.getAttribute(sparseCounterAttr);
18147
18316
  if (sparse !== null) {
@@ -18204,20 +18373,22 @@ function ensureSparseCountersForAllRecords(counterRecords) {
18204
18373
  async function clearOpensteerLiveCounters(engine, pageRef) {
18205
18374
  const frames = await engine.listFrames({ pageRef });
18206
18375
  const failures = [];
18207
- for (const frame of frames) {
18208
- try {
18209
- await engine.evaluateFrame({
18210
- frameRef: frame.frameRef,
18211
- script: CLEAR_LIVE_COUNTERS_SCRIPT,
18212
- args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
18213
- });
18214
- } catch (error) {
18215
- if (isDetachedFrameSyncError(error)) {
18216
- continue;
18376
+ await Promise.all(
18377
+ frames.map(async (frame) => {
18378
+ try {
18379
+ await engine.evaluateFrame({
18380
+ frameRef: frame.frameRef,
18381
+ script: CLEAR_LIVE_COUNTERS_SCRIPT,
18382
+ args: [{ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR }]
18383
+ });
18384
+ } catch (error) {
18385
+ if (isDetachedFrameSyncError(error)) {
18386
+ return;
18387
+ }
18388
+ failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
18217
18389
  }
18218
- failures.push(`frame ${frame.frameRef} could not be cleared (${describeError(error)}).`);
18219
- }
18220
- }
18390
+ })
18391
+ );
18221
18392
  if (failures.length > 0) {
18222
18393
  throw buildLiveCounterSyncError("clear live counters", failures);
18223
18394
  }
@@ -18266,25 +18437,29 @@ async function syncDenseCountersToLiveDom(engine, pageRef, sparseToDirectMapping
18266
18437
  denseCounter
18267
18438
  ])
18268
18439
  );
18269
- for (const frame of frames) {
18270
- try {
18271
- await engine.evaluateFrame({
18272
- frameRef: frame.frameRef,
18273
- script: APPLY_DENSE_COUNTERS_SCRIPT,
18274
- args: [
18275
- {
18276
- sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
18277
- mapping: mappingObj
18278
- }
18279
- ]
18280
- });
18281
- } catch (error) {
18282
- if (isDetachedFrameSyncError(error)) {
18283
- continue;
18440
+ await Promise.all(
18441
+ frames.map(async (frame) => {
18442
+ try {
18443
+ await engine.evaluateFrame({
18444
+ frameRef: frame.frameRef,
18445
+ script: APPLY_DENSE_COUNTERS_SCRIPT,
18446
+ args: [
18447
+ {
18448
+ sparseCounterAttr: OPENSTEER_SPARSE_COUNTER_ATTR,
18449
+ mapping: mappingObj
18450
+ }
18451
+ ]
18452
+ });
18453
+ } catch (error) {
18454
+ if (isDetachedFrameSyncError(error)) {
18455
+ return;
18456
+ }
18457
+ failures.push(
18458
+ `frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`
18459
+ );
18284
18460
  }
18285
- failures.push(`frame ${frame.frameRef} could not be synchronized (${describeError(error)}).`);
18286
- }
18287
- }
18461
+ })
18462
+ );
18288
18463
  if (failures.length > 0) {
18289
18464
  throw buildLiveCounterSyncError("synchronize dense counters", failures);
18290
18465
  }
@@ -18302,26 +18477,26 @@ async function compileOpensteerSnapshot(options) {
18302
18477
  await clearOpensteerLiveCounters(options.engine, options.pageRef);
18303
18478
  await assignSparseCountersToLiveDom(options.engine, options.pageRef);
18304
18479
  const pageInfo = await options.engine.getPageInfo({ pageRef: options.pageRef });
18305
- const mainSnapshot = await getMainDocumentSnapshot(options.engine, options.pageRef);
18306
- const snapshotsByDocumentRef = await collectDocumentSnapshots(options.engine, mainSnapshot);
18480
+ const { mainSnapshot, snapshotsByDocumentRef } = await getPageDocumentSnapshots(
18481
+ options.engine,
18482
+ options.pageRef
18483
+ );
18307
18484
  await cleanupLiveSemantics();
18308
18485
  cleanupLiveSemantics = async () => {
18309
18486
  };
18310
- const snapshotIndices = /* @__PURE__ */ new Map();
18311
18487
  const renderedNodes = /* @__PURE__ */ new Map();
18312
18488
  const rawHtml = renderDocumentSnapshot(
18313
18489
  mainSnapshot.documentRef,
18314
18490
  snapshotsByDocumentRef,
18315
- snapshotIndices,
18316
18491
  renderedNodes,
18317
18492
  {
18318
18493
  iframeDepth: 0,
18319
18494
  shadowDepth: 0
18320
18495
  }
18321
18496
  );
18322
- const cleanedHtml = options.mode === "extraction" ? cleanForExtraction(rawHtml) : cleanForAction(rawHtml);
18323
- const compiledHtml = assignCounters(cleanedHtml, renderedNodes);
18324
- const finalHtml = options.mode === "extraction" ? unwrapExtractionHtml(compiledHtml.html) : compiledHtml.html;
18497
+ const preparedSnapshotDom = options.mode === "extraction" ? prepareExtractionSnapshotDom(rawHtml) : prepareActionSnapshotDom(rawHtml);
18498
+ const compiledHtml = assignCountersInDom(preparedSnapshotDom, renderedNodes, options.mode);
18499
+ const finalHtml = preparedSnapshotDom === void 0 ? "" : options.mode === "extraction" ? serializePreparedExtractionSnapshot(preparedSnapshotDom) : serializePreparedActionSnapshot(preparedSnapshotDom);
18325
18500
  ensureSparseCountersForAllRecords(compiledHtml.counterRecords);
18326
18501
  await syncDenseCountersToLiveDom(
18327
18502
  options.engine,
@@ -18356,6 +18531,25 @@ async function getMainDocumentSnapshot(engine, pageRef) {
18356
18531
  }
18357
18532
  return engine.getDomSnapshot({ frameRef: mainFrame.frameRef });
18358
18533
  }
18534
+ async function getPageDocumentSnapshots(engine, pageRef) {
18535
+ const bundleEngine = engine;
18536
+ const bundledSnapshots = await bundleEngine.getPageDomSnapshots?.({ pageRef });
18537
+ if (bundledSnapshots && bundledSnapshots.length > 0) {
18538
+ const mainSnapshot2 = bundledSnapshots.find((snapshot) => snapshot.parentDocumentRef === void 0) ?? bundledSnapshots[0];
18539
+ return {
18540
+ mainSnapshot: mainSnapshot2,
18541
+ snapshotsByDocumentRef: new Map(
18542
+ bundledSnapshots.map((snapshot) => [snapshot.documentRef, snapshot])
18543
+ )
18544
+ };
18545
+ }
18546
+ const mainSnapshot = await getMainDocumentSnapshot(engine, pageRef);
18547
+ const snapshotsByDocumentRef = await collectDocumentSnapshots(engine, mainSnapshot);
18548
+ return {
18549
+ mainSnapshot,
18550
+ snapshotsByDocumentRef
18551
+ };
18552
+ }
18359
18553
  async function collectDocumentSnapshots(engine, mainSnapshot) {
18360
18554
  const snapshotsByDocumentRef = /* @__PURE__ */ new Map([
18361
18555
  [mainSnapshot.documentRef, mainSnapshot]
@@ -18374,7 +18568,7 @@ async function collectDocumentSnapshots(engine, mainSnapshot) {
18374
18568
  }
18375
18569
  return snapshotsByDocumentRef;
18376
18570
  }
18377
- function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18571
+ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, renderedNodes, depth) {
18378
18572
  const snapshot = snapshotsByDocumentRef.get(documentRef);
18379
18573
  if (!snapshot) {
18380
18574
  return "";
@@ -18386,17 +18580,9 @@ function renderDocumentSnapshot(documentRef, snapshotsByDocumentRef, snapshotInd
18386
18580
  `snapshot ${snapshot.documentRef} is missing root node ${String(snapshot.rootSnapshotNodeId)}`
18387
18581
  );
18388
18582
  }
18389
- return renderNode(
18390
- snapshot,
18391
- rootNode,
18392
- nodesById,
18393
- snapshotsByDocumentRef,
18394
- snapshotIndices,
18395
- renderedNodes,
18396
- depth
18397
- );
18583
+ return renderNode(snapshot, rootNode, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18398
18584
  }
18399
- function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18585
+ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
18400
18586
  if (node.nodeType === 3) {
18401
18587
  return escapeHtml(node.nodeValue || node.textContent || "");
18402
18588
  }
@@ -18404,56 +18590,26 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18404
18590
  return "";
18405
18591
  }
18406
18592
  if (node.nodeType === 9 || node.nodeType === 11) {
18407
- return renderChildren(
18408
- snapshot,
18409
- node,
18410
- nodesById,
18411
- snapshotsByDocumentRef,
18412
- snapshotIndices,
18413
- renderedNodes,
18414
- depth
18415
- );
18593
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18416
18594
  }
18417
18595
  if (node.nodeType !== 1) {
18418
- return renderChildren(
18419
- snapshot,
18420
- node,
18421
- nodesById,
18422
- snapshotsByDocumentRef,
18423
- snapshotIndices,
18424
- renderedNodes,
18425
- depth
18426
- );
18596
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18427
18597
  }
18428
18598
  const tagName = normalizeTagName(node.nodeName);
18429
18599
  if (isPseudoElementTagName(tagName)) {
18430
- return renderChildren(
18431
- snapshot,
18432
- node,
18433
- nodesById,
18434
- snapshotsByDocumentRef,
18435
- snapshotIndices,
18436
- renderedNodes,
18437
- depth
18438
- );
18600
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18439
18601
  }
18440
18602
  if ((depth.iframeDepth > 0 || depth.shadowDepth > 0) && (tagName === "html" || tagName === "head" || tagName === "body")) {
18441
- return renderChildren(
18442
- snapshot,
18443
- node,
18444
- nodesById,
18445
- snapshotsByDocumentRef,
18446
- snapshotIndices,
18447
- renderedNodes,
18448
- depth
18449
- );
18603
+ return renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth);
18450
18604
  }
18451
18605
  const snapshotAttributes = normalizeNodeAttributes(node.attributes);
18606
+ const snapshotAttributeIndex = indexNodeAttributes(snapshotAttributes);
18452
18607
  const authoredAttributes = stripInternalSnapshotAttributes(snapshotAttributes);
18608
+ const authoredAttributeIndex = indexNodeAttributes(authoredAttributes);
18453
18609
  const attributes = [...authoredAttributes];
18454
- const subtreeHidden = hasAttribute3(snapshotAttributes, OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
18455
- const selfHidden = !subtreeHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
18456
- const interactive = !subtreeHidden && !selfHidden && (hasAttribute3(snapshotAttributes, OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes));
18610
+ const subtreeHidden = snapshotAttributeIndex.has(OPENSTEER_HIDDEN_ATTR) || isLikelySubtreeHidden(node);
18611
+ const selfHidden = !subtreeHidden && (snapshotAttributeIndex.has(OPENSTEER_SELF_HIDDEN_ATTR) || isLikelySelfHidden(node, nodesById));
18612
+ const interactive = !subtreeHidden && !selfHidden && (snapshotAttributeIndex.has(OPENSTEER_INTERACTIVE_ATTR) || isLikelyInteractive(tagName, node, authoredAttributes, authoredAttributeIndex));
18457
18613
  if (interactive) {
18458
18614
  attributes.push({ name: OPENSTEER_INTERACTIVE_ATTR, value: "1" });
18459
18615
  }
@@ -18462,7 +18618,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18462
18618
  } else if (selfHidden) {
18463
18619
  attributes.push({ name: OPENSTEER_SELF_HIDDEN_ATTR, value: "1" });
18464
18620
  }
18465
- const sparseCounter = findAttributeValue(snapshotAttributes, OPENSTEER_SPARSE_COUNTER_ATTR);
18621
+ const sparseCounter = snapshotAttributeIndex.get(OPENSTEER_SPARSE_COUNTER_ATTR);
18466
18622
  if (sparseCounter !== void 0) {
18467
18623
  attributes.push({ name: OPENSTEER_SPARSE_COUNTER_ATTR, value: sparseCounter });
18468
18624
  }
@@ -18473,21 +18629,18 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18473
18629
  const syntheticNodeId = buildSyntheticNodeId(snapshot, node);
18474
18630
  attributes.push({ name: OPENSTEER_NODE_ID_ATTR, value: syntheticNodeId });
18475
18631
  renderedNodes.set(syntheticNodeId, {
18476
- locator: createNodeLocator(snapshot.documentRef, snapshot.documentEpoch, node.nodeRef),
18477
- anchor: buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices),
18478
18632
  pageRef: snapshot.pageRef,
18479
18633
  frameRef: snapshot.frameRef,
18480
18634
  documentRef: snapshot.documentRef,
18481
18635
  documentEpoch: snapshot.documentEpoch,
18482
18636
  nodeRef: node.nodeRef,
18483
18637
  tagName: tagName.toUpperCase(),
18484
- pathHint: buildPathHint(tagName, authoredAttributes),
18485
- ...buildTextSnippet(node.textContent) === void 0 ? {} : { text: buildTextSnippet(node.textContent) },
18486
18638
  ...authoredAttributes.length === 0 ? {} : { attributes: authoredAttributes },
18487
18639
  iframeDepth: depth.iframeDepth,
18488
18640
  shadowDepth: depth.shadowDepth,
18489
18641
  interactive,
18490
- liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById)
18642
+ liveCounterSyncEligible: isLiveCounterSyncEligible(node, nodesById),
18643
+ ...node.textContent === void 0 ? {} : { textContent: node.textContent }
18491
18644
  });
18492
18645
  }
18493
18646
  const attributeText = attributesToHtml(attributes);
@@ -18496,7 +18649,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18496
18649
  node,
18497
18650
  nodesById,
18498
18651
  snapshotsByDocumentRef,
18499
- snapshotIndices,
18500
18652
  renderedNodes,
18501
18653
  depth
18502
18654
  );
@@ -18507,7 +18659,6 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18507
18659
  const iframeHtml = renderDocumentSnapshot(
18508
18660
  node.contentDocumentRef,
18509
18661
  snapshotsByDocumentRef,
18510
- snapshotIndices,
18511
18662
  renderedNodes,
18512
18663
  {
18513
18664
  iframeDepth: depth.iframeDepth + 1,
@@ -18519,7 +18670,7 @@ function renderNode(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotI
18519
18670
  }
18520
18671
  return `${elementHtml}<${OPENSTEER_IFRAME_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="iframe">${iframeHtml}</${OPENSTEER_IFRAME_BOUNDARY_TAG}>`;
18521
18672
  }
18522
- function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snapshotIndices, renderedNodes, depth) {
18673
+ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, renderedNodes, depth) {
18523
18674
  const regularChildren = [];
18524
18675
  const shadowChildren = [];
18525
18676
  for (const childSnapshotNodeId of node.childSnapshotNodeIds) {
@@ -18536,18 +18687,10 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
18536
18687
  const chunks = [];
18537
18688
  if (shadowChildren.length > 0) {
18538
18689
  const shadowHtml = shadowChildren.map(
18539
- (child) => renderNode(
18540
- snapshot,
18541
- child,
18542
- nodesById,
18543
- snapshotsByDocumentRef,
18544
- snapshotIndices,
18545
- renderedNodes,
18546
- {
18547
- iframeDepth: depth.iframeDepth,
18548
- shadowDepth: depth.shadowDepth + 1
18549
- }
18550
- )
18690
+ (child) => renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, {
18691
+ iframeDepth: depth.iframeDepth,
18692
+ shadowDepth: depth.shadowDepth + 1
18693
+ })
18551
18694
  ).join("");
18552
18695
  chunks.push(
18553
18696
  `<${OPENSTEER_SHADOW_BOUNDARY_TAG} ${OPENSTEER_BOUNDARY_ATTR}="shadow">${shadowHtml}</${OPENSTEER_SHADOW_BOUNDARY_TAG}>`
@@ -18555,24 +18698,21 @@ function renderChildren(snapshot, node, nodesById, snapshotsByDocumentRef, snaps
18555
18698
  }
18556
18699
  for (const child of regularChildren) {
18557
18700
  chunks.push(
18558
- renderNode(
18559
- snapshot,
18560
- child,
18561
- nodesById,
18562
- snapshotsByDocumentRef,
18563
- snapshotIndices,
18564
- renderedNodes,
18565
- depth
18566
- )
18701
+ renderNode(snapshot, child, nodesById, snapshotsByDocumentRef, renderedNodes, depth)
18567
18702
  );
18568
18703
  }
18569
18704
  return chunks.join("");
18570
18705
  }
18571
- function assignCounters(cleanedHtml, renderedNodes) {
18572
- const $ = cheerio__namespace.load(cleanedHtml, { xmlMode: false });
18706
+ function assignCountersInDom($, renderedNodes, mode) {
18573
18707
  const counterRecords = /* @__PURE__ */ new Map();
18574
18708
  const sparseToDirectMapping = /* @__PURE__ */ new Map();
18575
18709
  let nextCounter = 1;
18710
+ if (!$) {
18711
+ return {
18712
+ counterRecords,
18713
+ sparseToDirectMapping
18714
+ };
18715
+ }
18576
18716
  $("*").each(function assignElementCounter() {
18577
18717
  const el = $(this);
18578
18718
  const syntheticNodeId = el.attr(OPENSTEER_NODE_ID_ATTR);
@@ -18584,14 +18724,24 @@ function assignCounters(cleanedHtml, renderedNodes) {
18584
18724
  if (!rendered) {
18585
18725
  return;
18586
18726
  }
18727
+ if (mode === "extraction" && EXTRACTION_SKIPPED_COUNTER_TAGS.has(rendered.tagName.toLowerCase())) {
18728
+ el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
18729
+ return;
18730
+ }
18587
18731
  const rawSparseCounter = el.attr(OPENSTEER_SPARSE_COUNTER_ATTR);
18588
18732
  el.removeAttr(OPENSTEER_SPARSE_COUNTER_ATTR);
18589
18733
  const sparseCounter = rawSparseCounter ? Number.parseInt(rawSparseCounter, 10) : void 0;
18734
+ const replayableSparseCounter = typeof sparseCounter === "number" && Number.isFinite(sparseCounter) ? sparseCounter : void 0;
18735
+ if (rendered.liveCounterSyncEligible && replayableSparseCounter === void 0) {
18736
+ return;
18737
+ }
18590
18738
  const counter = nextCounter++;
18591
18739
  el.attr("c", String(counter));
18592
- if (sparseCounter !== void 0 && Number.isFinite(sparseCounter)) {
18593
- sparseToDirectMapping.set(sparseCounter, counter);
18740
+ if (replayableSparseCounter !== void 0) {
18741
+ sparseToDirectMapping.set(replayableSparseCounter, counter);
18594
18742
  }
18743
+ const pathHint = buildPathHint(rendered.tagName.toLowerCase(), rendered.attributes ?? []);
18744
+ const text = buildTextSnippet(rendered.textContent);
18595
18745
  counterRecords.set(counter, {
18596
18746
  element: counter,
18597
18747
  pageRef: rendered.pageRef,
@@ -18600,20 +18750,17 @@ function assignCounters(cleanedHtml, renderedNodes) {
18600
18750
  documentEpoch: rendered.documentEpoch,
18601
18751
  nodeRef: rendered.nodeRef,
18602
18752
  tagName: rendered.tagName,
18603
- pathHint: rendered.pathHint,
18604
- ...rendered.text === void 0 ? {} : { text: rendered.text },
18753
+ pathHint,
18754
+ ...text === void 0 ? {} : { text },
18605
18755
  ...rendered.attributes === void 0 ? {} : { attributes: rendered.attributes },
18606
18756
  iframeDepth: rendered.iframeDepth,
18607
18757
  shadowDepth: rendered.shadowDepth,
18608
18758
  interactive: rendered.interactive,
18609
18759
  liveCounterSyncEligible: rendered.liveCounterSyncEligible,
18610
- locator: rendered.locator,
18611
- anchor: rendered.anchor,
18612
- ...sparseCounter !== void 0 && Number.isFinite(sparseCounter) ? { sparseCounter } : {}
18760
+ ...replayableSparseCounter === void 0 ? {} : { sparseCounter: replayableSparseCounter }
18613
18761
  });
18614
18762
  });
18615
18763
  return {
18616
- html: $.html(),
18617
18764
  counterRecords,
18618
18765
  sparseToDirectMapping
18619
18766
  };
@@ -18687,28 +18834,28 @@ function isLikelySelfHidden(node, nodesById) {
18687
18834
  }
18688
18835
  return !hasVisibleOutOfFlowChild(node, nodesById);
18689
18836
  }
18690
- function isLikelyInteractive(tagName, node, attributes) {
18837
+ function isLikelyInteractive(tagName, node, attributes, attributeIndex) {
18691
18838
  if (NATIVE_INTERACTIVE_TAGS.has(tagName)) {
18692
- if (tagName === "input" && findAttributeValue(attributes, "type")?.toLowerCase() === "hidden") {
18839
+ if (tagName === "input" && attributeIndex.get("type")?.toLowerCase() === "hidden") {
18693
18840
  return false;
18694
18841
  }
18695
18842
  if (tagName !== "a") {
18696
18843
  return true;
18697
18844
  }
18698
18845
  }
18699
- if (tagName === "a" && findAttributeValue(attributes, "href") !== void 0) {
18846
+ if (tagName === "a" && attributeIndex.has("href")) {
18700
18847
  return true;
18701
18848
  }
18702
- 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) {
18849
+ if (attributeIndex.has("onclick") || attributeIndex.has("onmousedown") || attributeIndex.has("onmouseup") || attributeIndex.has("data-action") || attributeIndex.has("data-click") || attributeIndex.has("data-toggle")) {
18703
18850
  return true;
18704
18851
  }
18705
- if (hasNonNegativeTabIndex(findAttributeValue(attributes, "tabindex"))) {
18852
+ if (hasNonNegativeTabIndex(attributeIndex.get("tabindex"))) {
18706
18853
  return true;
18707
18854
  }
18708
- if (findAttributeValue(attributes, "contenteditable")?.toLowerCase() === "true") {
18855
+ if (attributeIndex.get("contenteditable")?.toLowerCase() === "true") {
18709
18856
  return true;
18710
18857
  }
18711
- const role = findAttributeValue(attributes, "role")?.toLowerCase();
18858
+ const role = attributeIndex.get("role")?.toLowerCase();
18712
18859
  return role !== void 0 && INTERACTIVE_ROLE_SET.has(role);
18713
18860
  }
18714
18861
  function hasVisibleOutOfFlowChild(node, nodesById) {
@@ -18771,14 +18918,6 @@ function parseOpacity(value) {
18771
18918
  const parsed = Number.parseFloat(value);
18772
18919
  return Number.isFinite(parsed) ? parsed : Number.NaN;
18773
18920
  }
18774
- function hasAttribute3(attributes, name) {
18775
- const normalizedName = name.toLowerCase();
18776
- return attributes.some((attribute) => attribute.name.toLowerCase() === normalizedName);
18777
- }
18778
- function unwrapExtractionHtml(html) {
18779
- const $ = cheerio__namespace.load(html, { xmlMode: false });
18780
- return $("body").html()?.trim() || html;
18781
- }
18782
18921
  function buildSyntheticNodeId(snapshot, node) {
18783
18922
  return `${snapshot.documentRef}:${String(snapshot.documentEpoch)}:${String(node.snapshotNodeId)}`;
18784
18923
  }
@@ -18825,6 +18964,13 @@ function findAttributeValue(attributes, name) {
18825
18964
  const normalizedName = name.toLowerCase();
18826
18965
  return attributes.find((attribute) => attribute.name.toLowerCase() === normalizedName)?.value;
18827
18966
  }
18967
+ function indexNodeAttributes(attributes) {
18968
+ const indexed = /* @__PURE__ */ new Map();
18969
+ for (const attribute of attributes) {
18970
+ indexed.set(attribute.name.toLowerCase(), attribute.value);
18971
+ }
18972
+ return indexed;
18973
+ }
18828
18974
  function attributesToHtml(attributes) {
18829
18975
  if (attributes.length === 0) {
18830
18976
  return "";
@@ -18834,56 +18980,6 @@ function attributesToHtml(attributes) {
18834
18980
  function escapeAttribute(value) {
18835
18981
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
18836
18982
  }
18837
- function buildSnapshotElementAnchor(snapshot, node, snapshotsByDocumentRef, snapshotIndices) {
18838
- const index = getSnapshotIndex(snapshot.documentRef, snapshotsByDocumentRef, snapshotIndices);
18839
- const localAnchor = buildLocalStructuralElementAnchor(index, node);
18840
- return prefixIframeContext(snapshot, localAnchor, snapshotsByDocumentRef, snapshotIndices);
18841
- }
18842
- function prefixIframeContext(snapshot, localPath, snapshotsByDocumentRef, snapshotIndices) {
18843
- if (snapshot.parentDocumentRef === void 0) {
18844
- return sanitizeStructuralElementAnchor(localPath);
18845
- }
18846
- const parentSnapshot = snapshotsByDocumentRef.get(snapshot.parentDocumentRef);
18847
- if (!parentSnapshot) {
18848
- throw new Error(
18849
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no parent snapshot`
18850
- );
18851
- }
18852
- const parentIndex = getSnapshotIndex(
18853
- parentSnapshot.documentRef,
18854
- snapshotsByDocumentRef,
18855
- snapshotIndices
18856
- );
18857
- const iframeHost = findIframeHostNode(parentIndex, snapshot.documentRef);
18858
- if (!iframeHost) {
18859
- throw new Error(
18860
- `document ${snapshot.documentRef} has parent ${snapshot.parentDocumentRef} but no iframe host`
18861
- );
18862
- }
18863
- const hostPath = buildSnapshotElementAnchor(
18864
- parentSnapshot,
18865
- iframeHost,
18866
- snapshotsByDocumentRef,
18867
- snapshotIndices
18868
- );
18869
- return sanitizeStructuralElementAnchor({
18870
- context: [...hostPath.context, { kind: "iframe", host: hostPath.nodes }, ...localPath.context],
18871
- nodes: localPath.nodes
18872
- });
18873
- }
18874
- function getSnapshotIndex(documentRef, snapshotsByDocumentRef, snapshotIndices) {
18875
- const existing = snapshotIndices.get(documentRef);
18876
- if (existing) {
18877
- return existing;
18878
- }
18879
- const snapshot = snapshotsByDocumentRef.get(documentRef);
18880
- if (!snapshot) {
18881
- throw new Error(`missing DOM snapshot for ${documentRef}`);
18882
- }
18883
- const index = createSnapshotIndex(snapshot);
18884
- snapshotIndices.set(documentRef, index);
18885
- return index;
18886
- }
18887
18983
  function escapeHtml(value) {
18888
18984
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18889
18985
  }
@@ -20016,7 +20112,10 @@ var OpensteerSessionRuntime = class {
20016
20112
  sessionRef;
20017
20113
  pageRef;
20018
20114
  runId;
20019
- observations;
20115
+ observationSessions = /* @__PURE__ */ new Map();
20116
+ openingObservationSessions = /* @__PURE__ */ new Map();
20117
+ openedObservationSessions = /* @__PURE__ */ new Set();
20118
+ observationSessionStorage = new async_hooks.AsyncLocalStorage();
20020
20119
  operationEventStorage = new async_hooks.AsyncLocalStorage();
20021
20120
  pendingOperationEventCaptures = [];
20022
20121
  ownsEngine = false;
@@ -20068,18 +20167,26 @@ var OpensteerSessionRuntime = class {
20068
20167
  }
20069
20168
  async setObservabilityConfig(input) {
20070
20169
  this.observationConfig = normalizeObservabilityConfig(input);
20071
- const observationSessionId = this.resolveObservationSessionId();
20072
- if (observationSessionId === void 0) {
20073
- return this.observationConfig;
20074
- }
20075
- const sink = this.injectedObservationSink ?? (await this.ensureRoot()).observations;
20076
- this.observations = await sink.openSession({
20077
- sessionId: observationSessionId,
20078
- openedAt: Date.now(),
20079
- config: this.observationConfig
20080
- });
20170
+ await this.ensureConfiguredObservationSession();
20081
20171
  return this.observationConfig;
20082
20172
  }
20173
+ async withObservationSessionId(sessionId, task) {
20174
+ return await this.observationSessionStorage.run(
20175
+ {
20176
+ mode: "session",
20177
+ sessionId: normalizeNonEmptyString("sessionId", sessionId)
20178
+ },
20179
+ task
20180
+ );
20181
+ }
20182
+ async withoutObservationSession(task) {
20183
+ return await this.observationSessionStorage.run(
20184
+ {
20185
+ mode: "disabled"
20186
+ },
20187
+ task
20188
+ );
20189
+ }
20083
20190
  async open(input = {}, options = {}) {
20084
20191
  assertValidSemanticOperationInput("session.open", input);
20085
20192
  if (input.workspace !== void 0 && normalizeNamespace2(input.workspace) !== this.workspace) {
@@ -20096,7 +20203,7 @@ var OpensteerSessionRuntime = class {
20096
20203
  options
20097
20204
  );
20098
20205
  }
20099
- return this.readSessionState();
20206
+ return this.readNavigationSummary();
20100
20207
  }
20101
20208
  const startedAt = Date.now();
20102
20209
  const root = await this.ensureRoot();
@@ -20117,7 +20224,8 @@ var OpensteerSessionRuntime = class {
20117
20224
  openedSessionRef = sessionRef;
20118
20225
  const createdPage = await timeout.runStep(
20119
20226
  () => engine.createPage({
20120
- sessionRef
20227
+ sessionRef,
20228
+ ...input.url === void 0 ? {} : { url: input.url }
20121
20229
  })
20122
20230
  );
20123
20231
  openedPageRef = createdPage.data.pageRef;
@@ -20127,18 +20235,19 @@ var OpensteerSessionRuntime = class {
20127
20235
  await timeout.runStep(() => this.ensureSemantics());
20128
20236
  let frameRef2 = createdPage.frameRef;
20129
20237
  if (input.url !== void 0) {
20130
- const navigation = await this.navigatePage(
20131
- {
20238
+ await timeout.runStep(
20239
+ () => settleWithPolicy(this.policy.settle, {
20132
20240
  operation: "session.open",
20241
+ trigger: "navigation",
20242
+ engine: this.requireEngine(),
20133
20243
  pageRef: createdPage.data.pageRef,
20134
- url: input.url
20135
- },
20136
- timeout
20244
+ signal: timeout.signal,
20245
+ remainingMs: timeout.remainingMs()
20246
+ })
20137
20247
  );
20138
- frameRef2 = navigation.data.mainFrame.frameRef;
20139
20248
  }
20140
20249
  return {
20141
- state: await timeout.runStep(() => this.readSessionState()),
20250
+ state: await timeout.runStep(() => this.readNavigationSummary()),
20142
20251
  frameRef: frameRef2
20143
20252
  };
20144
20253
  },
@@ -20231,7 +20340,11 @@ var OpensteerSessionRuntime = class {
20231
20340
  "page.new cannot use openerPageRef before a session exists"
20232
20341
  );
20233
20342
  }
20234
- return this.open(input.url === void 0 ? {} : { url: input.url }, options);
20343
+ const summary = await this.open(input.url === void 0 ? {} : { url: input.url }, options);
20344
+ return {
20345
+ pageRef: await this.ensurePageRef(),
20346
+ ...summary
20347
+ };
20235
20348
  }
20236
20349
  const startedAt = Date.now();
20237
20350
  try {
@@ -20246,7 +20359,7 @@ var OpensteerSessionRuntime = class {
20246
20359
  })
20247
20360
  );
20248
20361
  this.pageRef = created.data.pageRef;
20249
- return this.readSessionState();
20362
+ return this.readCreatedPageOutput(created.data.pageRef);
20250
20363
  },
20251
20364
  options
20252
20365
  );
@@ -20288,7 +20401,7 @@ var OpensteerSessionRuntime = class {
20288
20401
  () => this.requireEngine().activatePage({ pageRef: input.pageRef })
20289
20402
  );
20290
20403
  this.pageRef = input.pageRef;
20291
- return this.readSessionState();
20404
+ return this.readNavigationSummary(input.pageRef);
20292
20405
  },
20293
20406
  options
20294
20407
  );
@@ -20414,7 +20527,7 @@ var OpensteerSessionRuntime = class {
20414
20527
  timeout.throwIfAborted();
20415
20528
  return {
20416
20529
  navigation: navigation2,
20417
- state: await timeout.runStep(() => this.readSessionState())
20530
+ state: await timeout.runStep(() => this.readNavigationSummary(pageRef))
20418
20531
  };
20419
20532
  },
20420
20533
  (diagnostics) => {
@@ -21648,7 +21761,7 @@ var OpensteerSessionRuntime = class {
21648
21761
  let mutationCaptureDiagnostics;
21649
21762
  let boundaryDiagnostics;
21650
21763
  try {
21651
- const { artifacts, output } = await this.runMutationCapturedOperation(
21764
+ const { artifacts, output, result } = await this.runMutationCapturedOperation(
21652
21765
  "computer.execute",
21653
21766
  {
21654
21767
  ...input.captureNetwork === void 0 ? {} : { captureNetwork: input.captureNetwork },
@@ -21666,9 +21779,14 @@ var OpensteerSessionRuntime = class {
21666
21779
  await this.invalidateLiveSnapshotCounters([pageRef, output2.pageRef], timeout);
21667
21780
  this.pageRef = output2.pageRef;
21668
21781
  const artifacts2 = await this.persistComputerArtifacts(output2, timeout);
21782
+ const result2 = {
21783
+ ...await timeout.runStep(() => this.readNavigationSummary(output2.pageRef)),
21784
+ screenshot: artifacts2.screenshot
21785
+ };
21669
21786
  return {
21670
21787
  artifacts: { manifests: artifacts2.manifests },
21671
- output: artifacts2.output
21788
+ output: output2,
21789
+ result: result2
21672
21790
  };
21673
21791
  } catch (error) {
21674
21792
  boundaryDiagnostics ??= takeActionBoundaryDiagnostics(timeout.signal);
@@ -21705,7 +21823,7 @@ var OpensteerSessionRuntime = class {
21705
21823
  documentEpoch: output.screenshot.documentEpoch
21706
21824
  })
21707
21825
  });
21708
- return output;
21826
+ return result;
21709
21827
  } catch (error) {
21710
21828
  await this.appendTrace({
21711
21829
  operation: "computer.execute",
@@ -21853,8 +21971,9 @@ var OpensteerSessionRuntime = class {
21853
21971
  mutationCaptureDiagnostics = diagnostics;
21854
21972
  }
21855
21973
  );
21856
- const output = toOpensteerActionResult(executed.result);
21974
+ const output = toOpensteerActionResult(executed.result.resolved);
21857
21975
  const actionEvents = "events" in executed.result ? executed.result.events : void 0;
21976
+ const resolvedTarget = toOpensteerResolvedTarget2(executed.result.resolved);
21858
21977
  await this.appendTrace({
21859
21978
  operation,
21860
21979
  startedAt,
@@ -21862,8 +21981,13 @@ var OpensteerSessionRuntime = class {
21862
21981
  outcome: "ok",
21863
21982
  ...actionEvents === void 0 ? {} : { events: actionEvents },
21864
21983
  data: {
21865
- target: output.target,
21866
- ...output.point === void 0 ? {} : { point: output.point },
21984
+ target: resolvedTarget,
21985
+ ..."point" in executed.result && executed.result.point !== void 0 ? {
21986
+ point: {
21987
+ x: executed.result.point.x,
21988
+ y: executed.result.point.y
21989
+ }
21990
+ } : {},
21867
21991
  ...boundaryDiagnostics === void 0 ? {} : { settle: boundaryDiagnostics },
21868
21992
  ...buildMutationCaptureTraceData(mutationCaptureDiagnostics)
21869
21993
  },
@@ -23328,20 +23452,20 @@ var OpensteerSessionRuntime = class {
23328
23452
  throw error;
23329
23453
  }
23330
23454
  }
23331
- async readSessionState() {
23332
- const pageRef = await this.ensurePageRef();
23455
+ async readNavigationSummary(targetPageRef) {
23456
+ const pageRef = targetPageRef ?? await this.ensurePageRef();
23333
23457
  const pageInfo = await this.requireEngine().getPageInfo({ pageRef });
23334
- const sessionRef = this.sessionRef;
23335
- if (!sessionRef) {
23336
- throw new Error("Opensteer session is not initialized");
23337
- }
23338
23458
  return {
23339
- sessionRef,
23340
- pageRef,
23341
23459
  url: pageInfo.url,
23342
23460
  title: pageInfo.title
23343
23461
  };
23344
23462
  }
23463
+ async readCreatedPageOutput(pageRef) {
23464
+ return {
23465
+ pageRef,
23466
+ ...await this.readNavigationSummary(pageRef)
23467
+ };
23468
+ }
23345
23469
  async captureSnapshotArtifacts(pageRef, options, timeout) {
23346
23470
  const root = this.requireRoot();
23347
23471
  const mainFrame = await timeout.runStep(() => getMainFrame(this.requireEngine(), pageRef));
@@ -23413,12 +23537,12 @@ var OpensteerSessionRuntime = class {
23413
23537
  const screenshotPayload = manifestToExternalBinaryLocation(root.rootPath, screenshotManifest);
23414
23538
  return {
23415
23539
  manifests,
23416
- output: {
23417
- ...output,
23418
- screenshot: {
23419
- ...output.screenshot,
23420
- payload: screenshotPayload
23421
- }
23540
+ screenshot: {
23541
+ payload: screenshotPayload,
23542
+ format: output.screenshot.format,
23543
+ size: output.screenshot.size,
23544
+ coordinateSpace: output.screenshot.coordinateSpace,
23545
+ ...output.screenshot.clip === void 0 ? {} : { clip: output.screenshot.clip }
23422
23546
  }
23423
23547
  };
23424
23548
  }
@@ -23506,7 +23630,7 @@ var OpensteerSessionRuntime = class {
23506
23630
  }
23507
23631
  async resetRuntimeState(options) {
23508
23632
  const engine = this.engine;
23509
- const observations = this.observations;
23633
+ const observationSessions = [...this.openedObservationSessions];
23510
23634
  this.networkHistory.clear();
23511
23635
  this.sessionRef = void 0;
23512
23636
  this.pageRef = void 0;
@@ -23515,9 +23639,15 @@ var OpensteerSessionRuntime = class {
23515
23639
  this.computer = void 0;
23516
23640
  this.extractionDescriptors = void 0;
23517
23641
  this.engine = void 0;
23518
- this.observations = void 0;
23642
+ this.observationSessions.clear();
23643
+ this.openingObservationSessions.clear();
23644
+ this.openedObservationSessions.clear();
23519
23645
  this.pendingOperationEventCaptures.length = 0;
23520
- await observations?.close("runtime_reset").catch(() => void 0);
23646
+ await Promise.allSettled(
23647
+ observationSessions.map(
23648
+ (observationSession) => observationSession.close("runtime_reset").catch(() => void 0)
23649
+ )
23650
+ );
23521
23651
  if (options.disposeEngine && this.ownsEngine && engine?.dispose) {
23522
23652
  await engine.dispose();
23523
23653
  }
@@ -23527,23 +23657,64 @@ var OpensteerSessionRuntime = class {
23527
23657
  if (this.observationConfig.profile === "off") {
23528
23658
  return void 0;
23529
23659
  }
23530
- if (this.observations !== void 0) {
23531
- return this.observations;
23660
+ const observationSessionId = this.resolveObservationSessionId();
23661
+ if (observationSessionId === void 0) {
23662
+ return void 0;
23663
+ }
23664
+ const existingObservationSession = this.observationSessions.get(observationSessionId);
23665
+ if (existingObservationSession !== void 0) {
23666
+ return existingObservationSession;
23667
+ }
23668
+ const openingObservationSession = this.openingObservationSessions.get(observationSessionId);
23669
+ if (openingObservationSession !== void 0) {
23670
+ return await openingObservationSession;
23671
+ }
23672
+ const openObservationSessionTask = this.openObservationSession(observationSessionId).finally(
23673
+ () => {
23674
+ this.openingObservationSessions.delete(observationSessionId);
23675
+ }
23676
+ );
23677
+ this.openingObservationSessions.set(observationSessionId, openObservationSessionTask);
23678
+ return await openObservationSessionTask;
23679
+ }
23680
+ async ensureConfiguredObservationSession() {
23681
+ if (this.observationConfig.profile === "off") {
23682
+ return void 0;
23532
23683
  }
23533
23684
  const observationSessionId = this.resolveObservationSessionId();
23534
23685
  if (observationSessionId === void 0) {
23535
23686
  return void 0;
23536
23687
  }
23688
+ const hadObservationSession = this.observationSessions.has(observationSessionId) || this.openingObservationSessions.has(observationSessionId);
23689
+ const observationSession = await this.ensureObservationSession();
23690
+ if (observationSession !== void 0 && hadObservationSession) {
23691
+ await observationSession.configure?.({
23692
+ config: this.observationConfig,
23693
+ updatedAt: Date.now()
23694
+ });
23695
+ }
23696
+ return observationSession;
23697
+ }
23698
+ resolveObservationSessionId() {
23699
+ const scopedSession = this.observationSessionStorage.getStore();
23700
+ if (scopedSession?.mode === "session") {
23701
+ return scopedSession.sessionId;
23702
+ }
23703
+ if (scopedSession?.mode === "disabled") {
23704
+ return void 0;
23705
+ }
23706
+ return this.observationSessionId ?? this.sessionRef;
23707
+ }
23708
+ async openObservationSession(sessionId) {
23537
23709
  const sink = this.injectedObservationSink ?? (await this.ensureRoot()).observations;
23538
- this.observations = await sink.openSession({
23539
- sessionId: observationSessionId,
23710
+ const observationSession = await sink.openSession({
23711
+ sessionId,
23540
23712
  openedAt: Date.now(),
23541
23713
  config: this.observationConfig
23542
23714
  });
23543
- return this.observations;
23544
- }
23545
- resolveObservationSessionId() {
23546
- return this.observationSessionId ?? this.sessionRef;
23715
+ this.observationSessions.set(sessionId, observationSession);
23716
+ this.openedObservationSessions.add(observationSession);
23717
+ return observationSession;
23547
23718
  }
23548
23719
  runWithOperationTimeout(operation, callback, options = {}) {
23549
23720
  const timeoutPolicy = options.timeoutMs === void 0 ? this.policy.timeout : {
@@ -24941,15 +25112,10 @@ function normalizeNamespace2(value) {
24941
25112
  const normalized = String(value ?? "default").trim();
24942
25113
  return normalized.length === 0 ? "default" : normalized;
24943
25114
  }
24944
- function toOpensteerActionResult(result) {
25115
+ function toOpensteerActionResult(target) {
24945
25116
  return {
24946
- target: toOpensteerResolvedTarget2(result.resolved),
24947
- ...result.point === void 0 ? {} : {
24948
- point: {
24949
- x: result.point.x,
24950
- y: result.point.y
24951
- }
24952
- }
25117
+ tagName: toOpensteerTagName2(target.node.nodeName),
25118
+ ...target.persist === void 0 ? {} : { persist: target.persist }
24953
25119
  };
24954
25120
  }
24955
25121
  function toOpensteerResolvedTarget2(target) {
@@ -24959,12 +25125,16 @@ function toOpensteerResolvedTarget2(target) {
24959
25125
  documentRef: target.documentRef,
24960
25126
  documentEpoch: target.documentEpoch,
24961
25127
  nodeRef: target.nodeRef,
24962
- tagName: target.node.nodeName.toUpperCase(),
25128
+ tagName: toOpensteerTagName2(target.node.nodeName),
24963
25129
  pathHint: buildPathSelectorHint(target.replayPath ?? target.anchor),
24964
25130
  ...target.persist === void 0 ? {} : { persist: target.persist },
24965
25131
  ...target.selectorUsed === void 0 ? {} : { selectorUsed: target.selectorUsed }
24966
25132
  };
24967
25133
  }
25134
+ function toOpensteerTagName2(nodeName) {
25135
+ const tagName = String(nodeName).trim().toLowerCase();
25136
+ return tagName.length === 0 ? "element" : tagName;
25137
+ }
24968
25138
  function normalizeOpensteerError(error) {
24969
25139
  return normalizeThrownOpensteerError2(error, "Unknown Opensteer runtime failure");
24970
25140
  }
@@ -26220,7 +26390,29 @@ function isLoopbackBaseUrl(baseUrl) {
26220
26390
  }
26221
26391
  return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]";
26222
26392
  }
26223
- var OpensteerRuntime = class extends OpensteerSessionRuntime {
26393
+ var LocalActivePageHintRuntime = class extends OpensteerSessionRuntime {
26394
+ async completeWithLocalActivePageHint(operation) {
26395
+ const output = await operation();
26396
+ await persistLocalActivePageHint(this, this.rootPath);
26397
+ return output;
26398
+ }
26399
+ async open(input = {}, options = {}) {
26400
+ return this.completeWithLocalActivePageHint(() => super.open(input, options));
26401
+ }
26402
+ async newPage(input = {}, options = {}) {
26403
+ return this.completeWithLocalActivePageHint(() => super.newPage(input, options));
26404
+ }
26405
+ async activatePage(input, options = {}) {
26406
+ return this.completeWithLocalActivePageHint(() => super.activatePage(input, options));
26407
+ }
26408
+ async closePage(input = {}, options = {}) {
26409
+ return this.completeWithLocalActivePageHint(() => super.closePage(input, options));
26410
+ }
26411
+ async goto(input, options = {}) {
26412
+ return this.completeWithLocalActivePageHint(() => super.goto(input, options));
26413
+ }
26414
+ };
26415
+ var OpensteerRuntime = class extends LocalActivePageHintRuntime {
26224
26416
  constructor(options = {}) {
26225
26417
  const publicWorkspace = normalizeWorkspace2(options.workspace);
26226
26418
  const rootPath = options.rootPath ?? (publicWorkspace === void 0 ? path10__default.default.resolve(options.rootDir ?? process.cwd(), ".opensteer", "temporary", crypto.randomUUID()) : resolveFilesystemWorkspacePath({
@@ -26258,6 +26450,41 @@ var OpensteerRuntime = class extends OpensteerSessionRuntime {
26258
26450
  );
26259
26451
  }
26260
26452
  };
26453
+ async function persistLocalActivePageHint(runtime, rootPath) {
26454
+ try {
26455
+ await syncPersistedLocalActivePageHint(runtime, rootPath);
26456
+ } catch {
26457
+ }
26458
+ }
26459
+ async function syncPersistedLocalActivePageHint(runtime, rootPath) {
26460
+ const record = await readPersistedLocalBrowserSessionRecord(rootPath);
26461
+ if (!record) {
26462
+ return;
26463
+ }
26464
+ const sessionInfo = await runtime.info();
26465
+ const activePageRef = sessionInfo.activePageRef;
26466
+ let activePageUrl;
26467
+ let activePageTitle;
26468
+ if (activePageRef !== void 0) {
26469
+ const pages = await runtime.listPages();
26470
+ const activePage = pages.pages.find((page) => page.pageRef === activePageRef);
26471
+ activePageUrl = activePage?.url;
26472
+ activePageTitle = activePage?.title;
26473
+ }
26474
+ const {
26475
+ activePageRef: _previousActivePageRef,
26476
+ activePageUrl: _previousActivePageUrl,
26477
+ activePageTitle: _previousActivePageTitle,
26478
+ ...restRecord
26479
+ } = record;
26480
+ await writePersistedSessionRecord(rootPath, {
26481
+ ...restRecord,
26482
+ updatedAt: Date.now(),
26483
+ ...activePageRef === void 0 ? {} : { activePageRef },
26484
+ ...activePageUrl === void 0 ? {} : { activePageUrl },
26485
+ ...activePageTitle === void 0 ? {} : { activePageTitle }
26486
+ });
26487
+ }
26261
26488
  function buildSharedRuntimeOptions(input) {
26262
26489
  const ownership = resolveOwnership(input.browser);
26263
26490
  const engineFactory = input.engineFactory ?? ((factoryOptions) => new OpensteerBrowserManager({
@@ -26758,6 +26985,7 @@ function delay3(ms) {
26758
26985
  return new Promise((resolve4) => setTimeout(resolve4, ms));
26759
26986
  }
26760
26987
 
26988
+ exports.DEFAULT_OPENSTEER_CLOUD_BASE_URL = DEFAULT_OPENSTEER_CLOUD_BASE_URL;
26761
26989
  exports.DEFAULT_OPENSTEER_ENGINE = DEFAULT_OPENSTEER_ENGINE;
26762
26990
  exports.DEFERRED_MATCH_ATTR_KEYS = DEFERRED_MATCH_ATTR_KEYS;
26763
26991
  exports.ElementPathError = ElementPathError;