opensteer 0.9.7 → 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 (34) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-R33BXCMQ.js → chunk-BPGXP3RF.js} +245 -21
  3. package/dist/chunk-BPGXP3RF.js.map +1 -0
  4. package/dist/{chunk-PJXN7HED.js → chunk-EXXRLPLI.js} +46 -35
  5. package/dist/chunk-EXXRLPLI.js.map +1 -0
  6. package/dist/{chunk-U4BUCIZ4.js → chunk-GKYBP3KD.js} +4 -4
  7. package/dist/chunk-GKYBP3KD.js.map +1 -0
  8. package/dist/{chunk-3OHKIPBD.js → chunk-LFWP5RXF.js} +189 -53
  9. package/dist/chunk-LFWP5RXF.js.map +1 -0
  10. package/dist/{chunk-52UNH5UW.js → chunk-SOJEWKSW.js} +5 -5
  11. package/dist/{chunk-52UNH5UW.js.map → chunk-SOJEWKSW.js.map} +1 -1
  12. package/dist/cli/bin.cjs +570 -119
  13. package/dist/cli/bin.cjs.map +1 -1
  14. package/dist/cli/bin.js +106 -28
  15. package/dist/cli/bin.js.map +1 -1
  16. package/dist/index.cjs +211 -72
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +10 -1
  19. package/dist/index.d.ts +10 -1
  20. package/dist/index.js +4 -4
  21. package/dist/local-view/serve-entry.cjs +288 -50
  22. package/dist/local-view/serve-entry.cjs.map +1 -1
  23. package/dist/local-view/serve-entry.js +2 -2
  24. package/dist/opensteer-XLPY343Y.js +6 -0
  25. package/dist/{opensteer-CY2QUJEG.js.map → opensteer-XLPY343Y.js.map} +1 -1
  26. package/dist/{session-control-FIP6ZJLH.js → session-control-FVKKD45R.js} +4 -4
  27. package/dist/{session-control-FIP6ZJLH.js.map → session-control-FVKKD45R.js.map} +1 -1
  28. package/package.json +7 -7
  29. package/skills/recorder/SKILL.md +2 -2
  30. package/dist/chunk-3OHKIPBD.js.map +0 -1
  31. package/dist/chunk-PJXN7HED.js.map +0 -1
  32. package/dist/chunk-R33BXCMQ.js.map +0 -1
  33. package/dist/chunk-U4BUCIZ4.js.map +0 -1
  34. package/dist/opensteer-CY2QUJEG.js +0 -6
package/dist/cli/bin.cjs CHANGED
@@ -2940,6 +2940,9 @@ var init_metadata2 = __esm({
2940
2940
  {
2941
2941
  pageRef: pageRefSchema,
2942
2942
  sessionRef: sessionRefSchema,
2943
+ targetId: stringSchema({
2944
+ description: "Underlying browser target identifier when available."
2945
+ }),
2943
2946
  openerPageRef: pageRefSchema,
2944
2947
  url: stringSchema({
2945
2948
  description: "Current main-frame URL."
@@ -9143,6 +9146,9 @@ var init_observations = __esm({
9143
9146
  this.store = store;
9144
9147
  this.sessionId = sessionId;
9145
9148
  }
9149
+ configure(input) {
9150
+ return this.store.configureSession(this.sessionId, input);
9151
+ }
9146
9152
  append(input) {
9147
9153
  return this.store.appendEvent(this.sessionId, input);
9148
9154
  }
@@ -9173,40 +9179,14 @@ var init_observations = __esm({
9173
9179
  const sessionId = normalizeNonEmptyString("sessionId", input.sessionId);
9174
9180
  const openedAt = normalizeTimestamp("openedAt", input.openedAt ?? Date.now());
9175
9181
  const config = normalizeObservabilityConfig(input.config);
9176
- const redactor = createObservationRedactor(config);
9177
- this.redactors.set(sessionId, redactor);
9178
- const redactedLabels = redactor.redactLabels(config.labels);
9179
- const redactedTraceContext = redactor.redactTraceContext(config.traceContext);
9180
- await withFilesystemLock(this.sessionLockPath(sessionId), async () => {
9181
- const existing = await this.reconcileSessionManifest(sessionId);
9182
- if (existing === void 0) {
9183
- await ensureDirectory(this.sessionEventsDirectory(sessionId));
9184
- await ensureDirectory(this.sessionArtifactsDirectory(sessionId));
9185
- const session = {
9186
- sessionId,
9187
- profile: config.profile,
9188
- ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
9189
- ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
9190
- openedAt,
9191
- updatedAt: openedAt,
9192
- currentSequence: 0,
9193
- eventCount: 0,
9194
- artifactCount: 0
9195
- };
9196
- await writeJsonFileExclusive(this.sessionManifestPath(sessionId), session);
9197
- return;
9198
- }
9199
- const patched = {
9200
- ...existing,
9201
- profile: config.profile,
9202
- ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
9203
- ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
9204
- updatedAt: Math.max(existing.updatedAt, openedAt)
9205
- };
9206
- await writeJsonFileAtomic(this.sessionManifestPath(sessionId), patched);
9207
- });
9182
+ await this.applySessionConfiguration(sessionId, config, openedAt);
9208
9183
  return new FilesystemSessionSink(this, sessionId);
9209
9184
  }
9185
+ async configureSession(sessionId, input) {
9186
+ const updatedAt = normalizeTimestamp("updatedAt", input.updatedAt ?? Date.now());
9187
+ const config = normalizeObservabilityConfig(input.config);
9188
+ await this.applySessionConfiguration(sessionId, config, updatedAt);
9189
+ }
9210
9190
  async getSession(sessionId) {
9211
9191
  const manifestPath = this.sessionManifestPath(sessionId);
9212
9192
  if (!await pathExists(manifestPath)) {
@@ -9427,6 +9407,40 @@ var init_observations = __esm({
9427
9407
  sessionLockPath(sessionId) {
9428
9408
  return path10__default.default.join(this.sessionDirectory(sessionId), ".lock");
9429
9409
  }
9410
+ async applySessionConfiguration(sessionId, config, timestamp) {
9411
+ const redactor = createObservationRedactor(config);
9412
+ this.redactors.set(sessionId, redactor);
9413
+ const redactedLabels = redactor.redactLabels(config.labels);
9414
+ const redactedTraceContext = redactor.redactTraceContext(config.traceContext);
9415
+ await withFilesystemLock(this.sessionLockPath(sessionId), async () => {
9416
+ const existing = await this.reconcileSessionManifest(sessionId);
9417
+ if (existing === void 0) {
9418
+ await ensureDirectory(this.sessionEventsDirectory(sessionId));
9419
+ await ensureDirectory(this.sessionArtifactsDirectory(sessionId));
9420
+ const session = {
9421
+ sessionId,
9422
+ profile: config.profile,
9423
+ ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
9424
+ ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
9425
+ openedAt: timestamp,
9426
+ updatedAt: timestamp,
9427
+ currentSequence: 0,
9428
+ eventCount: 0,
9429
+ artifactCount: 0
9430
+ };
9431
+ await writeJsonFileExclusive(this.sessionManifestPath(sessionId), session);
9432
+ return;
9433
+ }
9434
+ const patched = {
9435
+ ...existing,
9436
+ profile: config.profile,
9437
+ ...redactedLabels === void 0 ? {} : { labels: redactedLabels },
9438
+ ...redactedTraceContext === void 0 ? {} : { traceContext: redactedTraceContext },
9439
+ updatedAt: Math.max(existing.updatedAt, timestamp)
9440
+ };
9441
+ await writeJsonFileAtomic(this.sessionManifestPath(sessionId), patched);
9442
+ });
9443
+ }
9430
9444
  async reconcileSessionManifest(sessionId) {
9431
9445
  const session = await this.getSession(sessionId);
9432
9446
  if (session === void 0) {
@@ -9863,10 +9877,10 @@ async function isAttachedLocalBrowserSessionReachable(record) {
9863
9877
  }
9864
9878
  }
9865
9879
  function isPersistedCloudSessionRecord(value) {
9866
- return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "cloud" && typeof value.sessionId === "string" && value.sessionId.length > 0 && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
9880
+ 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);
9867
9881
  }
9868
9882
  function isPersistedLocalBrowserSessionRecord(value) {
9869
- return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "local" && (value.engine === "playwright" || value.engine === "abp") && (value.ownership === void 0 || value.ownership === "owned" || value.ownership === "attached") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && typeof value.userDataDir === "string" && value.userDataDir.length > 0;
9883
+ 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;
9870
9884
  }
9871
9885
  var OPENSTEER_LIVE_SESSION_LAYOUT, OPENSTEER_LIVE_SESSION_VERSION;
9872
9886
  var init_live_session = __esm({
@@ -12145,11 +12159,11 @@ function delay2(ms) {
12145
12159
  function wrapCloudFetchError(error, input) {
12146
12160
  if (!(error instanceof Error)) {
12147
12161
  return new Error(
12148
- `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check OPENSTEER_BASE_URL and network reachability from this environment.`
12162
+ `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check the configured Opensteer cloud base URL and network reachability from this environment.`
12149
12163
  );
12150
12164
  }
12151
12165
  const wrapped = new Error(
12152
- `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check OPENSTEER_BASE_URL and network reachability from this environment.`,
12166
+ `Failed to reach Opensteer cloud endpoint ${input.method} ${input.url}. Check the configured Opensteer cloud base URL and network reachability from this environment.`,
12153
12167
  {
12154
12168
  cause: error
12155
12169
  }
@@ -12424,34 +12438,40 @@ function resolveCloudConfig(input = {}) {
12424
12438
  return void 0;
12425
12439
  }
12426
12440
  const cloudProvider = input.provider?.mode === "cloud" ? input.provider : void 0;
12427
- const apiKey = cloudProvider?.apiKey ?? input.environment?.OPENSTEER_API_KEY;
12428
- if (!apiKey || apiKey.trim().length === 0) {
12441
+ const apiKey = normalizeOptionalCloudConfigValue(cloudProvider?.apiKey) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_API_KEY);
12442
+ if (apiKey === void 0) {
12429
12443
  throw new Error("provider=cloud requires OPENSTEER_API_KEY or provider.apiKey.");
12430
12444
  }
12431
- const baseUrl = cloudProvider?.baseUrl ?? input.environment?.OPENSTEER_BASE_URL;
12432
- if (!baseUrl || baseUrl.trim().length === 0) {
12433
- throw new Error("provider=cloud requires OPENSTEER_BASE_URL or provider.baseUrl.");
12434
- }
12435
- const appBaseUrl = cloudProvider?.appBaseUrl ?? input.environment?.OPENSTEER_CLOUD_APP_BASE_URL;
12445
+ const baseUrl = normalizeOptionalCloudConfigValue(cloudProvider?.baseUrl) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_BASE_URL) ?? DEFAULT_OPENSTEER_CLOUD_BASE_URL;
12446
+ const appBaseUrl = normalizeOptionalCloudConfigValue(cloudProvider?.appBaseUrl) ?? normalizeOptionalCloudConfigValue(input.environment?.OPENSTEER_CLOUD_APP_BASE_URL);
12436
12447
  return {
12437
- apiKey: apiKey.trim(),
12438
- baseUrl: baseUrl.trim().replace(/\/+$/, ""),
12439
- ...appBaseUrl === void 0 || appBaseUrl.trim().length === 0 ? {} : { appBaseUrl: appBaseUrl.trim().replace(/\/+$/, "") },
12448
+ apiKey,
12449
+ baseUrl,
12450
+ ...appBaseUrl === void 0 ? {} : { appBaseUrl },
12440
12451
  ...cloudProvider?.browserProfile === void 0 ? {} : { browserProfile: cloudProvider.browserProfile }
12441
12452
  };
12442
12453
  }
12443
12454
  function requireCloudAppBaseUrl(cloudConfig) {
12444
- const appBaseUrl = cloudConfig.appBaseUrl;
12445
- if (!appBaseUrl || appBaseUrl.trim().length === 0) {
12455
+ const appBaseUrl = normalizeOptionalCloudConfigValue(cloudConfig.appBaseUrl);
12456
+ if (appBaseUrl === void 0) {
12446
12457
  throw new Error(
12447
12458
  'record with provider=cloud requires OPENSTEER_CLOUD_APP_BASE_URL or "--cloud-app-base-url".'
12448
12459
  );
12449
12460
  }
12450
- return appBaseUrl.trim().replace(/\/+$/, "");
12461
+ return appBaseUrl;
12462
+ }
12463
+ function normalizeOptionalCloudConfigValue(value) {
12464
+ if (typeof value !== "string") {
12465
+ return void 0;
12466
+ }
12467
+ const normalized = value.trim().replace(/\/+$/, "");
12468
+ return normalized.length === 0 ? void 0 : normalized;
12451
12469
  }
12470
+ var DEFAULT_OPENSTEER_CLOUD_BASE_URL;
12452
12471
  var init_config2 = __esm({
12453
12472
  "src/cloud/config.ts"() {
12454
12473
  init_config();
12474
+ DEFAULT_OPENSTEER_CLOUD_BASE_URL = "https://api.opensteer.com";
12455
12475
  }
12456
12476
  });
12457
12477
 
@@ -12461,7 +12481,7 @@ var init_package = __esm({
12461
12481
  "../runtime-core/package.json"() {
12462
12482
  package_default2 = {
12463
12483
  name: "@opensteer/runtime-core",
12464
- version: "0.2.6",
12484
+ version: "0.2.7",
12465
12485
  description: "Shared semantic runtime for Opensteer local and cloud execution.",
12466
12486
  license: "MIT",
12467
12487
  type: "module",
@@ -13332,7 +13352,9 @@ function resolveExtractedValueInContext(normalizedValue, options) {
13332
13352
  function stripPositionClauses(nodes) {
13333
13353
  return (nodes || []).map((node) => ({
13334
13354
  ...node,
13335
- match: (node.match || []).filter((clause) => clause.kind !== "position" && clause.kind !== "text")
13355
+ match: (node.match || []).filter(
13356
+ (clause) => clause.kind !== "position" && clause.kind !== "text"
13357
+ )
13336
13358
  }));
13337
13359
  }
13338
13360
  function dedupeSelectors(selectors) {
@@ -15641,7 +15663,9 @@ var init_runtime = __esm({
15641
15663
  `Unable to resolve structural anchor "${buildPathSelectorHint(anchor)}" in the current session`
15642
15664
  );
15643
15665
  }
15644
- const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, { enableTextMatch: true });
15666
+ const replayPath = await this.tryBuildPathFromNode(context.snapshot, target.node, {
15667
+ enableTextMatch: true
15668
+ });
15645
15669
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
15646
15670
  ...persist === void 0 ? {} : { persist },
15647
15671
  ...replayPath === void 0 ? {} : { replayPath }
@@ -23269,7 +23293,10 @@ var init_runtime3 = __esm({
23269
23293
  sessionRef;
23270
23294
  pageRef;
23271
23295
  runId;
23272
- observations;
23296
+ observationSessions = /* @__PURE__ */ new Map();
23297
+ openingObservationSessions = /* @__PURE__ */ new Map();
23298
+ openedObservationSessions = /* @__PURE__ */ new Set();
23299
+ observationSessionStorage = new async_hooks.AsyncLocalStorage();
23273
23300
  operationEventStorage = new async_hooks.AsyncLocalStorage();
23274
23301
  pendingOperationEventCaptures = [];
23275
23302
  ownsEngine = false;
@@ -23321,18 +23348,26 @@ var init_runtime3 = __esm({
23321
23348
  }
23322
23349
  async setObservabilityConfig(input) {
23323
23350
  this.observationConfig = normalizeObservabilityConfig(input);
23324
- const observationSessionId = this.resolveObservationSessionId();
23325
- if (observationSessionId === void 0) {
23326
- return this.observationConfig;
23327
- }
23328
- const sink = this.injectedObservationSink ?? (await this.ensureRoot()).observations;
23329
- this.observations = await sink.openSession({
23330
- sessionId: observationSessionId,
23331
- openedAt: Date.now(),
23332
- config: this.observationConfig
23333
- });
23351
+ await this.ensureConfiguredObservationSession();
23334
23352
  return this.observationConfig;
23335
23353
  }
23354
+ async withObservationSessionId(sessionId, task) {
23355
+ return await this.observationSessionStorage.run(
23356
+ {
23357
+ mode: "session",
23358
+ sessionId: normalizeNonEmptyString("sessionId", sessionId)
23359
+ },
23360
+ task
23361
+ );
23362
+ }
23363
+ async withoutObservationSession(task) {
23364
+ return await this.observationSessionStorage.run(
23365
+ {
23366
+ mode: "disabled"
23367
+ },
23368
+ task
23369
+ );
23370
+ }
23336
23371
  async open(input = {}, options = {}) {
23337
23372
  assertValidSemanticOperationInput("session.open", input);
23338
23373
  if (input.workspace !== void 0 && normalizeNamespace2(input.workspace) !== this.workspace) {
@@ -26776,7 +26811,7 @@ var init_runtime3 = __esm({
26776
26811
  }
26777
26812
  async resetRuntimeState(options) {
26778
26813
  const engine = this.engine;
26779
- const observations = this.observations;
26814
+ const observationSessions = [...this.openedObservationSessions];
26780
26815
  this.networkHistory.clear();
26781
26816
  this.sessionRef = void 0;
26782
26817
  this.pageRef = void 0;
@@ -26785,9 +26820,15 @@ var init_runtime3 = __esm({
26785
26820
  this.computer = void 0;
26786
26821
  this.extractionDescriptors = void 0;
26787
26822
  this.engine = void 0;
26788
- this.observations = void 0;
26823
+ this.observationSessions.clear();
26824
+ this.openingObservationSessions.clear();
26825
+ this.openedObservationSessions.clear();
26789
26826
  this.pendingOperationEventCaptures.length = 0;
26790
- await observations?.close("runtime_reset").catch(() => void 0);
26827
+ await Promise.allSettled(
26828
+ observationSessions.map(
26829
+ (observationSession) => observationSession.close("runtime_reset").catch(() => void 0)
26830
+ )
26831
+ );
26791
26832
  if (options.disposeEngine && this.ownsEngine && engine?.dispose) {
26792
26833
  await engine.dispose();
26793
26834
  }
@@ -26797,23 +26838,64 @@ var init_runtime3 = __esm({
26797
26838
  if (this.observationConfig.profile === "off") {
26798
26839
  return void 0;
26799
26840
  }
26800
- if (this.observations !== void 0) {
26801
- return this.observations;
26841
+ const observationSessionId = this.resolveObservationSessionId();
26842
+ if (observationSessionId === void 0) {
26843
+ return void 0;
26844
+ }
26845
+ const existingObservationSession = this.observationSessions.get(observationSessionId);
26846
+ if (existingObservationSession !== void 0) {
26847
+ return existingObservationSession;
26848
+ }
26849
+ const openingObservationSession = this.openingObservationSessions.get(observationSessionId);
26850
+ if (openingObservationSession !== void 0) {
26851
+ return await openingObservationSession;
26852
+ }
26853
+ const openObservationSessionTask = this.openObservationSession(observationSessionId).finally(
26854
+ () => {
26855
+ this.openingObservationSessions.delete(observationSessionId);
26856
+ }
26857
+ );
26858
+ this.openingObservationSessions.set(observationSessionId, openObservationSessionTask);
26859
+ return await openObservationSessionTask;
26860
+ }
26861
+ async ensureConfiguredObservationSession() {
26862
+ if (this.observationConfig.profile === "off") {
26863
+ return void 0;
26802
26864
  }
26803
26865
  const observationSessionId = this.resolveObservationSessionId();
26804
26866
  if (observationSessionId === void 0) {
26805
26867
  return void 0;
26806
26868
  }
26869
+ const hadObservationSession = this.observationSessions.has(observationSessionId) || this.openingObservationSessions.has(observationSessionId);
26870
+ const observationSession = await this.ensureObservationSession();
26871
+ if (observationSession !== void 0 && hadObservationSession) {
26872
+ await observationSession.configure?.({
26873
+ config: this.observationConfig,
26874
+ updatedAt: Date.now()
26875
+ });
26876
+ }
26877
+ return observationSession;
26878
+ }
26879
+ resolveObservationSessionId() {
26880
+ const scopedSession = this.observationSessionStorage.getStore();
26881
+ if (scopedSession?.mode === "session") {
26882
+ return scopedSession.sessionId;
26883
+ }
26884
+ if (scopedSession?.mode === "disabled") {
26885
+ return void 0;
26886
+ }
26887
+ return this.observationSessionId ?? this.sessionRef;
26888
+ }
26889
+ async openObservationSession(sessionId) {
26807
26890
  const sink = this.injectedObservationSink ?? (await this.ensureRoot()).observations;
26808
- this.observations = await sink.openSession({
26809
- sessionId: observationSessionId,
26891
+ const observationSession = await sink.openSession({
26892
+ sessionId,
26810
26893
  openedAt: Date.now(),
26811
26894
  config: this.observationConfig
26812
26895
  });
26813
- return this.observations;
26814
- }
26815
- resolveObservationSessionId() {
26816
- return this.observationSessionId ?? this.sessionRef;
26896
+ this.observationSessions.set(sessionId, observationSession);
26897
+ this.openedObservationSessions.add(observationSession);
26898
+ return observationSession;
26817
26899
  }
26818
26900
  runWithOperationTimeout(operation, callback, options = {}) {
26819
26901
  const timeoutPolicy = options.timeoutMs === void 0 ? this.policy.timeout : {
@@ -28981,26 +29063,35 @@ function renderOpensteerBootstrap(replayTarget) {
28981
29063
  `const opensteer = new Opensteer({`,
28982
29064
  ` provider: {`,
28983
29065
  ` mode: "cloud",`,
28984
- ` baseUrl: requireEnv(${JSON.stringify(replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL")}),`,
28985
29066
  ` apiKey: requireEnv(${JSON.stringify(replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY")}),`,
29067
+ ...renderCloudBaseUrl(replayTarget),
28986
29068
  ...renderCloudBrowserProfile(replayTarget),
28987
29069
  ` },`,
28988
29070
  `});`
28989
29071
  ];
28990
29072
  }
28991
29073
  function renderRequireEnvHelper(replayTarget) {
28992
- const baseUrlEnvVar = replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL";
28993
29074
  const apiKeyEnvVar = replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY";
29075
+ const requiredEnvVars = [
29076
+ ...replayTarget.baseUrlEnvVar === void 0 ? [] : [replayTarget.baseUrlEnvVar],
29077
+ apiKeyEnvVar
29078
+ ];
28994
29079
  return [
28995
29080
  `function requireEnv(name: string): string {`,
28996
29081
  ` const value = process.env[name];`,
28997
29082
  ` if (typeof value === "string" && value.trim().length > 0) {`,
28998
29083
  ` return value;`,
28999
29084
  ` }`,
29000
- ` throw new Error(\`Missing environment variable \${name}. Set ${baseUrlEnvVar} and ${apiKeyEnvVar} before replaying this recording.\`);`,
29085
+ ` throw new Error(\`Missing environment variable \${name}. Set ${requiredEnvVars.join(" and ")} before replaying this recording.\`);`,
29001
29086
  `}`
29002
29087
  ].join("\n");
29003
29088
  }
29089
+ function renderCloudBaseUrl(replayTarget) {
29090
+ if (replayTarget.baseUrlEnvVar === void 0) {
29091
+ return [];
29092
+ }
29093
+ return [` baseUrl: requireEnv(${JSON.stringify(replayTarget.baseUrlEnvVar)}),`];
29094
+ }
29004
29095
  function renderCloudBrowserProfile(replayTarget) {
29005
29096
  if (replayTarget.browserProfileId === void 0) {
29006
29097
  return [];
@@ -30263,6 +30354,41 @@ var init_session_proxy = __esm({
30263
30354
  };
30264
30355
  }
30265
30356
  });
30357
+ async function persistLocalActivePageHint(runtime, rootPath) {
30358
+ try {
30359
+ await syncPersistedLocalActivePageHint(runtime, rootPath);
30360
+ } catch {
30361
+ }
30362
+ }
30363
+ async function syncPersistedLocalActivePageHint(runtime, rootPath) {
30364
+ const record = await readPersistedLocalBrowserSessionRecord(rootPath);
30365
+ if (!record) {
30366
+ return;
30367
+ }
30368
+ const sessionInfo = await runtime.info();
30369
+ const activePageRef = sessionInfo.activePageRef;
30370
+ let activePageUrl;
30371
+ let activePageTitle;
30372
+ if (activePageRef !== void 0) {
30373
+ const pages = await runtime.listPages();
30374
+ const activePage = pages.pages.find((page) => page.pageRef === activePageRef);
30375
+ activePageUrl = activePage?.url;
30376
+ activePageTitle = activePage?.title;
30377
+ }
30378
+ const {
30379
+ activePageRef: _previousActivePageRef,
30380
+ activePageUrl: _previousActivePageUrl,
30381
+ activePageTitle: _previousActivePageTitle,
30382
+ ...restRecord
30383
+ } = record;
30384
+ await writePersistedSessionRecord(rootPath, {
30385
+ ...restRecord,
30386
+ updatedAt: Date.now(),
30387
+ ...activePageRef === void 0 ? {} : { activePageRef },
30388
+ ...activePageUrl === void 0 ? {} : { activePageUrl },
30389
+ ...activePageTitle === void 0 ? {} : { activePageTitle }
30390
+ });
30391
+ }
30266
30392
  function buildSharedRuntimeOptions(input) {
30267
30393
  const ownership = resolveOwnership(input.browser);
30268
30394
  const engineFactory = input.engineFactory ?? ((factoryOptions) => new OpensteerBrowserManager({
@@ -30309,14 +30435,37 @@ function normalizeWorkspace2(workspace) {
30309
30435
  function resolveOwnership(browser) {
30310
30436
  return typeof browser === "object" && browser.mode === "attach" ? "attached" : "owned";
30311
30437
  }
30312
- var OpensteerRuntime;
30438
+ var LocalActivePageHintRuntime, OpensteerRuntime;
30313
30439
  var init_runtime4 = __esm({
30314
30440
  "src/sdk/runtime.ts"() {
30315
30441
  init_src3();
30316
30442
  init_browser_manager();
30317
30443
  init_engine_selection2();
30444
+ init_live_session();
30318
30445
  init_root2();
30319
- OpensteerRuntime = class extends OpensteerSessionRuntime {
30446
+ LocalActivePageHintRuntime = class extends OpensteerSessionRuntime {
30447
+ async completeWithLocalActivePageHint(operation) {
30448
+ const output = await operation();
30449
+ await persistLocalActivePageHint(this, this.rootPath);
30450
+ return output;
30451
+ }
30452
+ async open(input = {}, options = {}) {
30453
+ return this.completeWithLocalActivePageHint(() => super.open(input, options));
30454
+ }
30455
+ async newPage(input = {}, options = {}) {
30456
+ return this.completeWithLocalActivePageHint(() => super.newPage(input, options));
30457
+ }
30458
+ async activatePage(input, options = {}) {
30459
+ return this.completeWithLocalActivePageHint(() => super.activatePage(input, options));
30460
+ }
30461
+ async closePage(input = {}, options = {}) {
30462
+ return this.completeWithLocalActivePageHint(() => super.closePage(input, options));
30463
+ }
30464
+ async goto(input, options = {}) {
30465
+ return this.completeWithLocalActivePageHint(() => super.goto(input, options));
30466
+ }
30467
+ };
30468
+ OpensteerRuntime = class extends LocalActivePageHintRuntime {
30320
30469
  constructor(options = {}) {
30321
30470
  const publicWorkspace = normalizeWorkspace2(options.workspace);
30322
30471
  const rootPath = options.rootPath ?? (publicWorkspace === void 0 ? path10__default.default.resolve(options.rootDir ?? process.cwd(), ".opensteer", "temporary", crypto.randomUUID()) : resolveFilesystemWorkspacePath({
@@ -31038,7 +31187,7 @@ var init_opensteer = __esm({
31038
31187
 
31039
31188
  // package.json
31040
31189
  var package_default = {
31041
- version: "0.9.7"};
31190
+ version: "0.9.8"};
31042
31191
 
31043
31192
  // src/cli/bin.ts
31044
31193
  init_browser_manager();
@@ -31693,14 +31842,22 @@ async function buildOperationInput(operation, parsed, runtime) {
31693
31842
  return parsed.rest[0] === void 0 ? {} : { mode: parsed.rest[0] };
31694
31843
  case "page.evaluate":
31695
31844
  if (parsed.rest[0] === void 0) {
31696
- throw new CliError("missing_arguments", "evaluate requires a script.", CLI_USAGE_HINTS[operation]);
31845
+ throw new CliError(
31846
+ "missing_arguments",
31847
+ "evaluate requires a script.",
31848
+ CLI_USAGE_HINTS[operation]
31849
+ );
31697
31850
  }
31698
31851
  return {
31699
31852
  script: joinRest(parsed.rest, 0)
31700
31853
  };
31701
31854
  case "page.add-init-script":
31702
31855
  if (parsed.rest[0] === void 0) {
31703
- throw new CliError("missing_arguments", "init-script requires a script.", CLI_USAGE_HINTS[operation]);
31856
+ throw new CliError(
31857
+ "missing_arguments",
31858
+ "init-script requires a script.",
31859
+ CLI_USAGE_HINTS[operation]
31860
+ );
31704
31861
  }
31705
31862
  return {
31706
31863
  script: joinRest(parsed.rest, 0)
@@ -31716,7 +31873,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31716
31873
  return buildElementTargetInput(parsed, "hover");
31717
31874
  case "dom.input": {
31718
31875
  if (parsed.rest[1] === void 0) {
31719
- throw new CliError("missing_arguments", "input requires an element number and text.", CLI_USAGE_HINTS[operation]);
31876
+ throw new CliError(
31877
+ "missing_arguments",
31878
+ "input requires an element number and text.",
31879
+ CLI_USAGE_HINTS[operation]
31880
+ );
31720
31881
  }
31721
31882
  const pressEnter = readOptionalBoolean(parsed.rawOptions, "press-enter");
31722
31883
  return {
@@ -31747,7 +31908,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31747
31908
  }
31748
31909
  case "dom.extract": {
31749
31910
  if (parsed.rest[0] === void 0) {
31750
- throw new CliError("missing_arguments", "extract requires a template.", CLI_USAGE_HINTS[operation]);
31911
+ throw new CliError(
31912
+ "missing_arguments",
31913
+ "extract requires a template.",
31914
+ CLI_USAGE_HINTS[operation]
31915
+ );
31751
31916
  }
31752
31917
  const persist = readPersistKey(parsed, "extract");
31753
31918
  return {
@@ -31783,7 +31948,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31783
31948
  }
31784
31949
  case "network.detail": {
31785
31950
  if (parsed.rest[0] === void 0) {
31786
- throw new CliError("missing_arguments", "network detail requires a record id.", CLI_USAGE_HINTS[operation]);
31951
+ throw new CliError(
31952
+ "missing_arguments",
31953
+ "network detail requires a record id.",
31954
+ CLI_USAGE_HINTS[operation]
31955
+ );
31787
31956
  }
31788
31957
  const probeFlag = readOptionalBoolean(parsed.rawOptions, "probe");
31789
31958
  return {
@@ -31794,7 +31963,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31794
31963
  case "session.fetch": {
31795
31964
  const url = parsed.rest[0];
31796
31965
  if (url === void 0) {
31797
- throw new CliError("missing_arguments", "fetch requires a URL.", CLI_USAGE_HINTS[operation]);
31966
+ throw new CliError(
31967
+ "missing_arguments",
31968
+ "fetch requires a URL.",
31969
+ CLI_USAGE_HINTS[operation]
31970
+ );
31798
31971
  }
31799
31972
  const bodyJson = readJsonValue(parsed.rawOptions, "body");
31800
31973
  const bodyText = readSingle(parsed.rawOptions, "body-text");
@@ -31833,7 +32006,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31833
32006
  const siteKey = readSingle(parsed.rawOptions, "site-key");
31834
32007
  const pageUrl = readSingle(parsed.rawOptions, "page-url");
31835
32008
  if (provider === void 0 || apiKey === void 0) {
31836
- throw new CliError("missing_arguments", 'captcha solve requires "--provider" and "--api-key".', CLI_USAGE_HINTS[operation]);
32009
+ throw new CliError(
32010
+ "missing_arguments",
32011
+ 'captcha solve requires "--provider" and "--api-key".',
32012
+ CLI_USAGE_HINTS[operation]
32013
+ );
31837
32014
  }
31838
32015
  return {
31839
32016
  provider: readCaptchaProvider(provider),
@@ -31863,7 +32040,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31863
32040
  case "scripts.beautify":
31864
32041
  case "scripts.deobfuscate": {
31865
32042
  if (parsed.rest[0] === void 0) {
31866
- throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires an artifact id.`, CLI_USAGE_HINTS[operation]);
32043
+ throw new CliError(
32044
+ "missing_arguments",
32045
+ `${parsed.command.join(" ")} requires an artifact id.`,
32046
+ CLI_USAGE_HINTS[operation]
32047
+ );
31867
32048
  }
31868
32049
  const persist = readOptionalBoolean(parsed.rawOptions, "persist");
31869
32050
  return {
@@ -31873,7 +32054,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31873
32054
  }
31874
32055
  case "scripts.sandbox":
31875
32056
  if (parsed.rest[0] === void 0) {
31876
- throw new CliError("missing_arguments", "scripts sandbox requires an artifact id.", CLI_USAGE_HINTS[operation]);
32057
+ throw new CliError(
32058
+ "missing_arguments",
32059
+ "scripts sandbox requires an artifact id.",
32060
+ CLI_USAGE_HINTS[operation]
32061
+ );
31877
32062
  }
31878
32063
  {
31879
32064
  const fidelity = readSingle(parsed.rawOptions, "fidelity");
@@ -31922,14 +32107,22 @@ async function buildOperationInput(operation, parsed, runtime) {
31922
32107
  case "interaction.get":
31923
32108
  case "interaction.replay":
31924
32109
  if (parsed.rest[0] === void 0) {
31925
- throw new CliError("missing_arguments", `${parsed.command.join(" ")} requires a trace id.`, CLI_USAGE_HINTS[operation]);
32110
+ throw new CliError(
32111
+ "missing_arguments",
32112
+ `${parsed.command.join(" ")} requires a trace id.`,
32113
+ CLI_USAGE_HINTS[operation]
32114
+ );
31926
32115
  }
31927
32116
  return {
31928
32117
  traceId: parsed.rest[0]
31929
32118
  };
31930
32119
  case "interaction.diff":
31931
32120
  if (parsed.rest[0] === void 0 || parsed.rest[1] === void 0) {
31932
- throw new CliError("missing_arguments", "interaction diff requires two trace ids.", CLI_USAGE_HINTS[operation]);
32121
+ throw new CliError(
32122
+ "missing_arguments",
32123
+ "interaction diff requires two trace ids.",
32124
+ CLI_USAGE_HINTS[operation]
32125
+ );
31933
32126
  }
31934
32127
  return {
31935
32128
  leftTraceId: parsed.rest[0],
@@ -31937,7 +32130,11 @@ async function buildOperationInput(operation, parsed, runtime) {
31937
32130
  };
31938
32131
  case "artifact.read":
31939
32132
  if (parsed.rest[0] === void 0) {
31940
- throw new CliError("missing_arguments", "artifact read requires an artifact id.", CLI_USAGE_HINTS[operation]);
32133
+ throw new CliError(
32134
+ "missing_arguments",
32135
+ "artifact read requires an artifact id.",
32136
+ CLI_USAGE_HINTS[operation]
32137
+ );
31941
32138
  }
31942
32139
  return {
31943
32140
  artifactId: parsed.rest[0]
@@ -32075,7 +32272,10 @@ function buildComputerExecuteInput(parsed) {
32075
32272
  }
32076
32273
  };
32077
32274
  default:
32078
- throw new CliError("unknown_command", `Unknown computer command: ${parsed.command.join(" ")}`);
32275
+ throw new CliError(
32276
+ "unknown_command",
32277
+ `Unknown computer command: ${parsed.command.join(" ")}`
32278
+ );
32079
32279
  }
32080
32280
  }
32081
32281
  async function resolvePageRefByIndex(runtime, index) {
@@ -32105,7 +32305,10 @@ function readRequiredNumber(value, message) {
32105
32305
  }
32106
32306
  function readSingleDirection(value) {
32107
32307
  if (value === void 0 || !SCROLL_DIRECTIONS.has(value)) {
32108
- throw new CliError("missing_arguments", "scroll requires a direction: up, down, left, or right.");
32308
+ throw new CliError(
32309
+ "missing_arguments",
32310
+ "scroll requires a direction: up, down, left, or right."
32311
+ );
32109
32312
  }
32110
32313
  return value;
32111
32314
  }
@@ -32132,13 +32335,19 @@ function readCaptchaProvider(value) {
32132
32335
  }
32133
32336
  function readCaptchaType(value) {
32134
32337
  if (value === void 0 || !CAPTCHA_TYPES.has(value)) {
32135
- throw new CliError("invalid_value", 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.');
32338
+ throw new CliError(
32339
+ "invalid_value",
32340
+ 'Expected "--type" to be one of: recaptcha-v2, hcaptcha, turnstile.'
32341
+ );
32136
32342
  }
32137
32343
  return value;
32138
32344
  }
32139
32345
  function readSandboxFidelity(value) {
32140
32346
  if (value === void 0 || !SANDBOX_FIDELITIES.has(value)) {
32141
- throw new CliError("invalid_value", 'Expected "--fidelity" to be one of: minimal, standard, full.');
32347
+ throw new CliError(
32348
+ "invalid_value",
32349
+ 'Expected "--fidelity" to be one of: minimal, standard, full.'
32350
+ );
32142
32351
  }
32143
32352
  return value;
32144
32353
  }
@@ -32161,7 +32370,10 @@ function readKeyModifiers(value) {
32161
32370
  }
32162
32371
  for (const modifier of modifiers) {
32163
32372
  if (!KEY_MODIFIERS.has(modifier)) {
32164
- throw new CliError("invalid_value", 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.');
32373
+ throw new CliError(
32374
+ "invalid_value",
32375
+ 'Expected "--modifiers" to contain only: Shift, Control, Alt, Meta.'
32376
+ );
32165
32377
  }
32166
32378
  }
32167
32379
  return [...new Set(modifiers)];
@@ -33743,6 +33955,172 @@ init_process_owner();
33743
33955
  init_service_state();
33744
33956
  init_service_state();
33745
33957
 
33958
+ // src/local-view/browser-target-order.ts
33959
+ async function readPageTargetId(page) {
33960
+ const cdp = await page.context().newCDPSession(page);
33961
+ try {
33962
+ const result = await cdp.send("Target.getTargetInfo");
33963
+ const targetId = result?.targetInfo?.targetId;
33964
+ return typeof targetId === "string" && targetId.length > 0 ? targetId : null;
33965
+ } finally {
33966
+ await cdp.detach().catch(() => void 0);
33967
+ }
33968
+ }
33969
+ async function readBrowserPageTargetOrder(browserContext) {
33970
+ const browser = browserContext.browser();
33971
+ if (!browser || !hasBrowserCdpSession(browser)) {
33972
+ return [];
33973
+ }
33974
+ const cdp = await browser.newBrowserCDPSession();
33975
+ try {
33976
+ const result = await cdp.send("Target.getTargets");
33977
+ return normalizeBrowserPageTargetOrder(result?.targetInfos ?? []);
33978
+ } catch {
33979
+ return [];
33980
+ } finally {
33981
+ await cdp.detach().catch(() => void 0);
33982
+ }
33983
+ }
33984
+ async function orderPagesByBrowserTargetOrder(browserContext, pages) {
33985
+ if (pages.length < 2) {
33986
+ return pages;
33987
+ }
33988
+ const orderedTargetIds = await readBrowserPageTargetOrder(browserContext);
33989
+ if (orderedTargetIds.length === 0) {
33990
+ return pages;
33991
+ }
33992
+ const rankByTargetId = new Map(orderedTargetIds.map((targetId, index) => [targetId, index]));
33993
+ const targetIds = await Promise.all(
33994
+ pages.map((page) => readPageTargetId(page).catch(() => null))
33995
+ );
33996
+ return pages.map((page, index) => {
33997
+ const targetId = targetIds[index] ?? void 0;
33998
+ return {
33999
+ page,
34000
+ index,
34001
+ rank: targetId === void 0 ? void 0 : rankByTargetId.get(targetId)
34002
+ };
34003
+ }).sort((left, right) => {
34004
+ if (left.rank !== void 0 && right.rank !== void 0) {
34005
+ return left.rank - right.rank;
34006
+ }
34007
+ if (left.rank !== void 0) {
34008
+ return -1;
34009
+ }
34010
+ if (right.rank !== void 0) {
34011
+ return 1;
34012
+ }
34013
+ return left.index - right.index;
34014
+ }).map((entry) => entry.page);
34015
+ }
34016
+ function hasBrowserCdpSession(browser) {
34017
+ return typeof browser.newBrowserCDPSession === "function";
34018
+ }
34019
+ function normalizeBrowserPageTargetOrder(targetInfos) {
34020
+ const reversedPageInfos = [];
34021
+ for (const targetInfo of targetInfos) {
34022
+ if (targetInfo.type !== "page") {
34023
+ continue;
34024
+ }
34025
+ const targetId = typeof targetInfo.targetId === "string" && targetInfo.targetId.length > 0 ? targetInfo.targetId : void 0;
34026
+ if (targetId === void 0) {
34027
+ continue;
34028
+ }
34029
+ reversedPageInfos.push({
34030
+ targetId,
34031
+ openerId: typeof targetInfo.openerId === "string" && targetInfo.openerId.length > 0 ? targetInfo.openerId : void 0
34032
+ });
34033
+ }
34034
+ reversedPageInfos.reverse();
34035
+ const rawTargetInfoById = new Map(
34036
+ reversedPageInfos.map((targetInfo) => [targetInfo.targetId, targetInfo])
34037
+ );
34038
+ const targetInfoById = new Map(
34039
+ reversedPageInfos.map(
34040
+ (targetInfo) => [
34041
+ targetInfo.targetId,
34042
+ {
34043
+ ...targetInfo,
34044
+ openerId: resolveAcyclicOpenerId(targetInfo.targetId, rawTargetInfoById)
34045
+ }
34046
+ ]
34047
+ )
34048
+ );
34049
+ const orderedTargetIds = [];
34050
+ const placed = /* @__PURE__ */ new Set();
34051
+ const placeTarget = (targetId) => {
34052
+ if (placed.has(targetId)) {
34053
+ return;
34054
+ }
34055
+ const targetInfo = targetInfoById.get(targetId);
34056
+ if (!targetInfo) {
34057
+ return;
34058
+ }
34059
+ const openerId = targetInfo.openerId;
34060
+ if (openerId === void 0) {
34061
+ orderedTargetIds.push(targetId);
34062
+ placed.add(targetId);
34063
+ return;
34064
+ }
34065
+ placeTarget(openerId);
34066
+ const openerIndex = orderedTargetIds.indexOf(openerId);
34067
+ const insertionIndex = openerIndex === -1 ? orderedTargetIds.length : findPopupInsertionIndex(orderedTargetIds, openerIndex, openerId, targetInfoById);
34068
+ orderedTargetIds.splice(insertionIndex, 0, targetId);
34069
+ placed.add(targetId);
34070
+ };
34071
+ for (const targetInfo of reversedPageInfos) {
34072
+ placeTarget(targetInfo.targetId);
34073
+ }
34074
+ return orderedTargetIds;
34075
+ }
34076
+ function resolveAcyclicOpenerId(targetId, targetInfoById) {
34077
+ const openerId = targetInfoById.get(targetId)?.openerId;
34078
+ if (openerId === void 0 || !targetInfoById.has(openerId)) {
34079
+ return void 0;
34080
+ }
34081
+ const visitedTargetIds = /* @__PURE__ */ new Set([targetId]);
34082
+ let currentTargetId = openerId;
34083
+ while (currentTargetId !== void 0) {
34084
+ if (visitedTargetIds.has(currentTargetId)) {
34085
+ return void 0;
34086
+ }
34087
+ visitedTargetIds.add(currentTargetId);
34088
+ currentTargetId = targetInfoById.get(currentTargetId)?.openerId;
34089
+ }
34090
+ return openerId;
34091
+ }
34092
+ function findPopupInsertionIndex(orderedTargetIds, openerIndex, openerTargetId, targetInfoById) {
34093
+ let index = openerIndex + 1;
34094
+ while (index < orderedTargetIds.length) {
34095
+ const candidateTargetId = orderedTargetIds[index];
34096
+ if (!candidateTargetId || !isDescendantTarget(candidateTargetId, openerTargetId, targetInfoById)) {
34097
+ break;
34098
+ }
34099
+ index += 1;
34100
+ }
34101
+ return index;
34102
+ }
34103
+ function isDescendantTarget(targetId, ancestorTargetId, targetInfoById) {
34104
+ const visitedTargetIds = /* @__PURE__ */ new Set();
34105
+ let currentTargetId = targetId;
34106
+ while (currentTargetId !== void 0) {
34107
+ if (visitedTargetIds.has(currentTargetId)) {
34108
+ return false;
34109
+ }
34110
+ visitedTargetIds.add(currentTargetId);
34111
+ const currentTargetInfo = targetInfoById.get(currentTargetId);
34112
+ const openerId = currentTargetInfo?.openerId;
34113
+ if (openerId === void 0) {
34114
+ return false;
34115
+ }
34116
+ if (openerId === ancestorTargetId) {
34117
+ return true;
34118
+ }
34119
+ currentTargetId = openerId;
34120
+ }
34121
+ return false;
34122
+ }
34123
+
33746
34124
  // src/local-view/tab-state-tracker.ts
33747
34125
  var ACTIVATION_INTENT_DISCOVERY_GRACE_MS = 2e3;
33748
34126
  var TabStateTracker = class {
@@ -33758,6 +34136,7 @@ var TabStateTracker = class {
33758
34136
  boundContextCleanup = null;
33759
34137
  constructor(deps) {
33760
34138
  this.deps = deps;
34139
+ this.lastActivePage = deps.initialActivePage ?? null;
33761
34140
  }
33762
34141
  start() {
33763
34142
  if (this.running) {
@@ -33882,7 +34261,7 @@ var TabStateTracker = class {
33882
34261
  this.updatePolling(pages.length);
33883
34262
  const preferredActivePage = this.lastActivePage ?? pages[0] ?? null;
33884
34263
  const pageStates = await Promise.all(
33885
- pages.map(async (page, index) => {
34264
+ pages.map(async (page, originalIndex) => {
33886
34265
  const metadata = await this.readPageMetadata(page, {
33887
34266
  refresh: args.refreshMetadata
33888
34267
  });
@@ -33892,7 +34271,7 @@ var TabStateTracker = class {
33892
34271
  };
33893
34272
  return {
33894
34273
  page,
33895
- index,
34274
+ originalIndex,
33896
34275
  targetId: metadata.targetId,
33897
34276
  url: page.url(),
33898
34277
  title: metadata.title,
@@ -33901,17 +34280,18 @@ var TabStateTracker = class {
33901
34280
  };
33902
34281
  })
33903
34282
  );
34283
+ const orderedPageStates = await this.orderPageStates(pageStates);
33904
34284
  const activePage = this.pickActivePage(
33905
- pageStates,
34285
+ orderedPageStates,
33906
34286
  this.lastActivePage,
33907
34287
  preferredActivePage,
33908
- this.resolveIntentPage(pageStates)
34288
+ this.resolveIntentPage(orderedPageStates)
33909
34289
  );
33910
34290
  if (activePage && activePage !== this.lastActivePage) {
33911
34291
  this.lastActivePage = activePage;
33912
34292
  this.deps.onActivePageChanged(activePage);
33913
34293
  }
33914
- const tabs = pageStates.map((state) => ({
34294
+ const tabs = orderedPageStates.map((state) => ({
33915
34295
  index: state.index,
33916
34296
  ...state.targetId === void 0 ? {} : { targetId: state.targetId },
33917
34297
  url: state.url,
@@ -33961,18 +34341,11 @@ var TabStateTracker = class {
33961
34341
  if (cached) {
33962
34342
  return cached;
33963
34343
  }
33964
- const cdp = await page.context().newCDPSession(page);
33965
- try {
33966
- const result = await cdp.send("Target.getTargetInfo");
33967
- const targetId = result?.targetInfo?.targetId;
33968
- if (typeof targetId === "string" && targetId.length > 0) {
33969
- this.targetIdByPage.set(page, targetId);
33970
- return targetId;
33971
- }
33972
- return null;
33973
- } finally {
33974
- await cdp.detach().catch(() => void 0);
34344
+ const targetId = await readPageTargetId(page);
34345
+ if (targetId) {
34346
+ this.targetIdByPage.set(page, targetId);
33975
34347
  }
34348
+ return targetId;
33976
34349
  }
33977
34350
  async readFocusState(page) {
33978
34351
  try {
@@ -34040,6 +34413,33 @@ var TabStateTracker = class {
34040
34413
  this.deps.runtimeState.clearPageActivationIntent(this.deps.sessionId, intent.targetId);
34041
34414
  return { page: matched.page };
34042
34415
  }
34416
+ async orderPageStates(pageStates) {
34417
+ if (pageStates.length < 2) {
34418
+ return pageStates.map(({ originalIndex: _originalIndex, ...state }, index) => ({
34419
+ ...state,
34420
+ index
34421
+ }));
34422
+ }
34423
+ const orderedTargetIds = await readBrowserPageTargetOrder(this.deps.browserContext);
34424
+ const rankByTargetId = new Map(orderedTargetIds.map((targetId, index) => [targetId, index]));
34425
+ return [...pageStates].sort((left, right) => {
34426
+ const leftRank = left.targetId === void 0 ? void 0 : rankByTargetId.get(left.targetId);
34427
+ const rightRank = right.targetId === void 0 ? void 0 : rankByTargetId.get(right.targetId);
34428
+ if (leftRank !== void 0 && rightRank !== void 0) {
34429
+ return leftRank - rightRank;
34430
+ }
34431
+ if (leftRank !== void 0) {
34432
+ return -1;
34433
+ }
34434
+ if (rightRank !== void 0) {
34435
+ return 1;
34436
+ }
34437
+ return left.originalIndex - right.originalIndex;
34438
+ }).map(({ originalIndex: _originalIndex, ...state }, index) => ({
34439
+ ...state,
34440
+ index
34441
+ }));
34442
+ }
34043
34443
  };
34044
34444
 
34045
34445
  // src/local-view/view-stream-capture-policy.ts
@@ -34403,6 +34803,7 @@ var SessionViewStreamProducer = class {
34403
34803
  sessionId: this.deps.sessionId,
34404
34804
  pollMs: TAB_STATE_POLL_MS,
34405
34805
  runtimeState: this.deps.runtimeState,
34806
+ initialActivePage: session.page,
34406
34807
  onActivePageChanged: (page) => {
34407
34808
  this.activePage = page;
34408
34809
  void this.queueBindToPage(page).catch(() => void 0);
@@ -34501,7 +34902,20 @@ var SessionViewStreamProducer = class {
34501
34902
  if (!context) {
34502
34903
  throw new Error("Connected browser did not expose a Chromium browser context.");
34503
34904
  }
34504
- const page = context.pages()[0] ?? await context.newPage();
34905
+ const existingPages = context.pages();
34906
+ if (existingPages.length === 0) {
34907
+ const page2 = await context.newPage();
34908
+ return {
34909
+ browser,
34910
+ context,
34911
+ page: page2
34912
+ };
34913
+ }
34914
+ const orderedPages = await orderPagesByBrowserTargetOrder(context, existingPages);
34915
+ const page = await resolvePersistedActivePage(orderedPages, {
34916
+ ...resolved.record.activePageUrl === void 0 ? {} : { activePageUrl: resolved.record.activePageUrl },
34917
+ ...resolved.record.activePageTitle === void 0 ? {} : { activePageTitle: resolved.record.activePageTitle }
34918
+ }) ?? orderedPages[0];
34505
34919
  return {
34506
34920
  browser,
34507
34921
  context,
@@ -34846,6 +35260,28 @@ async function disconnectPlaywrightChromiumBrowser2(browser) {
34846
35260
  const { disconnectPlaywrightChromiumBrowser: disconnect } = await import('@opensteer/engine-playwright');
34847
35261
  await disconnect(browser);
34848
35262
  }
35263
+ async function resolvePersistedActivePage(pages, input) {
35264
+ if (pages.length === 0) {
35265
+ return null;
35266
+ }
35267
+ if (input.activePageUrl === void 0 && input.activePageTitle === void 0) {
35268
+ return null;
35269
+ }
35270
+ const matchesByUrl = input.activePageUrl === void 0 ? pages : pages.filter((page) => page.url() === input.activePageUrl);
35271
+ if (matchesByUrl.length === 0) {
35272
+ return null;
35273
+ }
35274
+ if (input.activePageTitle === void 0) {
35275
+ return matchesByUrl[0] ?? null;
35276
+ }
35277
+ for (const page of matchesByUrl) {
35278
+ const title = await page.title().catch(() => "");
35279
+ if (title === input.activePageTitle) {
35280
+ return page;
35281
+ }
35282
+ }
35283
+ return matchesByUrl[0] ?? null;
35284
+ }
34849
35285
 
34850
35286
  // src/local-view/server.ts
34851
35287
  var DEFAULT_MAX_FPS = 12;
@@ -35326,7 +35762,10 @@ async function main() {
35326
35762
  throw new CliError("unknown_command", `Unknown command: ${parsed.command.join(" ")}`);
35327
35763
  }
35328
35764
  if (parsed.options.workspace === void 0) {
35329
- throw new CliError("missing_workspace", 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.');
35765
+ throw new CliError(
35766
+ "missing_workspace",
35767
+ 'Stateful commands require "--workspace <id>" or OPENSTEER_WORKSPACE.'
35768
+ );
35330
35769
  }
35331
35770
  const { engineName, provider, runtimeProvider } = resolveCliRuntimeSelection(parsed);
35332
35771
  if (operation === "session.close") {
@@ -35373,7 +35812,10 @@ async function main() {
35373
35812
  }
35374
35813
  async function handleExecCommand(parsed) {
35375
35814
  if (parsed.options.workspace === void 0) {
35376
- throw new CliError("missing_workspace", 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35815
+ throw new CliError(
35816
+ "missing_workspace",
35817
+ 'exec requires "--workspace <id>" or OPENSTEER_WORKSPACE.'
35818
+ );
35377
35819
  }
35378
35820
  const expression = parsed.rest.join(" ");
35379
35821
  if (!expression) {
@@ -35516,7 +35958,10 @@ async function handleCloseCommand(parsed, engineName, providerMode, runtimeProvi
35516
35958
  }
35517
35959
  async function handleRecordCommandEntry(parsed) {
35518
35960
  if (parsed.options.workspace === void 0) {
35519
- throw new CliError("missing_workspace", 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.');
35961
+ throw new CliError(
35962
+ "missing_workspace",
35963
+ 'record requires "--workspace <id>" or OPENSTEER_WORKSPACE.'
35964
+ );
35520
35965
  }
35521
35966
  const url = parsed.options.url ?? parsed.rest[0];
35522
35967
  if (url === void 0) {
@@ -35556,7 +36001,10 @@ async function handleRecordCommandEntry(parsed) {
35556
36001
  return;
35557
36002
  }
35558
36003
  if (parsed.options.launch?.headless === true) {
35559
- throw new CliError("config_conflict", 'record requires a headed browser. Remove "--headless true".');
36004
+ throw new CliError(
36005
+ "config_conflict",
36006
+ 'record requires a headed browser. Remove "--headless true".'
36007
+ );
35560
36008
  }
35561
36009
  if (typeof recordBrowser === "object") {
35562
36010
  throw new CliError("config_conflict", 'record does not support browser.mode="attach".');
@@ -35628,7 +36076,10 @@ function resolveCliBootstrapAction(argv) {
35628
36076
  }
35629
36077
  function buildCliBrowserProfile(parsed) {
35630
36078
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
35631
- throw new CliError("invalid_option", '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
36079
+ throw new CliError(
36080
+ "invalid_option",
36081
+ '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".'
36082
+ );
35632
36083
  }
35633
36084
  return parsed.options.cloudProfileId === void 0 ? void 0 : {
35634
36085
  profileId: parsed.options.cloudProfileId,