opensteer 0.8.9 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/bin.cjs CHANGED
@@ -59,7 +59,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
59
59
 
60
60
  // package.json
61
61
  var package_default = {
62
- version: "0.8.8"};
62
+ version: "0.8.10"};
63
63
  util.promisify(child_process.execFile);
64
64
  Math.floor(Date.now() - process.uptime() * 1e3);
65
65
  ({ ...process.env});
@@ -690,8 +690,8 @@ function buildBrowserWebSocketUrl(httpUrl, webSocketPath) {
690
690
  const protocol = httpUrl.protocol === "https:" ? "wss:" : "ws:";
691
691
  return `${protocol}//${httpUrl.host}${normalizeWebSocketPath(webSocketPath)}`;
692
692
  }
693
- function normalizeWebSocketPath(path17) {
694
- return path17.startsWith("/") ? path17 : `/${path17}`;
693
+ function normalizeWebSocketPath(path18) {
694
+ return path18.startsWith("/") ? path18 : `/${path18}`;
695
695
  }
696
696
  function rewriteBrowserWebSocketHost(browserWsUrl, requestedUrl) {
697
697
  try {
@@ -1305,30 +1305,30 @@ function isPlainObject(value) {
1305
1305
  const prototype = Object.getPrototypeOf(value);
1306
1306
  return prototype === Object.prototype || prototype === null;
1307
1307
  }
1308
- function canonicalizeJsonValue(value, path17) {
1308
+ function canonicalizeJsonValue(value, path18) {
1309
1309
  if (value === null || typeof value === "string" || typeof value === "boolean") {
1310
1310
  return value;
1311
1311
  }
1312
1312
  if (typeof value === "number") {
1313
1313
  if (!Number.isFinite(value)) {
1314
- throw new TypeError(`${path17} must be a finite JSON number`);
1314
+ throw new TypeError(`${path18} must be a finite JSON number`);
1315
1315
  }
1316
1316
  return value;
1317
1317
  }
1318
1318
  if (Array.isArray(value)) {
1319
- return value.map((entry, index) => canonicalizeJsonValue(entry, `${path17}[${index}]`));
1319
+ return value.map((entry, index) => canonicalizeJsonValue(entry, `${path18}[${index}]`));
1320
1320
  }
1321
1321
  if (!isPlainObject(value)) {
1322
- throw new TypeError(`${path17} must be a plain JSON object`);
1322
+ throw new TypeError(`${path18} must be a plain JSON object`);
1323
1323
  }
1324
1324
  const sorted = Object.keys(value).sort((left, right) => left.localeCompare(right));
1325
1325
  const result = {};
1326
1326
  for (const key of sorted) {
1327
1327
  const entry = value[key];
1328
1328
  if (entry === void 0) {
1329
- throw new TypeError(`${path17}.${key} must not be undefined`);
1329
+ throw new TypeError(`${path18}.${key} must not be undefined`);
1330
1330
  }
1331
- result[key] = canonicalizeJsonValue(entry, `${path17}.${key}`);
1331
+ result[key] = canonicalizeJsonValue(entry, `${path18}.${key}`);
1332
1332
  }
1333
1333
  return result;
1334
1334
  }
@@ -1825,31 +1825,31 @@ function oneOfSchema(members, options = {}) {
1825
1825
  }
1826
1826
 
1827
1827
  // ../protocol/src/validation.ts
1828
- function validateJsonSchema(schema, value, path17 = "$") {
1829
- return validateSchemaNode(schema, value, path17);
1828
+ function validateJsonSchema(schema, value, path18 = "$") {
1829
+ return validateSchemaNode(schema, value, path18);
1830
1830
  }
1831
- function validateSchemaNode(schema, value, path17) {
1831
+ function validateSchemaNode(schema, value, path18) {
1832
1832
  const issues = [];
1833
1833
  if ("const" in schema && !isJsonValueEqual(schema.const, value)) {
1834
1834
  issues.push({
1835
- path: path17,
1835
+ path: path18,
1836
1836
  message: `must equal ${JSON.stringify(schema.const)}`
1837
1837
  });
1838
1838
  return issues;
1839
1839
  }
1840
1840
  if (schema.enum !== void 0 && !schema.enum.some((candidate) => isJsonValueEqual(candidate, value))) {
1841
1841
  issues.push({
1842
- path: path17,
1842
+ path: path18,
1843
1843
  message: `must be one of ${schema.enum.map((candidate) => JSON.stringify(candidate)).join(", ")}`
1844
1844
  });
1845
1845
  return issues;
1846
1846
  }
1847
1847
  if (schema.oneOf !== void 0) {
1848
- const branchIssues = schema.oneOf.map((member) => validateSchemaNode(member, value, path17));
1848
+ const branchIssues = schema.oneOf.map((member) => validateSchemaNode(member, value, path18));
1849
1849
  const validBranches = branchIssues.filter((current) => current.length === 0).length;
1850
1850
  if (validBranches !== 1) {
1851
1851
  issues.push({
1852
- path: path17,
1852
+ path: path18,
1853
1853
  message: validBranches === 0 ? "must match exactly one supported shape" : "matches multiple supported shapes"
1854
1854
  });
1855
1855
  return issues;
@@ -1857,11 +1857,11 @@ function validateSchemaNode(schema, value, path17) {
1857
1857
  }
1858
1858
  if (schema.anyOf !== void 0) {
1859
1859
  const hasMatch = schema.anyOf.some(
1860
- (member) => validateSchemaNode(member, value, path17).length === 0
1860
+ (member) => validateSchemaNode(member, value, path18).length === 0
1861
1861
  );
1862
1862
  if (!hasMatch) {
1863
1863
  issues.push({
1864
- path: path17,
1864
+ path: path18,
1865
1865
  message: "must match at least one supported shape"
1866
1866
  });
1867
1867
  return issues;
@@ -1869,7 +1869,7 @@ function validateSchemaNode(schema, value, path17) {
1869
1869
  }
1870
1870
  if (schema.allOf !== void 0) {
1871
1871
  for (const member of schema.allOf) {
1872
- issues.push(...validateSchemaNode(member, value, path17));
1872
+ issues.push(...validateSchemaNode(member, value, path18));
1873
1873
  }
1874
1874
  if (issues.length > 0) {
1875
1875
  return issues;
@@ -1877,7 +1877,7 @@ function validateSchemaNode(schema, value, path17) {
1877
1877
  }
1878
1878
  if (schema.type !== void 0 && !matchesSchemaType(schema.type, value)) {
1879
1879
  issues.push({
1880
- path: path17,
1880
+ path: path18,
1881
1881
  message: `must be ${describeSchemaType(schema.type)}`
1882
1882
  });
1883
1883
  return issues;
@@ -1885,19 +1885,19 @@ function validateSchemaNode(schema, value, path17) {
1885
1885
  if (typeof value === "string") {
1886
1886
  if (schema.minLength !== void 0 && value.length < schema.minLength) {
1887
1887
  issues.push({
1888
- path: path17,
1888
+ path: path18,
1889
1889
  message: `must have length >= ${String(schema.minLength)}`
1890
1890
  });
1891
1891
  }
1892
1892
  if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
1893
1893
  issues.push({
1894
- path: path17,
1894
+ path: path18,
1895
1895
  message: `must have length <= ${String(schema.maxLength)}`
1896
1896
  });
1897
1897
  }
1898
1898
  if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
1899
1899
  issues.push({
1900
- path: path17,
1900
+ path: path18,
1901
1901
  message: `must match pattern ${schema.pattern}`
1902
1902
  });
1903
1903
  }
@@ -1906,25 +1906,25 @@ function validateSchemaNode(schema, value, path17) {
1906
1906
  if (typeof value === "number") {
1907
1907
  if (schema.minimum !== void 0 && value < schema.minimum) {
1908
1908
  issues.push({
1909
- path: path17,
1909
+ path: path18,
1910
1910
  message: `must be >= ${String(schema.minimum)}`
1911
1911
  });
1912
1912
  }
1913
1913
  if (schema.maximum !== void 0 && value > schema.maximum) {
1914
1914
  issues.push({
1915
- path: path17,
1915
+ path: path18,
1916
1916
  message: `must be <= ${String(schema.maximum)}`
1917
1917
  });
1918
1918
  }
1919
1919
  if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
1920
1920
  issues.push({
1921
- path: path17,
1921
+ path: path18,
1922
1922
  message: `must be > ${String(schema.exclusiveMinimum)}`
1923
1923
  });
1924
1924
  }
1925
1925
  if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
1926
1926
  issues.push({
1927
- path: path17,
1927
+ path: path18,
1928
1928
  message: `must be < ${String(schema.exclusiveMaximum)}`
1929
1929
  });
1930
1930
  }
@@ -1933,13 +1933,13 @@ function validateSchemaNode(schema, value, path17) {
1933
1933
  if (Array.isArray(value)) {
1934
1934
  if (schema.minItems !== void 0 && value.length < schema.minItems) {
1935
1935
  issues.push({
1936
- path: path17,
1936
+ path: path18,
1937
1937
  message: `must have at least ${String(schema.minItems)} items`
1938
1938
  });
1939
1939
  }
1940
1940
  if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
1941
1941
  issues.push({
1942
- path: path17,
1942
+ path: path18,
1943
1943
  message: `must have at most ${String(schema.maxItems)} items`
1944
1944
  });
1945
1945
  }
@@ -1949,7 +1949,7 @@ function validateSchemaNode(schema, value, path17) {
1949
1949
  const key = JSON.stringify(item);
1950
1950
  if (seen.has(key)) {
1951
1951
  issues.push({
1952
- path: path17,
1952
+ path: path18,
1953
1953
  message: "must not contain duplicate items"
1954
1954
  });
1955
1955
  break;
@@ -1959,7 +1959,7 @@ function validateSchemaNode(schema, value, path17) {
1959
1959
  }
1960
1960
  if (schema.items !== void 0) {
1961
1961
  for (let index = 0; index < value.length; index += 1) {
1962
- issues.push(...validateSchemaNode(schema.items, value[index], `${path17}[${String(index)}]`));
1962
+ issues.push(...validateSchemaNode(schema.items, value[index], `${path18}[${String(index)}]`));
1963
1963
  }
1964
1964
  }
1965
1965
  return issues;
@@ -1969,7 +1969,7 @@ function validateSchemaNode(schema, value, path17) {
1969
1969
  for (const requiredKey of schema.required ?? []) {
1970
1970
  if (!(requiredKey in value)) {
1971
1971
  issues.push({
1972
- path: joinObjectPath(path17, requiredKey),
1972
+ path: joinObjectPath(path18, requiredKey),
1973
1973
  message: "is required"
1974
1974
  });
1975
1975
  }
@@ -1978,13 +1978,13 @@ function validateSchemaNode(schema, value, path17) {
1978
1978
  const propertySchema = properties[key];
1979
1979
  if (propertySchema !== void 0) {
1980
1980
  issues.push(
1981
- ...validateSchemaNode(propertySchema, propertyValue, joinObjectPath(path17, key))
1981
+ ...validateSchemaNode(propertySchema, propertyValue, joinObjectPath(path18, key))
1982
1982
  );
1983
1983
  continue;
1984
1984
  }
1985
1985
  if (schema.additionalProperties === false) {
1986
1986
  issues.push({
1987
- path: joinObjectPath(path17, key),
1987
+ path: joinObjectPath(path18, key),
1988
1988
  message: "is not allowed"
1989
1989
  });
1990
1990
  continue;
@@ -1994,7 +1994,7 @@ function validateSchemaNode(schema, value, path17) {
1994
1994
  ...validateSchemaNode(
1995
1995
  schema.additionalProperties,
1996
1996
  propertyValue,
1997
- joinObjectPath(path17, key)
1997
+ joinObjectPath(path18, key)
1998
1998
  )
1999
1999
  );
2000
2000
  }
@@ -2238,8 +2238,8 @@ function matchesNetworkRecordFilters(record, filters) {
2238
2238
  }
2239
2239
  }
2240
2240
  if (filters.path !== void 0) {
2241
- const path17 = getParsedUrl().pathname;
2242
- if (!includesCaseInsensitive(path17, filters.path)) {
2241
+ const path18 = getParsedUrl().pathname;
2242
+ if (!includesCaseInsensitive(path18, filters.path)) {
2243
2243
  return false;
2244
2244
  }
2245
2245
  }
@@ -2283,6 +2283,9 @@ function isBrowserCoreError(value) {
2283
2283
  // ../browser-core/src/cdp-visual-stability.ts
2284
2284
  var DEFAULT_VISUAL_STABILITY_SETTLE_MS = 750;
2285
2285
 
2286
+ // ../browser-core/src/post-load-tracker.ts
2287
+ var DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS = 400;
2288
+
2286
2289
  // ../protocol/src/identity.ts
2287
2290
  var refPrefixes = [
2288
2291
  "session",
@@ -7472,9 +7475,23 @@ var opensteerInspectStorageInputSchema = objectSchema(
7472
7475
  title: "OpensteerInspectStorageInput"
7473
7476
  }
7474
7477
  );
7478
+ var opensteerComputerMouseButtonSchema = enumSchema(["left", "middle", "right"], {
7479
+ title: "OpensteerComputerMouseButton"
7480
+ });
7481
+ var opensteerComputerKeyModifierSchema = enumSchema(
7482
+ ["Shift", "Control", "Alt", "Meta"],
7483
+ {
7484
+ title: "OpensteerComputerKeyModifier"
7485
+ }
7486
+ );
7475
7487
  var opensteerDomClickInputSchema = objectSchema(
7476
7488
  {
7477
7489
  target: opensteerTargetInputSchema,
7490
+ button: opensteerComputerMouseButtonSchema,
7491
+ clickCount: integerSchema({ minimum: 1 }),
7492
+ modifiers: arraySchema(opensteerComputerKeyModifierSchema, {
7493
+ uniqueItems: true
7494
+ }),
7478
7495
  persistAsDescription: stringSchema(),
7479
7496
  captureNetwork: stringSchema({ minLength: 1 })
7480
7497
  },
@@ -7574,18 +7591,6 @@ var opensteerSessionCloseOutputSchema = objectSchema(
7574
7591
  required: ["closed"]
7575
7592
  }
7576
7593
  );
7577
- var opensteerComputerMouseButtonSchema = enumSchema(
7578
- ["left", "middle", "right"],
7579
- {
7580
- title: "OpensteerComputerMouseButton"
7581
- }
7582
- );
7583
- var opensteerComputerKeyModifierSchema = enumSchema(
7584
- ["Shift", "Control", "Alt", "Meta"],
7585
- {
7586
- title: "OpensteerComputerKeyModifier"
7587
- }
7588
- );
7589
7594
  var opensteerComputerAnnotationSchema = enumSchema(opensteerComputerAnnotationNames, {
7590
7595
  title: "OpensteerComputerAnnotation"
7591
7596
  });
@@ -10299,7 +10304,7 @@ async function clearPersistedSessionRecord(rootPath, provider) {
10299
10304
  await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
10300
10305
  }
10301
10306
  function isPersistedCloudSessionRecord(value) {
10302
- 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.baseUrl === "string" && value.baseUrl.length > 0 && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
10307
+ 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);
10303
10308
  }
10304
10309
  function isPersistedLocalBrowserSessionRecord(value) {
10305
10310
  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;
@@ -11727,11 +11732,25 @@ function queryAllCookies(dbPath) {
11727
11732
  FROM cookies`
11728
11733
  );
11729
11734
  stmt.setReadBigInts(true);
11730
- return stmt.all();
11735
+ return stmt.all().map(toRawCookieRow);
11731
11736
  } finally {
11732
11737
  database.close();
11733
11738
  }
11734
11739
  }
11740
+ function toRawCookieRow(row) {
11741
+ return {
11742
+ host_key: row.host_key,
11743
+ name: row.name,
11744
+ value: row.value,
11745
+ encrypted_value: row.encrypted_value,
11746
+ path: row.path,
11747
+ expires_utc: row.expires_utc,
11748
+ is_secure: row.is_secure,
11749
+ is_httponly: row.is_httponly,
11750
+ samesite: row.samesite,
11751
+ is_persistent: row.is_persistent
11752
+ };
11753
+ }
11735
11754
  async function resolveDecryptionKey(brandId, userDataDir) {
11736
11755
  if (process.platform === "darwin") {
11737
11756
  const password = await resolveKeychainPassword(brandId);
@@ -11975,14 +11994,14 @@ function toPortableBrowserProfileCookieRecord(cookie) {
11975
11994
  if (!name || !domain) {
11976
11995
  return null;
11977
11996
  }
11978
- const path17 = typeof cookie.path === "string" && cookie.path.trim().length > 0 ? cookie.path : "/";
11997
+ const path18 = typeof cookie.path === "string" && cookie.path.trim().length > 0 ? cookie.path : "/";
11979
11998
  const expiresAt = typeof cookie.expires === "number" && Number.isFinite(cookie.expires) && cookie.expires > 0 ? Math.floor(cookie.expires * 1e3) : null;
11980
11999
  const sameSite = normalizeSameSite(cookie.sameSite);
11981
12000
  return {
11982
12001
  name,
11983
12002
  value: cookie.value,
11984
12003
  domain,
11985
- path: path17,
12004
+ path: path18,
11986
12005
  secure: cookie.secure,
11987
12006
  httpOnly: cookie.httpOnly,
11988
12007
  ...sameSite === void 0 ? {} : { sameSite },
@@ -12082,7 +12101,11 @@ var OpensteerCloudClient = class {
12082
12101
  ...input.browser === void 0 ? {} : { browser: input.browser },
12083
12102
  ...input.context === void 0 ? {} : { context: input.context },
12084
12103
  ...input.browserProfile === void 0 ? {} : { browserProfile: input.browserProfile },
12085
- ...input.observability === void 0 ? {} : { observability: input.observability }
12104
+ ...input.observability === void 0 ? {} : { observability: input.observability },
12105
+ ...input.sourceType === void 0 ? {} : { sourceType: input.sourceType },
12106
+ ...input.sourceRef === void 0 ? {} : { sourceRef: input.sourceRef },
12107
+ ...input.localWorkspaceRootPath === void 0 ? {} : { localWorkspaceRootPath: input.localWorkspaceRootPath },
12108
+ ...input.locality === void 0 ? {} : { locality: input.locality }
12086
12109
  }
12087
12110
  });
12088
12111
  return await response.json();
@@ -12108,6 +12131,30 @@ var OpensteerCloudClient = class {
12108
12131
  });
12109
12132
  return await response.json();
12110
12133
  }
12134
+ async getSessionRecording(sessionId) {
12135
+ const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}/recording`, {
12136
+ method: "GET"
12137
+ });
12138
+ return await response.json();
12139
+ }
12140
+ async startSessionRecording(sessionId) {
12141
+ const response = await this.request(
12142
+ `/v1/sessions/${encodeURIComponent(sessionId)}/recording/start`,
12143
+ {
12144
+ method: "POST"
12145
+ }
12146
+ );
12147
+ return await response.json();
12148
+ }
12149
+ async stopSessionRecording(sessionId) {
12150
+ const response = await this.request(
12151
+ `/v1/sessions/${encodeURIComponent(sessionId)}/recording/stop`,
12152
+ {
12153
+ method: "POST"
12154
+ }
12155
+ );
12156
+ return await response.json();
12157
+ }
12111
12158
  async closeSession(sessionId) {
12112
12159
  const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}`, {
12113
12160
  method: "DELETE"
@@ -12298,9 +12345,11 @@ function resolveCloudConfig(input = {}) {
12298
12345
  if (!baseUrl || baseUrl.trim().length === 0) {
12299
12346
  throw new Error("provider=cloud requires OPENSTEER_BASE_URL or provider.baseUrl.");
12300
12347
  }
12348
+ const appBaseUrl = cloudProvider?.appBaseUrl ?? input.environment?.OPENSTEER_CLOUD_APP_BASE_URL;
12301
12349
  return {
12302
12350
  apiKey: apiKey.trim(),
12303
12351
  baseUrl: baseUrl.trim().replace(/\/+$/, ""),
12352
+ ...appBaseUrl === void 0 || appBaseUrl.trim().length === 0 ? {} : { appBaseUrl: appBaseUrl.trim().replace(/\/+$/, "") },
12304
12353
  ...cloudProvider?.browserProfile === void 0 ? {} : { browserProfile: cloudProvider.browserProfile }
12305
12354
  };
12306
12355
  }
@@ -12315,15 +12364,7 @@ var OPENSTEER_RUNTIME_CORE_VERSION = package_default2.version;
12315
12364
  // ../runtime-core/src/action-boundary.ts
12316
12365
  var actionBoundaryDiagnosticsBySignal = /* @__PURE__ */ new WeakMap();
12317
12366
  async function captureActionBoundarySnapshot(engine, pageRef) {
12318
- const frames = await engine.listFrames({ pageRef });
12319
- const mainFrame = frames.find((frame) => frame.isMainFrame);
12320
- if (!mainFrame) {
12321
- throw new Error(`page ${pageRef} does not expose a main frame`);
12322
- }
12323
- return {
12324
- pageRef,
12325
- documentRef: mainFrame.documentRef
12326
- };
12367
+ return engine.getActionBoundarySnapshot({ pageRef });
12327
12368
  }
12328
12369
  function createActionBoundaryDiagnostics(input) {
12329
12370
  return {
@@ -12440,6 +12481,7 @@ var NAVIGATION_VISUAL_STABILITY_PROFILE = {
12440
12481
  scope: "visible-frames",
12441
12482
  timeoutMs: 7e3
12442
12483
  };
12484
+ var NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS = 1e3;
12443
12485
  var defaultDomActionSettleObserver = {
12444
12486
  async settle(input) {
12445
12487
  if (input.trigger !== "dom-action") {
@@ -12475,6 +12517,13 @@ var defaultNavigationSettleObserver = {
12475
12517
  return false;
12476
12518
  }
12477
12519
  try {
12520
+ await input.engine.waitForPostLoadQuiet({
12521
+ pageRef: input.pageRef,
12522
+ timeoutMs: effectiveTimeout,
12523
+ quietMs: DEFAULT_POST_LOAD_TRACKER_QUIET_WINDOW_MS,
12524
+ captureWindowMs: Math.min(NAVIGATION_POST_LOAD_CAPTURE_WINDOW_MS, effectiveTimeout),
12525
+ signal: input.signal
12526
+ });
12478
12527
  await input.engine.waitForVisualStability({
12479
12528
  pageRef: input.pageRef,
12480
12529
  timeoutMs: effectiveTimeout,
@@ -13017,9 +13066,9 @@ var IFRAME_URL_ATTRIBUTES = /* @__PURE__ */ new Set([
13017
13066
  "poster",
13018
13067
  "ping"
13019
13068
  ]);
13020
- function buildArrayFieldPathCandidates(path17) {
13021
- const strict = path17.nodes.length ? buildPathCandidates(path17.nodes) : [];
13022
- const relaxedNodes = stripPositionClauses(path17.nodes);
13069
+ function buildArrayFieldPathCandidates(path18) {
13070
+ const strict = path18.nodes.length ? buildPathCandidates(path18.nodes) : [];
13071
+ const relaxedNodes = stripPositionClauses(path18.nodes);
13023
13072
  const relaxed = relaxedNodes.length ? buildPathCandidates(relaxedNodes) : [];
13024
13073
  return dedupeSelectors([...strict, ...relaxed]);
13025
13074
  }
@@ -13546,8 +13595,8 @@ function cloneStructuralElementAnchor(anchor) {
13546
13595
  nodes: anchor.nodes.map(clonePathNode)
13547
13596
  };
13548
13597
  }
13549
- function buildPathSelectorHint(path17) {
13550
- const nodes = path17?.nodes || [];
13598
+ function buildPathSelectorHint(path18) {
13599
+ const nodes = path18?.nodes || [];
13551
13600
  const last = nodes[nodes.length - 1];
13552
13601
  if (!last) {
13553
13602
  return "*";
@@ -13596,15 +13645,15 @@ function sanitizeStructuralElementAnchor(anchor) {
13596
13645
  nodes: sanitizeNodes(anchor.nodes)
13597
13646
  };
13598
13647
  }
13599
- function sanitizeReplayElementPath(path17) {
13648
+ function sanitizeReplayElementPath(path18) {
13600
13649
  return {
13601
13650
  resolution: "deterministic",
13602
- context: sanitizeContext(path17.context),
13603
- nodes: sanitizeNodes(path17.nodes)
13651
+ context: sanitizeContext(path18.context),
13652
+ nodes: sanitizeNodes(path18.nodes)
13604
13653
  };
13605
13654
  }
13606
- function sanitizeElementPath(path17) {
13607
- return sanitizeReplayElementPath(path17);
13655
+ function sanitizeElementPath(path18) {
13656
+ return sanitizeReplayElementPath(path18);
13608
13657
  }
13609
13658
  function buildLocalStructuralElementAnchor(index, rawTargetNode) {
13610
13659
  const targetNode = requireElementNode(index, rawTargetNode);
@@ -13727,8 +13776,8 @@ function buildTargetNotFoundMessage(domPath, diagnostics) {
13727
13776
  }
13728
13777
  return `${base} Target depth ${String(depth)}. Candidate counts: ${sample}.`;
13729
13778
  }
13730
- function buildArrayFieldCandidates(path17) {
13731
- return buildArrayFieldPathCandidates(path17);
13779
+ function buildArrayFieldCandidates(path18) {
13780
+ return buildArrayFieldPathCandidates(path18);
13732
13781
  }
13733
13782
  function firstDefinedAttribute(node, keys) {
13734
13783
  for (const key of keys) {
@@ -15205,21 +15254,21 @@ var DefaultDomRuntime = class {
15205
15254
  return match;
15206
15255
  }
15207
15256
  async resolvePathTarget(session, pageRef, rawPath, source, description, descriptor) {
15208
- const path17 = sanitizeReplayElementPath(rawPath);
15209
- const context = await this.resolvePathContext(session, pageRef, path17.context);
15210
- const target = resolveDomPathInScope(context.index, path17.nodes, context.scope);
15257
+ const path18 = sanitizeReplayElementPath(rawPath);
15258
+ const context = await this.resolvePathContext(session, pageRef, path18.context);
15259
+ const target = resolveDomPathInScope(context.index, path18.nodes, context.scope);
15211
15260
  if (!target) {
15212
- throwTargetNotFound(context.index, path17.nodes, context.scope);
15261
+ throwTargetNotFound(context.index, path18.nodes, context.scope);
15213
15262
  }
15214
15263
  if (target.node.nodeRef === void 0) {
15215
15264
  throw new Error(
15216
- `resolved path "${buildPathSelectorHint(path17)}" does not point to a live element`
15265
+ `resolved path "${buildPathSelectorHint(path18)}" does not point to a live element`
15217
15266
  );
15218
15267
  }
15219
15268
  const anchor = await this.buildAnchorFromSnapshotNode(session, context.snapshot, target.node);
15220
15269
  return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
15221
15270
  ...description === void 0 ? {} : { description },
15222
- replayPath: path17,
15271
+ replayPath: path18,
15223
15272
  ...source === "path" || source === "descriptor" ? { selectorUsed: target.selector } : {},
15224
15273
  ...descriptor === void 0 ? {} : { descriptor }
15225
15274
  });
@@ -15240,9 +15289,9 @@ var DefaultDomRuntime = class {
15240
15289
  });
15241
15290
  }
15242
15291
  async queryAllByElementPath(session, pageRef, rawPath) {
15243
- const path17 = sanitizeReplayElementPath(rawPath);
15244
- const context = await this.resolvePathContext(session, pageRef, path17.context);
15245
- return queryAllDomPathInScope(context.index, path17.nodes, context.scope).filter(
15292
+ const path18 = sanitizeReplayElementPath(rawPath);
15293
+ const context = await this.resolvePathContext(session, pageRef, path18.context);
15294
+ return queryAllDomPathInScope(context.index, path18.nodes, context.scope).filter(
15246
15295
  (node) => node.nodeRef !== void 0
15247
15296
  ).map((node) => this.createSnapshotTarget(context.snapshot, node));
15248
15297
  }
@@ -15428,16 +15477,16 @@ var DefaultDomRuntime = class {
15428
15477
  const index = createSnapshotIndex(item.snapshot);
15429
15478
  return this.resolveFirstArrayFieldTargetInNode(index, item.node, field.path);
15430
15479
  }
15431
- resolveFirstArrayFieldTargetInNode(index, rootNode, path17) {
15432
- const normalizedPath = sanitizeElementPath(path17);
15480
+ resolveFirstArrayFieldTargetInNode(index, rootNode, path18) {
15481
+ const normalizedPath = sanitizeElementPath(path18);
15433
15482
  const selectors = buildArrayFieldCandidates(normalizedPath);
15434
15483
  if (!selectors.length) {
15435
15484
  return rootNode;
15436
15485
  }
15437
15486
  return resolveFirstWithinNodeBySelectors(index, rootNode, selectors);
15438
15487
  }
15439
- resolveUniqueArrayFieldTargetInNode(index, rootNode, path17) {
15440
- const normalizedPath = sanitizeElementPath(path17);
15488
+ resolveUniqueArrayFieldTargetInNode(index, rootNode, path18) {
15489
+ const normalizedPath = sanitizeElementPath(path18);
15441
15490
  const selectors = buildArrayFieldCandidates(normalizedPath);
15442
15491
  if (!selectors.length) {
15443
15492
  return rootNode;
@@ -16813,7 +16862,7 @@ function diffStringMap(prefix, left, right, includeUnchanged, output) {
16813
16862
  diffScalarField(`${prefix}.${key}`, left[key], right[key], includeUnchanged, output);
16814
16863
  }
16815
16864
  }
16816
- function diffScalarField(path17, left, right, includeUnchanged, output) {
16865
+ function diffScalarField(path18, left, right, includeUnchanged, output) {
16817
16866
  const leftValue = stringifyFieldValue(left);
16818
16867
  const rightValue = stringifyFieldValue(right);
16819
16868
  const kind = leftValue === void 0 ? rightValue === void 0 ? "unchanged" : "added" : rightValue === void 0 ? "removed" : leftValue === rightValue ? "unchanged" : "changed";
@@ -16821,7 +16870,7 @@ function diffScalarField(path17, left, right, includeUnchanged, output) {
16821
16870
  return;
16822
16871
  }
16823
16872
  output.push({
16824
- path: path17,
16873
+ path: path18,
16825
16874
  kind,
16826
16875
  ...leftValue === void 0 ? {} : { leftValue },
16827
16876
  ...rightValue === void 0 ? {} : { rightValue },
@@ -17827,9 +17876,9 @@ function matchReverseTargetHints(channel, codec, targetHints) {
17827
17876
  matches.add(`host:${host}`);
17828
17877
  }
17829
17878
  }
17830
- for (const path17 of targetHints.paths ?? []) {
17831
- if (url.pathname.includes(path17)) {
17832
- matches.add(`path:${path17}`);
17879
+ for (const path18 of targetHints.paths ?? []) {
17880
+ if (url.pathname.includes(path18)) {
17881
+ matches.add(`path:${path18}`);
17833
17882
  }
17834
17883
  }
17835
17884
  for (const operationName of targetHints.operationNames ?? []) {
@@ -19665,8 +19714,8 @@ function encodeDataPath(tokens) {
19665
19714
  }
19666
19715
  return out;
19667
19716
  }
19668
- function parseDataPath(path17) {
19669
- const input = path17.trim();
19717
+ function parseDataPath(path18) {
19718
+ const input = path18.trim();
19670
19719
  if (input.length === 0) {
19671
19720
  return [];
19672
19721
  }
@@ -19716,8 +19765,8 @@ function parseDataPath(path17) {
19716
19765
  function inflateDataPathObject(flat) {
19717
19766
  let root = {};
19718
19767
  let initialized = false;
19719
- for (const [path17, value] of Object.entries(flat)) {
19720
- const tokens = parseDataPath(path17);
19768
+ for (const [path18, value] of Object.entries(flat)) {
19769
+ const tokens = parseDataPath(path18);
19721
19770
  if (!tokens || tokens.length === 0) {
19722
19771
  continue;
19723
19772
  }
@@ -20049,8 +20098,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
20049
20098
  fields: mergedFields
20050
20099
  };
20051
20100
  }
20052
- function minimizePathMatchClauses(path17, mode) {
20053
- const normalized = sanitizeElementPath(path17);
20101
+ function minimizePathMatchClauses(path18, mode) {
20102
+ const normalized = sanitizeElementPath(path18);
20054
20103
  const nodes = normalized.nodes.map((node, index) => {
20055
20104
  const isLast = index === normalized.nodes.length - 1;
20056
20105
  const attrs = node.attrs || {};
@@ -20154,8 +20203,8 @@ function seedMinimalAttrClause(attrs) {
20154
20203
  }
20155
20204
  return null;
20156
20205
  }
20157
- function relaxPathForSingleSample(path17, mode) {
20158
- const normalized = sanitizeElementPath(path17);
20206
+ function relaxPathForSingleSample(path18, mode) {
20207
+ const normalized = sanitizeElementPath(path18);
20159
20208
  const relaxedNodes = normalized.nodes.map((node, index) => {
20160
20209
  const isLast = index === normalized.nodes.length - 1;
20161
20210
  const attrs = normalizeAttrsForSingleSample(node.attrs || {});
@@ -20240,8 +20289,8 @@ function shouldKeepAttrForSingleSample(key) {
20240
20289
  }
20241
20290
  return true;
20242
20291
  }
20243
- function buildPathStructureKey(path17) {
20244
- const normalized = sanitizeElementPath(path17);
20292
+ function buildPathStructureKey(path18) {
20293
+ const normalized = sanitizeElementPath(path18);
20245
20294
  return canonicalJsonString({
20246
20295
  context: (normalized.context || []).map((hop) => ({
20247
20296
  kind: hop.kind,
@@ -20368,30 +20417,30 @@ function buildArrayItemNode(fields) {
20368
20417
  }
20369
20418
  return node;
20370
20419
  }
20371
- function insertNodeAtPath(root, path17, node) {
20372
- const tokens = parseDataPath(path17);
20420
+ function insertNodeAtPath(root, path18, node) {
20421
+ const tokens = parseDataPath(path18);
20373
20422
  if (!tokens || !tokens.length) {
20374
20423
  throw new Error(
20375
- `Invalid persisted extraction path "${path17}": expected a non-empty object path.`
20424
+ `Invalid persisted extraction path "${path18}": expected a non-empty object path.`
20376
20425
  );
20377
20426
  }
20378
20427
  if (tokens.some((token) => token.kind === "index")) {
20379
20428
  throw new Error(
20380
- `Invalid persisted extraction path "${path17}": nested array indices are not supported in cached descriptors.`
20429
+ `Invalid persisted extraction path "${path18}": nested array indices are not supported in cached descriptors.`
20381
20430
  );
20382
20431
  }
20383
20432
  let current = root;
20384
20433
  for (let index = 0; index < tokens.length; index += 1) {
20385
20434
  const token = tokens[index];
20386
20435
  if (!token || token.kind !== "prop") {
20387
- throw new Error(`Invalid persisted extraction path "${path17}": expected object segment.`);
20436
+ throw new Error(`Invalid persisted extraction path "${path18}": expected object segment.`);
20388
20437
  }
20389
20438
  const isLast = index === tokens.length - 1;
20390
20439
  if (isLast) {
20391
20440
  const existing = current[token.key];
20392
20441
  if (existing) {
20393
20442
  throw new Error(
20394
- `Conflicting persisted extraction path "${path17}" detected while building descriptor tree.`
20443
+ `Conflicting persisted extraction path "${path18}" detected while building descriptor tree.`
20395
20444
  );
20396
20445
  }
20397
20446
  current[token.key] = node;
@@ -20406,7 +20455,7 @@ function insertNodeAtPath(root, path17, node) {
20406
20455
  }
20407
20456
  if (!isPersistedObjectNode(next)) {
20408
20457
  throw new Error(
20409
- `Conflicting persisted extraction path "${path17}" detected at "${token.key}".`
20458
+ `Conflicting persisted extraction path "${path18}" detected at "${token.key}".`
20410
20459
  );
20411
20460
  }
20412
20461
  current = next;
@@ -20441,7 +20490,7 @@ function buildItemRootForArrayIndex(entries) {
20441
20490
  }
20442
20491
  const paths = entries.map(
20443
20492
  (entry) => isPersistablePathField(entry.source) ? sanitizeElementPath(entry.source.path) : null
20444
- ).filter((path17) => path17 !== null);
20493
+ ).filter((path18) => path18 !== null);
20445
20494
  if (!paths.length) {
20446
20495
  return null;
20447
20496
  }
@@ -20462,7 +20511,7 @@ function getCommonPathPrefixLength(paths) {
20462
20511
  if (!paths.length) {
20463
20512
  return 0;
20464
20513
  }
20465
- const nodeChains = paths.map((path17) => path17.nodes);
20514
+ const nodeChains = paths.map((path18) => path18.nodes);
20466
20515
  const minLength = Math.min(...nodeChains.map((nodes) => nodes.length));
20467
20516
  if (!Number.isFinite(minLength) || minLength <= 0) {
20468
20517
  return 0;
@@ -20531,30 +20580,30 @@ function mergeElementPathsByMajority(paths) {
20531
20580
  if (!paths.length) {
20532
20581
  return null;
20533
20582
  }
20534
- const normalized = paths.map((path17) => sanitizeElementPath(path17));
20583
+ const normalized = paths.map((path18) => sanitizeElementPath(path18));
20535
20584
  const contextKey = pickModeString(
20536
- normalized.map((path17) => canonicalJsonString(path17.context)),
20585
+ normalized.map((path18) => canonicalJsonString(path18.context)),
20537
20586
  1
20538
20587
  );
20539
20588
  if (!contextKey) {
20540
20589
  return null;
20541
20590
  }
20542
- const sameContext = normalized.filter((path17) => canonicalJsonString(path17.context) === contextKey);
20591
+ const sameContext = normalized.filter((path18) => canonicalJsonString(path18.context) === contextKey);
20543
20592
  if (!sameContext.length) {
20544
20593
  return null;
20545
20594
  }
20546
20595
  const targetLength = pickModeNumber(
20547
- sameContext.map((path17) => path17.nodes.length),
20596
+ sameContext.map((path18) => path18.nodes.length),
20548
20597
  1
20549
20598
  ) ?? sameContext[0]?.nodes.length ?? 0;
20550
- const aligned = sameContext.filter((path17) => path17.nodes.length === targetLength);
20599
+ const aligned = sameContext.filter((path18) => path18.nodes.length === targetLength);
20551
20600
  if (!aligned.length) {
20552
20601
  return null;
20553
20602
  }
20554
20603
  const threshold = majorityThreshold(aligned.length);
20555
20604
  const nodes = [];
20556
20605
  for (let index = 0; index < targetLength; index += 1) {
20557
- const nodesAtIndex = aligned.map((path17) => path17.nodes[index]).filter((node) => node !== void 0);
20606
+ const nodesAtIndex = aligned.map((path18) => path18.nodes[index]).filter((node) => node !== void 0);
20558
20607
  if (!nodesAtIndex.length) {
20559
20608
  return null;
20560
20609
  }
@@ -20800,8 +20849,8 @@ function clonePathContext(context) {
20800
20849
  function clonePathNodes(nodes) {
20801
20850
  return JSON.parse(JSON.stringify(nodes || []));
20802
20851
  }
20803
- function cloneElementPath2(path17) {
20804
- return JSON.parse(JSON.stringify(path17));
20852
+ function cloneElementPath2(path18) {
20853
+ return JSON.parse(JSON.stringify(path18));
20805
20854
  }
20806
20855
  function clonePersistedOpensteerExtractionNode(node) {
20807
20856
  return JSON.parse(JSON.stringify(node));
@@ -21119,8 +21168,8 @@ function collectPersistedValueNodeRefs(node) {
21119
21168
  return [
21120
21169
  {
21121
21170
  path: sanitizeElementPath(node.$path),
21122
- replacePath: (path17) => {
21123
- node.$path = sanitizeElementPath(path17);
21171
+ replacePath: (path18) => {
21172
+ node.$path = sanitizeElementPath(path18);
21124
21173
  }
21125
21174
  }
21126
21175
  ];
@@ -21134,13 +21183,13 @@ function collectPersistedValueNodeRefs(node) {
21134
21183
  }
21135
21184
  return refs;
21136
21185
  }
21137
- function hasPositionClause(path17) {
21138
- return path17.nodes.some((node) => node.match.some((clause) => clause.kind === "position"));
21186
+ function hasPositionClause(path18) {
21187
+ return path18.nodes.some((node) => node.match.some((clause) => clause.kind === "position"));
21139
21188
  }
21140
- function stripPositionClauses2(path17) {
21189
+ function stripPositionClauses2(path18) {
21141
21190
  return sanitizeElementPath({
21142
- context: path17.context,
21143
- nodes: path17.nodes.map((node) => ({
21191
+ context: path18.context,
21192
+ nodes: path18.nodes.map((node) => ({
21144
21193
  ...node,
21145
21194
  match: node.match.filter((clause) => clause.kind !== "position")
21146
21195
  }))
@@ -21550,8 +21599,8 @@ function normalizeNonEmptyString2(name, value) {
21550
21599
  function normalizeKey(value) {
21551
21600
  return String(value ?? "").trim();
21552
21601
  }
21553
- function labelForPath(path17) {
21554
- return path17.trim().length === 0 ? "$" : path17;
21602
+ function labelForPath(path18) {
21603
+ return path18.trim().length === 0 ? "$" : path18;
21555
21604
  }
21556
21605
  function sha256Hex3(value) {
21557
21606
  return crypto.createHash("sha256").update(value).digest("hex");
@@ -23255,11 +23304,11 @@ var SandboxClock = class {
23255
23304
  performanceNow() {
23256
23305
  return this.mode === "manual" ? this.manualNow - this.startedAt : (globalThis.performance?.now() ?? 0) - this.performanceStartedAt;
23257
23306
  }
23258
- setTimeout(callback, delay2 = 0, ...args) {
23259
- return this.registerTimer(false, callback, delay2, args);
23307
+ setTimeout(callback, delay4 = 0, ...args) {
23308
+ return this.registerTimer(false, callback, delay4, args);
23260
23309
  }
23261
- setInterval(callback, delay2 = 0, ...args) {
23262
- return this.registerTimer(true, callback, delay2, args);
23310
+ setInterval(callback, delay4 = 0, ...args) {
23311
+ return this.registerTimer(true, callback, delay4, args);
23263
23312
  }
23264
23313
  clearTimeout(timerId) {
23265
23314
  this.clearTimer(timerId);
@@ -23280,9 +23329,9 @@ var SandboxClock = class {
23280
23329
  this.clearTimer(timerId);
23281
23330
  }
23282
23331
  }
23283
- registerTimer(repeat, callback, delay2, args) {
23332
+ registerTimer(repeat, callback, delay4, args) {
23284
23333
  const timerId = this.nextTimerId++;
23285
- const normalizedDelay = Math.max(0, delay2);
23334
+ const normalizedDelay = Math.max(0, delay4);
23286
23335
  const record = {
23287
23336
  callback,
23288
23337
  args,
@@ -23789,6 +23838,7 @@ function diffInteractionTraces(left, right) {
23789
23838
  // ../runtime-core/src/sdk/runtime.ts
23790
23839
  var requireForAuthRecipeHook = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href)));
23791
23840
  var MUTATION_CAPTURE_FINALIZE_TIMEOUT_MS = 5e3;
23841
+ var PERSISTED_NETWORK_FLUSH_TIMEOUT_MS = 5e3;
23792
23842
  var PENDING_OPERATION_EVENT_CAPTURE_LIMIT = 64;
23793
23843
  var PENDING_OPERATION_EVENT_CAPTURE_SKEW_MS = 1e3;
23794
23844
  var OpensteerSessionRuntime = class {
@@ -24452,6 +24502,9 @@ var OpensteerSessionRuntime = class {
24452
24502
  const result = await this.requireDom().click({
24453
24503
  pageRef,
24454
24504
  target,
24505
+ ...input.button === void 0 ? {} : { button: input.button },
24506
+ ...input.clickCount === void 0 ? {} : { clickCount: input.clickCount },
24507
+ ...input.modifiers === void 0 ? {} : { modifiers: input.modifiers },
24455
24508
  timeout
24456
24509
  });
24457
24510
  return {
@@ -29928,6 +29981,29 @@ var OpensteerSessionRuntime = class {
29928
29981
  return snapshot.sessionStorage?.filter((entry) => entry.origin === origin).find((entry) => pageUrl === void 0 || entry.origin === new URL(pageUrl).origin)?.entries.find((entry) => entry.key === key)?.value;
29929
29982
  }
29930
29983
  async flushPersistedNetworkHistory() {
29984
+ if (this.sessionRef === void 0) {
29985
+ return;
29986
+ }
29987
+ const root = await this.ensureRoot();
29988
+ try {
29989
+ await withDetachedTimeoutSignal(PERSISTED_NETWORK_FLUSH_TIMEOUT_MS, async (signal) => {
29990
+ const browserRecords = await this.readBrowserNetworkRecords(
29991
+ {
29992
+ includeBodies: true,
29993
+ includeCurrentPageOnly: false
29994
+ },
29995
+ signal
29996
+ );
29997
+ await this.networkHistory.persist(browserRecords, root.registry.savedNetwork, {
29998
+ bodyWriteMode: "authoritative",
29999
+ redactSecretHeaders: false
30000
+ });
30001
+ });
30002
+ } catch (error) {
30003
+ if (!isIgnorableRuntimeBindingError(error)) {
30004
+ throw error;
30005
+ }
30006
+ }
29931
30007
  }
29932
30008
  toDomTargetRef(target) {
29933
30009
  if (target.kind === "description") {
@@ -30084,6 +30160,12 @@ var OpensteerSessionRuntime = class {
30084
30160
  return "live";
30085
30161
  } catch (error) {
30086
30162
  if (isIgnorableRuntimeBindingError(error)) {
30163
+ const remainingPages = await engine.listPages({ sessionRef }).catch(() => void 0);
30164
+ const replacementPageRef = remainingPages?.[0]?.pageRef;
30165
+ if (replacementPageRef !== void 0) {
30166
+ this.pageRef = replacementPageRef;
30167
+ return "live";
30168
+ }
30087
30169
  return "invalid";
30088
30170
  }
30089
30171
  throw error;
@@ -31439,12 +31521,12 @@ function extractReverseRuntimeValue(value, pointer) {
31439
31521
  }
31440
31522
  return readDotPath(value, pointer);
31441
31523
  }
31442
- function readDotPath(value, path17) {
31443
- if (path17.length === 0) {
31524
+ function readDotPath(value, path18) {
31525
+ if (path18.length === 0) {
31444
31526
  return value;
31445
31527
  }
31446
31528
  let current = value;
31447
- for (const segment of path17.split(".").filter((entry) => entry.length > 0)) {
31529
+ for (const segment of path18.split(".").filter((entry) => entry.length > 0)) {
31448
31530
  if (current === null || current === void 0) {
31449
31531
  return void 0;
31450
31532
  }
@@ -31917,7 +31999,7 @@ function parseSetCookieHeader(value, requestUrl) {
31917
31999
  }
31918
32000
  const url = new URL(requestUrl);
31919
32001
  let domain = url.hostname;
31920
- let path17 = defaultCookiePath(url.pathname);
32002
+ let path18 = defaultCookiePath(url.pathname);
31921
32003
  let secure = url.protocol === "https:";
31922
32004
  let expiresAt;
31923
32005
  const cookieValue = rawValueParts.join("=").trim();
@@ -31930,7 +32012,7 @@ function parseSetCookieHeader(value, requestUrl) {
31930
32012
  continue;
31931
32013
  }
31932
32014
  if (key === "path" && attributeValue.length > 0) {
31933
- path17 = attributeValue;
32015
+ path18 = attributeValue;
31934
32016
  continue;
31935
32017
  }
31936
32018
  if (key === "secure") {
@@ -31956,7 +32038,7 @@ function parseSetCookieHeader(value, requestUrl) {
31956
32038
  name,
31957
32039
  value: cookieValue,
31958
32040
  domain,
31959
- path: path17,
32041
+ path: path18,
31960
32042
  secure,
31961
32043
  ...expiresAt === void 0 ? {} : { expiresAt }
31962
32044
  }
@@ -33137,6 +33219,1770 @@ function screenshotMediaType(format2) {
33137
33219
  return "image/webp";
33138
33220
  }
33139
33221
  }
33222
+
33223
+ // ../runtime-core/src/recorder/browser-scripts.ts
33224
+ var SINGLE_ATTRIBUTE_PRIORITY = Array.from(
33225
+ /* @__PURE__ */ new Set(["data-testid", "data-test", "data-qa", "data-cy", "id", ...STABLE_PRIMARY_ATTR_KEYS])
33226
+ );
33227
+ function createFlowRecorderInstallScript(options = {}) {
33228
+ const normalizedOptions = {
33229
+ showStopButton: options.showStopButton ?? true
33230
+ };
33231
+ return String.raw`(() => {
33232
+ const TOP_LEVEL_ONLY = (() => {
33233
+ try {
33234
+ return window.top === window.self;
33235
+ } catch {
33236
+ return false;
33237
+ }
33238
+ })();
33239
+ if (!TOP_LEVEL_ONLY) {
33240
+ return;
33241
+ }
33242
+
33243
+ const globalScope = globalThis;
33244
+ const recorderKey = "__opensteerFlowRecorder";
33245
+ const historyStateKey = "__opensteerFlowRecorderHistory";
33246
+ const recorderUiAttribute = "data-opensteer-recorder-ui";
33247
+ const queueLimit = 1000;
33248
+ const singleAttributePriority = ${JSON.stringify(SINGLE_ATTRIBUTE_PRIORITY)};
33249
+ const stablePrimaryAttrKeys = new Set(${JSON.stringify([...STABLE_PRIMARY_ATTR_KEYS])});
33250
+ const matchAttributePriority = ${JSON.stringify([...MATCH_ATTRIBUTE_PRIORITY])};
33251
+ const attributeDenyKeys = new Set(${JSON.stringify([...ATTRIBUTE_DENY_KEYS])});
33252
+ const lazyLoadingMediaTags = new Set(${JSON.stringify([...LAZY_LOADING_MEDIA_TAGS])});
33253
+ const volatileLazyLoadingAttrs = new Set(${JSON.stringify([...VOLATILE_LAZY_LOADING_ATTRS])});
33254
+ const volatileClassTokens = new Set(${JSON.stringify([...VOLATILE_CLASS_TOKENS])});
33255
+ const volatileLazyClassTokens = new Set(${JSON.stringify([...VOLATILE_LAZY_CLASS_TOKENS])});
33256
+ const options = ${JSON.stringify(normalizedOptions)};
33257
+
33258
+ const previous = globalScope[recorderKey];
33259
+ if (previous && typeof previous.dispose === "function") {
33260
+ previous.dispose();
33261
+ }
33262
+
33263
+ const queue = [];
33264
+ const cleanup = [];
33265
+ const inputFlushTimers = new Map();
33266
+ const pendingInputs = new Map();
33267
+ let historyStateCache = undefined;
33268
+ let stopRequested = false;
33269
+ let pendingWheel = undefined;
33270
+
33271
+ const actionTargetTags = new Set([
33272
+ "a",
33273
+ "button",
33274
+ "input",
33275
+ "label",
33276
+ "option",
33277
+ "select",
33278
+ "summary",
33279
+ "textarea",
33280
+ ]);
33281
+
33282
+ function now() {
33283
+ return Date.now();
33284
+ }
33285
+
33286
+ function enqueue(entry) {
33287
+ queue.push({
33288
+ ...entry,
33289
+ timestamp: typeof entry.timestamp === "number" ? entry.timestamp : now(),
33290
+ });
33291
+ if (queue.length > queueLimit) {
33292
+ queue.splice(0, queue.length - queueLimit);
33293
+ }
33294
+ }
33295
+
33296
+ function isRecorderUiNode(node) {
33297
+ let current = node instanceof Node ? node : null;
33298
+ while (current) {
33299
+ if (current instanceof Element && current.hasAttribute(recorderUiAttribute)) {
33300
+ return true;
33301
+ }
33302
+ const root = typeof current.getRootNode === "function" ? current.getRootNode() : null;
33303
+ if (root instanceof ShadowRoot) {
33304
+ current = root.host;
33305
+ continue;
33306
+ }
33307
+ current = current instanceof Element ? current.parentElement : null;
33308
+ }
33309
+ return false;
33310
+ }
33311
+
33312
+ function isValidAttributeName(name) {
33313
+ if (typeof name !== "string") {
33314
+ return false;
33315
+ }
33316
+ const normalized = name.trim();
33317
+ if (normalized.length === 0) {
33318
+ return false;
33319
+ }
33320
+ if (/[\s"'<>/]/.test(normalized)) {
33321
+ return false;
33322
+ }
33323
+ return /^[A-Za-z_][A-Za-z0-9_:\-.]*$/.test(normalized);
33324
+ }
33325
+
33326
+ function escapeAttributeValue(value) {
33327
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
33328
+ }
33329
+
33330
+ function escapeIdentifier(value) {
33331
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
33332
+ return CSS.escape(value);
33333
+ }
33334
+ return String(value).replace(/[^A-Za-z0-9_-]/g, (character) => {
33335
+ const codePoint = character.codePointAt(0);
33336
+ return "\\" + (codePoint == null ? "" : codePoint.toString(16)) + " ";
33337
+ });
33338
+ }
33339
+
33340
+ function normalizeClassValue(element, rawValue) {
33341
+ const tag = element.tagName.toLowerCase();
33342
+ const tokens = String(rawValue)
33343
+ .split(/\s+/u)
33344
+ .map((token) => token.trim())
33345
+ .filter(Boolean)
33346
+ .filter((token) => !volatileClassTokens.has(token))
33347
+ .filter((token) => !(lazyLoadingMediaTags.has(tag) && volatileLazyClassTokens.has(token)));
33348
+ return tokens.join(" ");
33349
+ }
33350
+
33351
+ function shouldKeepAttribute(element, name, value) {
33352
+ const key = String(name || "")
33353
+ .trim()
33354
+ .toLowerCase();
33355
+ if (!key || !String(value || "").trim()) {
33356
+ return false;
33357
+ }
33358
+ if (!isValidAttributeName(key)) {
33359
+ return false;
33360
+ }
33361
+ if (key === "c") {
33362
+ return false;
33363
+ }
33364
+ if (/^on[a-z]/i.test(key)) {
33365
+ return false;
33366
+ }
33367
+ if (attributeDenyKeys.has(key)) {
33368
+ return false;
33369
+ }
33370
+ if (key.startsWith("data-os-") || key.startsWith("data-opensteer-")) {
33371
+ return false;
33372
+ }
33373
+ if (lazyLoadingMediaTags.has(element.tagName.toLowerCase()) && volatileLazyLoadingAttrs.has(key)) {
33374
+ return false;
33375
+ }
33376
+ return true;
33377
+ }
33378
+
33379
+ function readAttributeValue(element, key) {
33380
+ if (key === "class") {
33381
+ const normalized = normalizeClassValue(element, element.getAttribute("class") || "");
33382
+ return normalized.length === 0 ? undefined : normalized;
33383
+ }
33384
+ const value = element.getAttribute(key);
33385
+ if (!shouldKeepAttribute(element, key, value || "")) {
33386
+ return undefined;
33387
+ }
33388
+ return value || undefined;
33389
+ }
33390
+
33391
+ function buildSingleAttributeSelector(element, key, value) {
33392
+ if (!value) {
33393
+ return undefined;
33394
+ }
33395
+ const tag = element.tagName.toLowerCase();
33396
+ if (key === "id") {
33397
+ const idSelector = "#" + escapeIdentifier(value);
33398
+ return element.matches(idSelector)
33399
+ ? idSelector
33400
+ : tag + '[id="' + escapeAttributeValue(value) + '"]';
33401
+ }
33402
+ return tag + "[" + key + '="' + escapeAttributeValue(value) + '"]';
33403
+ }
33404
+
33405
+ function isUniqueSelector(selector, element) {
33406
+ if (!selector) {
33407
+ return false;
33408
+ }
33409
+ let matches;
33410
+ try {
33411
+ matches = document.querySelectorAll(selector);
33412
+ } catch {
33413
+ return false;
33414
+ }
33415
+ return matches.length === 1 && matches[0] === element;
33416
+ }
33417
+
33418
+ function nearestRecordTarget(node) {
33419
+ if (isRecorderUiNode(node)) {
33420
+ return null;
33421
+ }
33422
+ let current = node instanceof Element ? node : null;
33423
+ while (current) {
33424
+ if (current.hasAttribute(recorderUiAttribute)) {
33425
+ return null;
33426
+ }
33427
+ const tag = current.tagName.toLowerCase();
33428
+ if (
33429
+ actionTargetTags.has(tag) ||
33430
+ current.hasAttribute("data-testid") ||
33431
+ current.hasAttribute("data-test") ||
33432
+ current.hasAttribute("data-qa") ||
33433
+ current.hasAttribute("data-cy") ||
33434
+ current.hasAttribute("role") ||
33435
+ current.hasAttribute("aria-label") ||
33436
+ current.hasAttribute("name")
33437
+ ) {
33438
+ return current;
33439
+ }
33440
+ current = current.parentElement;
33441
+ }
33442
+ return node instanceof Element ? node : null;
33443
+ }
33444
+
33445
+ function buildSegmentSelector(element) {
33446
+ const tag = element.tagName.toLowerCase();
33447
+ for (const key of matchAttributePriority) {
33448
+ const value = readAttributeValue(element, key);
33449
+ if (!value) {
33450
+ continue;
33451
+ }
33452
+ if (key === "class") {
33453
+ const tokens = value
33454
+ .split(/\s+/u)
33455
+ .map((token) => token.trim())
33456
+ .filter(Boolean)
33457
+ .slice(0, 2);
33458
+ if (tokens.length === 0) {
33459
+ continue;
33460
+ }
33461
+ return tag + tokens.map((token) => "." + escapeIdentifier(token)).join("");
33462
+ }
33463
+ return key === "id"
33464
+ ? tag + '[id="' + escapeAttributeValue(value) + '"]'
33465
+ : tag + "[" + key + '="' + escapeAttributeValue(value) + '"]';
33466
+ }
33467
+ return tag;
33468
+ }
33469
+
33470
+ function nthOfTypeSegment(element, baseSelector) {
33471
+ const parent = element.parentElement;
33472
+ if (!parent) {
33473
+ return baseSelector;
33474
+ }
33475
+ const sameType = Array.from(parent.children).filter(
33476
+ (child) => child.tagName === element.tagName,
33477
+ );
33478
+ if (sameType.length <= 1) {
33479
+ return baseSelector;
33480
+ }
33481
+ const index = sameType.indexOf(element) + 1;
33482
+ return baseSelector + ":nth-of-type(" + String(index) + ")";
33483
+ }
33484
+
33485
+ function buildSelector(node) {
33486
+ const element = nearestRecordTarget(node);
33487
+ if (!(element instanceof Element)) {
33488
+ return undefined;
33489
+ }
33490
+
33491
+ for (const key of singleAttributePriority) {
33492
+ const value = readAttributeValue(element, key);
33493
+ const selector = buildSingleAttributeSelector(element, key, value);
33494
+ if (selector && isUniqueSelector(selector, element)) {
33495
+ return selector;
33496
+ }
33497
+ if (value && !stablePrimaryAttrKeys.has(key) && key !== "id") {
33498
+ const tagQualified =
33499
+ element.tagName.toLowerCase() + "[" + key + '="' + escapeAttributeValue(value) + '"]';
33500
+ if (isUniqueSelector(tagQualified, element)) {
33501
+ return tagQualified;
33502
+ }
33503
+ }
33504
+ }
33505
+
33506
+ const segments = [];
33507
+ let current = element;
33508
+ let depth = 0;
33509
+ while (current && depth < 6) {
33510
+ const segment = nthOfTypeSegment(current, buildSegmentSelector(current));
33511
+ segments.unshift(segment);
33512
+ const selector = segments.join(" > ");
33513
+ if (isUniqueSelector(selector, element)) {
33514
+ return selector;
33515
+ }
33516
+ current = current.parentElement;
33517
+ depth += 1;
33518
+ }
33519
+
33520
+ const fallback = segments.join(" > ");
33521
+ return fallback.length > 0 ? fallback : element.tagName.toLowerCase();
33522
+ }
33523
+
33524
+ function readTargetValue(target) {
33525
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
33526
+ return target.value;
33527
+ }
33528
+ if (target instanceof HTMLSelectElement) {
33529
+ return target.value;
33530
+ }
33531
+ if (target instanceof HTMLElement && target.isContentEditable) {
33532
+ return target.textContent || "";
33533
+ }
33534
+ return undefined;
33535
+ }
33536
+
33537
+ function flushPendingInput(selector) {
33538
+ const pending = pendingInputs.get(selector);
33539
+ if (!pending) {
33540
+ return;
33541
+ }
33542
+ pendingInputs.delete(selector);
33543
+ const timer = inputFlushTimers.get(selector);
33544
+ if (timer !== undefined) {
33545
+ clearTimeout(timer);
33546
+ inputFlushTimers.delete(selector);
33547
+ }
33548
+ enqueue({
33549
+ kind: "type",
33550
+ selector,
33551
+ text: pending.text,
33552
+ timestamp: pending.timestamp,
33553
+ });
33554
+ }
33555
+
33556
+ function flushAllInputs() {
33557
+ for (const selector of Array.from(pendingInputs.keys())) {
33558
+ flushPendingInput(selector);
33559
+ }
33560
+ }
33561
+
33562
+ function flushPendingWheel() {
33563
+ if (!pendingWheel) {
33564
+ return;
33565
+ }
33566
+ clearTimeout(pendingWheel.timerId);
33567
+ enqueue({
33568
+ kind: "scroll",
33569
+ selector: pendingWheel.selector,
33570
+ deltaX: pendingWheel.deltaX,
33571
+ deltaY: pendingWheel.deltaY,
33572
+ timestamp: pendingWheel.timestamp,
33573
+ });
33574
+ pendingWheel = undefined;
33575
+ }
33576
+
33577
+ function scheduleInputFlush(selector) {
33578
+ const existing = inputFlushTimers.get(selector);
33579
+ if (existing !== undefined) {
33580
+ clearTimeout(existing);
33581
+ }
33582
+ const timerId = setTimeout(() => {
33583
+ flushPendingInput(selector);
33584
+ }, 400);
33585
+ inputFlushTimers.set(selector, timerId);
33586
+ }
33587
+
33588
+ function createDefaultHistoryState(currentUrl) {
33589
+ return {
33590
+ entries: [currentUrl],
33591
+ index: 0,
33592
+ };
33593
+ }
33594
+
33595
+ function normalizeHistoryState(state) {
33596
+ if (
33597
+ !state ||
33598
+ !Array.isArray(state.entries) ||
33599
+ !state.entries.every((entry) => typeof entry === "string") ||
33600
+ !Number.isInteger(state.index)
33601
+ ) {
33602
+ return undefined;
33603
+ }
33604
+ const entries = state.entries.slice();
33605
+ if (entries.length === 0) {
33606
+ return createDefaultHistoryState(location.href);
33607
+ }
33608
+ return {
33609
+ entries,
33610
+ index: Math.min(entries.length - 1, Math.max(0, state.index)),
33611
+ };
33612
+ }
33613
+
33614
+ function writeHistoryState(state) {
33615
+ const normalized = normalizeHistoryState(state) ?? createDefaultHistoryState(location.href);
33616
+ historyStateCache = normalized;
33617
+ try {
33618
+ sessionStorage.setItem(historyStateKey, JSON.stringify(normalized));
33619
+ } catch {}
33620
+ return normalized;
33621
+ }
33622
+
33623
+ function applyHistoryState(state, mode, nextUrl) {
33624
+ const current = normalizeHistoryState(state) ?? createDefaultHistoryState(location.href);
33625
+ const nextState = {
33626
+ entries: current.entries.slice(),
33627
+ index: current.index,
33628
+ };
33629
+ if (mode === "replace") {
33630
+ nextState.entries[nextState.index] = nextUrl;
33631
+ } else if (mode === "push") {
33632
+ nextState.entries = nextState.entries.slice(0, nextState.index + 1);
33633
+ nextState.entries.push(nextUrl);
33634
+ nextState.index = nextState.entries.length - 1;
33635
+ } else if (mode === "back") {
33636
+ nextState.index = Math.max(0, nextState.index - 1);
33637
+ } else if (mode === "forward") {
33638
+ nextState.index = Math.min(nextState.entries.length - 1, nextState.index + 1);
33639
+ }
33640
+ return nextState;
33641
+ }
33642
+
33643
+ function updateHistoryState(mode, nextUrl) {
33644
+ return writeHistoryState(applyHistoryState(readHistoryState(), mode, nextUrl));
33645
+ }
33646
+
33647
+ function readHistoryState() {
33648
+ const cached = normalizeHistoryState(historyStateCache);
33649
+ if (cached) {
33650
+ historyStateCache = cached;
33651
+ return cached;
33652
+ }
33653
+ try {
33654
+ const raw = sessionStorage.getItem(historyStateKey);
33655
+ const parsed = normalizeHistoryState(raw ? JSON.parse(raw) : undefined);
33656
+ if (parsed) {
33657
+ historyStateCache = parsed;
33658
+ return parsed;
33659
+ }
33660
+ } catch {}
33661
+ return undefined;
33662
+ }
33663
+
33664
+ function classifyHistoryTraversal(currentUrl) {
33665
+ const state = readHistoryState();
33666
+ if (!state) {
33667
+ return undefined;
33668
+ }
33669
+ if (state.entries[state.index] === currentUrl) {
33670
+ return undefined;
33671
+ }
33672
+ if (state.entries[state.index - 1] === currentUrl) {
33673
+ writeHistoryState({
33674
+ entries: state.entries,
33675
+ index: state.index - 1,
33676
+ });
33677
+ return "go-back";
33678
+ }
33679
+ if (state.entries[state.index + 1] === currentUrl) {
33680
+ writeHistoryState({
33681
+ entries: state.entries,
33682
+ index: state.index + 1,
33683
+ });
33684
+ return "go-forward";
33685
+ }
33686
+ const existingIndex = state.entries.lastIndexOf(currentUrl);
33687
+ if (existingIndex !== -1 && existingIndex !== state.index) {
33688
+ writeHistoryState({
33689
+ entries: state.entries,
33690
+ index: existingIndex,
33691
+ });
33692
+ return existingIndex < state.index ? "go-back" : "go-forward";
33693
+ }
33694
+ return undefined;
33695
+ }
33696
+
33697
+ function onInstall() {
33698
+ const currentUrl = location.href;
33699
+ const navigationEntry =
33700
+ typeof performance.getEntriesByType === "function"
33701
+ ? performance.getEntriesByType("navigation")[0]
33702
+ : undefined;
33703
+ const navigationType =
33704
+ navigationEntry && typeof navigationEntry.type === "string" ? navigationEntry.type : undefined;
33705
+ const existingState = readHistoryState();
33706
+
33707
+ if (!existingState) {
33708
+ updateHistoryState("replace", currentUrl);
33709
+ return;
33710
+ }
33711
+
33712
+ if (navigationType === "reload") {
33713
+ updateHistoryState("replace", currentUrl);
33714
+ enqueue({
33715
+ kind: "reload",
33716
+ url: currentUrl,
33717
+ });
33718
+ return;
33719
+ }
33720
+
33721
+ if (navigationType === "back_forward") {
33722
+ const traversal = classifyHistoryTraversal(currentUrl);
33723
+ if (traversal === "go-back" || traversal === "go-forward") {
33724
+ enqueue({
33725
+ kind: traversal,
33726
+ url: currentUrl,
33727
+ });
33728
+ return;
33729
+ }
33730
+ }
33731
+
33732
+ if (existingState.entries[existingState.index] !== currentUrl) {
33733
+ updateHistoryState("push", currentUrl);
33734
+ enqueue({
33735
+ kind: "navigate",
33736
+ url: currentUrl,
33737
+ source: "full-navigation",
33738
+ });
33739
+ }
33740
+ }
33741
+
33742
+ function modifierKeys(event) {
33743
+ const modifiers = [];
33744
+ if (event.altKey) {
33745
+ modifiers.push("Alt");
33746
+ }
33747
+ if (event.ctrlKey) {
33748
+ modifiers.push("Control");
33749
+ }
33750
+ if (event.metaKey) {
33751
+ modifiers.push("Meta");
33752
+ }
33753
+ if (event.shiftKey) {
33754
+ modifiers.push("Shift");
33755
+ }
33756
+ return modifiers;
33757
+ }
33758
+
33759
+ function addListener(target, type, listener, options) {
33760
+ target.addEventListener(type, listener, options);
33761
+ cleanup.push(() => {
33762
+ target.removeEventListener(type, listener, options);
33763
+ });
33764
+ }
33765
+
33766
+ function requestStop() {
33767
+ stopRequested = true;
33768
+ }
33769
+
33770
+ function mountStopButton() {
33771
+ if (document.querySelector("[" + recorderUiAttribute + "]")) {
33772
+ return true;
33773
+ }
33774
+ if (stopRequested) {
33775
+ return true;
33776
+ }
33777
+ const parent = document.body || document.documentElement;
33778
+ if (!(parent instanceof HTMLElement)) {
33779
+ return false;
33780
+ }
33781
+
33782
+ const host = document.createElement("div");
33783
+ host.setAttribute(recorderUiAttribute, "true");
33784
+ const shadowRoot = host.attachShadow({ mode: "open" });
33785
+ const style = document.createElement("style");
33786
+ style.textContent = [
33787
+ ":host {",
33788
+ " all: initial;",
33789
+ " display: block;",
33790
+ " position: fixed;",
33791
+ " top: 16px;",
33792
+ " right: 16px;",
33793
+ " z-index: 2147483647;",
33794
+ " pointer-events: auto;",
33795
+ " color-scheme: only light;",
33796
+ " contain: content;",
33797
+ " isolation: isolate;",
33798
+ "}",
33799
+ "button {",
33800
+ " appearance: none;",
33801
+ " border: 1px solid rgba(15, 23, 42, 0.2);",
33802
+ " border-radius: 999px;",
33803
+ " background: rgba(15, 23, 42, 0.92);",
33804
+ " color: #ffffff;",
33805
+ " color-scheme: only light;",
33806
+ " cursor: pointer;",
33807
+ " font: 600 12px/1.2 ui-sans-serif, system-ui, sans-serif;",
33808
+ " letter-spacing: 0.01em;",
33809
+ " padding: 10px 14px;",
33810
+ " box-shadow: 0 10px 30px rgba(15, 23, 42, 0.22);",
33811
+ "}",
33812
+ "button:hover {",
33813
+ " background: rgba(15, 23, 42, 0.98);",
33814
+ "}",
33815
+ "button:focus-visible {",
33816
+ " outline: 2px solid #2563eb;",
33817
+ " outline-offset: 2px;",
33818
+ "}",
33819
+ ].join("\n");
33820
+ const button = document.createElement("button");
33821
+ button.type = "button";
33822
+ button.textContent = "Stop recording";
33823
+ button.setAttribute("aria-label", "Stop recording");
33824
+ const consumePointerEvent = (event) => {
33825
+ event.preventDefault();
33826
+ event.stopPropagation();
33827
+ };
33828
+ for (const type of ["pointerdown", "mousedown", "mouseup", "click"]) {
33829
+ button.addEventListener(type, consumePointerEvent);
33830
+ }
33831
+ button.addEventListener("click", () => {
33832
+ requestStop();
33833
+ host.remove();
33834
+ });
33835
+ shadowRoot.append(style, button);
33836
+ parent.appendChild(host);
33837
+ cleanup.push(() => {
33838
+ host.remove();
33839
+ });
33840
+ return true;
33841
+ }
33842
+
33843
+ function ensureStopButtonMounted() {
33844
+ if (mountStopButton()) {
33845
+ return;
33846
+ }
33847
+
33848
+ const observer = new MutationObserver(() => {
33849
+ if (!mountStopButton()) {
33850
+ return;
33851
+ }
33852
+ observer.disconnect();
33853
+ });
33854
+ observer.observe(document, {
33855
+ childList: true,
33856
+ subtree: true,
33857
+ });
33858
+ cleanup.push(() => {
33859
+ observer.disconnect();
33860
+ });
33861
+ }
33862
+
33863
+ addListener(document, "click", (event) => {
33864
+ if (!event.isTrusted) {
33865
+ return;
33866
+ }
33867
+ const target = nearestRecordTarget(event.target);
33868
+ if (!(target instanceof Element)) {
33869
+ return;
33870
+ }
33871
+ const tag = target.tagName.toLowerCase();
33872
+ if (tag === "select" || tag === "option") {
33873
+ return;
33874
+ }
33875
+ const selector = buildSelector(target);
33876
+ if (!selector) {
33877
+ return;
33878
+ }
33879
+ enqueue({
33880
+ kind: "click",
33881
+ selector,
33882
+ button: event.button,
33883
+ modifiers: modifierKeys(event),
33884
+ });
33885
+ }, true);
33886
+
33887
+ addListener(document, "dblclick", (event) => {
33888
+ if (!event.isTrusted) {
33889
+ return;
33890
+ }
33891
+ const target = nearestRecordTarget(event.target);
33892
+ if (!(target instanceof Element)) {
33893
+ return;
33894
+ }
33895
+ const selector = buildSelector(target);
33896
+ if (!selector) {
33897
+ return;
33898
+ }
33899
+ enqueue({
33900
+ kind: "dblclick",
33901
+ selector,
33902
+ });
33903
+ }, true);
33904
+
33905
+ addListener(document, "input", (event) => {
33906
+ if (!event.isTrusted) {
33907
+ return;
33908
+ }
33909
+ const target = nearestRecordTarget(event.target);
33910
+ if (!(target instanceof Element)) {
33911
+ return;
33912
+ }
33913
+ const selector = buildSelector(target);
33914
+ const text = readTargetValue(target);
33915
+ if (!selector || typeof text !== "string") {
33916
+ return;
33917
+ }
33918
+ pendingInputs.set(selector, {
33919
+ selector,
33920
+ text,
33921
+ timestamp: now(),
33922
+ });
33923
+ scheduleInputFlush(selector);
33924
+ }, true);
33925
+
33926
+ addListener(document, "change", (event) => {
33927
+ if (!event.isTrusted) {
33928
+ return;
33929
+ }
33930
+ const target = nearestRecordTarget(event.target);
33931
+ if (!(target instanceof HTMLSelectElement)) {
33932
+ return;
33933
+ }
33934
+ const selector = buildSelector(target);
33935
+ if (!selector) {
33936
+ return;
33937
+ }
33938
+ const selectedOption = target.selectedOptions && target.selectedOptions.length > 0
33939
+ ? target.selectedOptions[0]
33940
+ : undefined;
33941
+ enqueue({
33942
+ kind: "select-option",
33943
+ selector,
33944
+ value: target.value,
33945
+ ...(selectedOption === undefined ? {} : { label: selectedOption.label || selectedOption.textContent || "" }),
33946
+ });
33947
+ }, true);
33948
+
33949
+ addListener(document, "keydown", (event) => {
33950
+ if (!event.isTrusted) {
33951
+ return;
33952
+ }
33953
+ const allowedKeys = new Set([
33954
+ "ArrowDown",
33955
+ "ArrowLeft",
33956
+ "ArrowRight",
33957
+ "ArrowUp",
33958
+ "Backspace",
33959
+ "Delete",
33960
+ "Enter",
33961
+ "Escape",
33962
+ "Tab",
33963
+ ]);
33964
+ if (!allowedKeys.has(event.key)) {
33965
+ return;
33966
+ }
33967
+ const target = nearestRecordTarget(event.target);
33968
+ const selector = target instanceof Element ? buildSelector(target) : undefined;
33969
+ if (event.key === "Enter" && selector) {
33970
+ flushPendingInput(selector);
33971
+ }
33972
+ enqueue({
33973
+ kind: "keypress",
33974
+ key: event.key,
33975
+ modifiers: modifierKeys(event),
33976
+ ...(selector === undefined ? {} : { selector }),
33977
+ });
33978
+ }, true);
33979
+
33980
+ addListener(document, "wheel", (event) => {
33981
+ if (!event.isTrusted) {
33982
+ return;
33983
+ }
33984
+ const target = nearestRecordTarget(event.target);
33985
+ const selector = target instanceof Element ? buildSelector(target) : undefined;
33986
+ if (pendingWheel && pendingWheel.selector === selector) {
33987
+ pendingWheel.deltaX += event.deltaX;
33988
+ pendingWheel.deltaY += event.deltaY;
33989
+ clearTimeout(pendingWheel.timerId);
33990
+ pendingWheel.timerId = setTimeout(flushPendingWheel, 250);
33991
+ return;
33992
+ }
33993
+ flushPendingWheel();
33994
+ pendingWheel = {
33995
+ selector,
33996
+ deltaX: event.deltaX,
33997
+ deltaY: event.deltaY,
33998
+ timestamp: now(),
33999
+ timerId: setTimeout(flushPendingWheel, 250),
34000
+ };
34001
+ }, true);
34002
+
34003
+ const originalPushState = history.pushState.bind(history);
34004
+ history.pushState = function pushState(state, unused, url) {
34005
+ const beforeUrl = location.href;
34006
+ const output = originalPushState.apply(this, arguments);
34007
+ const nextUrl = location.href;
34008
+ if (nextUrl !== beforeUrl) {
34009
+ updateHistoryState("push", nextUrl);
34010
+ enqueue({
34011
+ kind: "navigate",
34012
+ url: nextUrl,
34013
+ source: "push-state",
34014
+ });
34015
+ }
34016
+ return output;
34017
+ };
34018
+ cleanup.push(() => {
34019
+ history.pushState = originalPushState;
34020
+ });
34021
+
34022
+ const originalReplaceState = history.replaceState.bind(history);
34023
+ history.replaceState = function replaceState(state, unused, url) {
34024
+ const beforeUrl = location.href;
34025
+ const output = originalReplaceState.apply(this, arguments);
34026
+ const nextUrl = location.href;
34027
+ if (nextUrl !== beforeUrl) {
34028
+ updateHistoryState("replace", nextUrl);
34029
+ enqueue({
34030
+ kind: "navigate",
34031
+ url: nextUrl,
34032
+ source: "replace-state",
34033
+ });
34034
+ }
34035
+ return output;
34036
+ };
34037
+ cleanup.push(() => {
34038
+ history.replaceState = originalReplaceState;
34039
+ });
34040
+
34041
+ addListener(globalScope, "popstate", () => {
34042
+ const currentUrl = location.href;
34043
+ const traversal = classifyHistoryTraversal(currentUrl);
34044
+ if (traversal === "go-back" || traversal === "go-forward") {
34045
+ enqueue({
34046
+ kind: traversal,
34047
+ url: currentUrl,
34048
+ });
34049
+ return;
34050
+ }
34051
+ const state = readHistoryState();
34052
+ if (state?.entries[state.index] !== currentUrl) {
34053
+ updateHistoryState("push", currentUrl);
34054
+ enqueue({
34055
+ kind: "navigate",
34056
+ url: currentUrl,
34057
+ source: "history-traversal",
34058
+ });
34059
+ }
34060
+ });
34061
+
34062
+ addListener(globalScope, "hashchange", () => {
34063
+ const currentUrl = location.href;
34064
+ updateHistoryState("replace", currentUrl);
34065
+ enqueue({
34066
+ kind: "navigate",
34067
+ url: currentUrl,
34068
+ source: "hashchange",
34069
+ });
34070
+ });
34071
+
34072
+ function drain() {
34073
+ flushAllInputs();
34074
+ flushPendingWheel();
34075
+ return {
34076
+ url: location.href,
34077
+ focused: document.hasFocus(),
34078
+ visibilityState: document.visibilityState,
34079
+ stopRequested,
34080
+ events: queue.splice(0, queue.length),
34081
+ };
34082
+ }
34083
+
34084
+ globalScope[recorderKey] = {
34085
+ installed: true,
34086
+ debugSelector(target) {
34087
+ return buildSelector(target);
34088
+ },
34089
+ drain,
34090
+ requestStop,
34091
+ dispose() {
34092
+ flushAllInputs();
34093
+ flushPendingWheel();
34094
+ for (const dispose of cleanup.splice(0, cleanup.length)) {
34095
+ dispose();
34096
+ }
34097
+ for (const timerId of inputFlushTimers.values()) {
34098
+ clearTimeout(timerId);
34099
+ }
34100
+ inputFlushTimers.clear();
34101
+ pendingInputs.clear();
34102
+ delete globalScope[recorderKey];
34103
+ },
34104
+ };
34105
+
34106
+ if (options.showStopButton) {
34107
+ ensureStopButtonMounted();
34108
+ }
34109
+ onInstall();
34110
+ })();`;
34111
+ }
34112
+ var FLOW_RECORDER_INSTALL_SCRIPT = createFlowRecorderInstallScript();
34113
+ var FLOW_RECORDER_DRAIN_SCRIPT = String.raw`(() => {
34114
+ const recorder = globalThis.__opensteerFlowRecorder;
34115
+ if (!recorder || typeof recorder.drain !== "function") {
34116
+ return {
34117
+ url: location.href,
34118
+ focused: document.hasFocus(),
34119
+ visibilityState: document.visibilityState,
34120
+ stopRequested: false,
34121
+ events: [],
34122
+ };
34123
+ }
34124
+ return recorder.drain();
34125
+ })();`;
34126
+
34127
+ // ../runtime-core/src/recorder/event-collector.ts
34128
+ var FlowRecorderCollector = class {
34129
+ runtime;
34130
+ pollIntervalMs;
34131
+ onAction;
34132
+ installScript;
34133
+ pages = /* @__PURE__ */ new Map();
34134
+ actions = [];
34135
+ nextPageOrdinal = 0;
34136
+ runningLoop;
34137
+ loopStopRequested = false;
34138
+ stopDetected = false;
34139
+ focusedPageId;
34140
+ stopWaiters = [];
34141
+ constructor(runtime, options = {}) {
34142
+ this.runtime = runtime;
34143
+ this.pollIntervalMs = options.pollIntervalMs ?? 250;
34144
+ this.onAction = options.onAction;
34145
+ this.installScript = options.installScript ?? FLOW_RECORDER_INSTALL_SCRIPT;
34146
+ }
34147
+ async install() {
34148
+ await this.runtime.addInitScript({
34149
+ script: this.installScript
34150
+ });
34151
+ const { pages } = await this.runtime.listPages();
34152
+ for (const page of pages) {
34153
+ this.ensureKnownPage(page.pageRef, page.url, page.openerPageRef);
34154
+ }
34155
+ await Promise.all(
34156
+ pages.map(
34157
+ (page) => this.runtime.evaluate({
34158
+ script: this.installScript,
34159
+ pageRef: page.pageRef
34160
+ }).catch(() => void 0)
34161
+ )
34162
+ );
34163
+ const evaluatedPages = await this.readEvaluatedPages(pages);
34164
+ for (const page of evaluatedPages) {
34165
+ this.updateKnownPage(page.pageRef, page.currentUrl, page.openerPageRef);
34166
+ }
34167
+ this.focusedPageId = evaluatedPages.find((page) => page.focused)?.pageId ?? this.focusedPageId;
34168
+ }
34169
+ start() {
34170
+ if (this.runningLoop !== void 0) {
34171
+ return;
34172
+ }
34173
+ this.loopStopRequested = false;
34174
+ this.runningLoop = this.runLoop();
34175
+ }
34176
+ async stop() {
34177
+ this.loopStopRequested = true;
34178
+ if (this.runningLoop !== void 0) {
34179
+ await this.runningLoop;
34180
+ this.runningLoop = void 0;
34181
+ }
34182
+ if (!this.stopDetected) {
34183
+ await this.pollOnce().catch(() => void 0);
34184
+ }
34185
+ return this.actions.slice();
34186
+ }
34187
+ getActions() {
34188
+ return this.actions.slice();
34189
+ }
34190
+ getPages() {
34191
+ return [...this.pages.values()].map((page) => ({
34192
+ pageId: page.pageId,
34193
+ pageRef: page.pageRef,
34194
+ ...page.openerPageRef === void 0 ? {} : { openerPageRef: page.openerPageRef },
34195
+ ...page.openerPageId === void 0 ? {} : { openerPageId: page.openerPageId },
34196
+ currentUrl: page.currentUrl
34197
+ })).sort((left, right) => comparePageIds(left.pageId, right.pageId));
34198
+ }
34199
+ getFocusedPageId() {
34200
+ return this.focusedPageId;
34201
+ }
34202
+ async waitForStop() {
34203
+ if (this.stopDetected) {
34204
+ return;
34205
+ }
34206
+ await new Promise((resolve4) => {
34207
+ this.stopWaiters.push(resolve4);
34208
+ });
34209
+ }
34210
+ async pollOnce() {
34211
+ const pollTimestamp = Date.now();
34212
+ const { pages } = await this.runtime.listPages();
34213
+ const previousPageRefs = new Set(this.pages.keys());
34214
+ const evaluatedPages = await this.readEvaluatedPages(pages);
34215
+ if (!this.stopDetected && evaluatedPages.some((page) => page.stopRequested)) {
34216
+ this.stopDetected = true;
34217
+ this.loopStopRequested = true;
34218
+ for (const resolve4 of this.stopWaiters.splice(0, this.stopWaiters.length)) {
34219
+ resolve4();
34220
+ }
34221
+ }
34222
+ const actions = this.normalizePoll({
34223
+ pollTimestamp,
34224
+ previousPageRefs,
34225
+ listedPages: pages,
34226
+ evaluatedPages
34227
+ });
34228
+ if (actions.length === 0) {
34229
+ return [];
34230
+ }
34231
+ actions.sort((left, right) => {
34232
+ const timestampOrder = left.timestamp - right.timestamp;
34233
+ if (timestampOrder !== 0) {
34234
+ return timestampOrder;
34235
+ }
34236
+ return actionSortPriority(left.kind) - actionSortPriority(right.kind);
34237
+ });
34238
+ for (const action of actions) {
34239
+ this.actions.push(action);
34240
+ if (this.onAction !== void 0) {
34241
+ await this.onAction(action);
34242
+ }
34243
+ }
34244
+ return actions;
34245
+ }
34246
+ async runLoop() {
34247
+ while (!this.loopStopRequested) {
34248
+ try {
34249
+ await this.pollOnce();
34250
+ } catch {
34251
+ }
34252
+ if (this.loopStopRequested) {
34253
+ break;
34254
+ }
34255
+ await delay2(this.pollIntervalMs);
34256
+ }
34257
+ }
34258
+ async readEvaluatedPages(listedPages) {
34259
+ const pages = await Promise.all(
34260
+ listedPages.map(async (page) => {
34261
+ const knownPage = this.ensureKnownPage(page.pageRef, page.url, page.openerPageRef);
34262
+ const snapshot = await this.readSnapshot(page.pageRef, page.url);
34263
+ this.updateKnownPage(page.pageRef, snapshot.url, page.openerPageRef);
34264
+ return {
34265
+ pageRef: page.pageRef,
34266
+ pageId: knownPage.pageId,
34267
+ previousUrl: knownPage.currentUrl,
34268
+ currentUrl: snapshot.url,
34269
+ focused: snapshot.focused || snapshot.visibilityState === "visible",
34270
+ stopRequested: snapshot.stopRequested,
34271
+ events: snapshot.events,
34272
+ ...page.openerPageRef === void 0 ? {} : { openerPageRef: page.openerPageRef },
34273
+ ...knownPage.openerPageId === void 0 ? {} : { openerPageId: knownPage.openerPageId }
34274
+ };
34275
+ })
34276
+ );
34277
+ return pages;
34278
+ }
34279
+ async readSnapshot(pageRef, fallbackUrl) {
34280
+ const value = await this.runtime.evaluate({
34281
+ script: FLOW_RECORDER_DRAIN_SCRIPT,
34282
+ pageRef
34283
+ });
34284
+ return normalizeSnapshot(value, fallbackUrl);
34285
+ }
34286
+ normalizePoll(input) {
34287
+ const listedPageRefs = new Set(input.listedPages.map((page) => page.pageRef));
34288
+ const actions = [];
34289
+ const firstEventTimestampByPage = /* @__PURE__ */ new Map();
34290
+ for (const evaluatedPage of input.evaluatedPages) {
34291
+ const firstTimestamp = evaluatedPage.events[0]?.timestamp;
34292
+ if (firstTimestamp !== void 0) {
34293
+ firstEventTimestampByPage.set(evaluatedPage.pageRef, firstTimestamp);
34294
+ }
34295
+ }
34296
+ for (const listedPage of input.listedPages) {
34297
+ if (!input.previousPageRefs.has(listedPage.pageRef)) {
34298
+ const created = this.ensureKnownPage(
34299
+ listedPage.pageRef,
34300
+ listedPage.url,
34301
+ listedPage.openerPageRef
34302
+ );
34303
+ actions.push({
34304
+ kind: "new-tab",
34305
+ timestamp: Math.max(
34306
+ 0,
34307
+ (firstEventTimestampByPage.get(listedPage.pageRef) ?? input.pollTimestamp) - 1
34308
+ ),
34309
+ pageId: created.pageId,
34310
+ pageUrl: listedPage.url,
34311
+ detail: {
34312
+ kind: "new-tab",
34313
+ ...created.openerPageId === void 0 ? {} : { openerPageId: created.openerPageId },
34314
+ initialUrl: listedPage.url
34315
+ }
34316
+ });
34317
+ }
34318
+ }
34319
+ for (const [pageRef, knownPage] of [...this.pages.entries()]) {
34320
+ if (listedPageRefs.has(pageRef)) {
34321
+ continue;
34322
+ }
34323
+ actions.push({
34324
+ kind: "close-tab",
34325
+ timestamp: input.pollTimestamp,
34326
+ pageId: knownPage.pageId,
34327
+ pageUrl: knownPage.currentUrl,
34328
+ detail: {
34329
+ kind: "close-tab"
34330
+ }
34331
+ });
34332
+ this.pages.delete(pageRef);
34333
+ }
34334
+ for (const evaluatedPage of input.evaluatedPages) {
34335
+ const knownPage = this.pages.get(evaluatedPage.pageRef);
34336
+ if (!knownPage) {
34337
+ continue;
34338
+ }
34339
+ if (evaluatedPage.previousUrl !== evaluatedPage.currentUrl && !evaluatedPage.events.some(
34340
+ (event) => event.kind === "navigate" || event.kind === "reload" || event.kind === "go-back" || event.kind === "go-forward"
34341
+ )) {
34342
+ actions.push({
34343
+ kind: "navigate",
34344
+ timestamp: Math.max(
34345
+ 0,
34346
+ (firstEventTimestampByPage.get(evaluatedPage.pageRef) ?? input.pollTimestamp) - 1
34347
+ ),
34348
+ pageId: knownPage.pageId,
34349
+ pageUrl: evaluatedPage.currentUrl,
34350
+ detail: {
34351
+ kind: "navigate",
34352
+ url: evaluatedPage.currentUrl,
34353
+ source: "poll"
34354
+ }
34355
+ });
34356
+ }
34357
+ actions.push(
34358
+ ...evaluatedPage.events.flatMap(
34359
+ (event) => this.normalizeRawEvent(event, knownPage, evaluatedPage.currentUrl)
34360
+ )
34361
+ );
34362
+ this.updateKnownPage(
34363
+ evaluatedPage.pageRef,
34364
+ evaluatedPage.currentUrl,
34365
+ evaluatedPage.openerPageRef
34366
+ );
34367
+ }
34368
+ const focusedPage = input.evaluatedPages.find((page) => page.focused);
34369
+ if (focusedPage !== void 0 && focusedPage.pageId !== this.focusedPageId) {
34370
+ actions.push({
34371
+ kind: "switch-tab",
34372
+ timestamp: Math.max(
34373
+ 0,
34374
+ (firstEventTimestampByPage.get(focusedPage.pageRef) ?? input.pollTimestamp) - 1
34375
+ ),
34376
+ pageId: focusedPage.pageId,
34377
+ pageUrl: focusedPage.currentUrl,
34378
+ detail: {
34379
+ kind: "switch-tab",
34380
+ ...this.focusedPageId === void 0 ? {} : { fromPageId: this.focusedPageId },
34381
+ toPageId: focusedPage.pageId
34382
+ }
34383
+ });
34384
+ this.focusedPageId = focusedPage.pageId;
34385
+ }
34386
+ return dedupeConsecutiveSwitchActions(actions);
34387
+ }
34388
+ normalizeRawEvent(event, page, currentUrl) {
34389
+ switch (event.kind) {
34390
+ case "navigate":
34391
+ return [
34392
+ {
34393
+ kind: "navigate",
34394
+ timestamp: event.timestamp,
34395
+ pageId: page.pageId,
34396
+ pageUrl: event.url,
34397
+ detail: {
34398
+ kind: "navigate",
34399
+ url: event.url,
34400
+ source: event.source
34401
+ }
34402
+ }
34403
+ ];
34404
+ case "click":
34405
+ return [
34406
+ {
34407
+ kind: "click",
34408
+ timestamp: event.timestamp,
34409
+ pageId: page.pageId,
34410
+ pageUrl: currentUrl,
34411
+ selector: event.selector,
34412
+ detail: {
34413
+ kind: "click",
34414
+ button: event.button,
34415
+ modifiers: event.modifiers
34416
+ }
34417
+ }
34418
+ ];
34419
+ case "dblclick":
34420
+ return [
34421
+ {
34422
+ kind: "dblclick",
34423
+ timestamp: event.timestamp,
34424
+ pageId: page.pageId,
34425
+ pageUrl: currentUrl,
34426
+ selector: event.selector,
34427
+ detail: {
34428
+ kind: "dblclick"
34429
+ }
34430
+ }
34431
+ ];
34432
+ case "type":
34433
+ return [
34434
+ {
34435
+ kind: "type",
34436
+ timestamp: event.timestamp,
34437
+ pageId: page.pageId,
34438
+ pageUrl: currentUrl,
34439
+ selector: event.selector,
34440
+ detail: {
34441
+ kind: "type",
34442
+ text: event.text
34443
+ }
34444
+ }
34445
+ ];
34446
+ case "keypress":
34447
+ return [
34448
+ {
34449
+ kind: "keypress",
34450
+ timestamp: event.timestamp,
34451
+ pageId: page.pageId,
34452
+ pageUrl: currentUrl,
34453
+ ...event.selector === void 0 ? {} : { selector: event.selector },
34454
+ detail: {
34455
+ kind: "keypress",
34456
+ key: event.key,
34457
+ modifiers: event.modifiers
34458
+ }
34459
+ }
34460
+ ];
34461
+ case "scroll":
34462
+ return [
34463
+ {
34464
+ kind: "scroll",
34465
+ timestamp: event.timestamp,
34466
+ pageId: page.pageId,
34467
+ pageUrl: currentUrl,
34468
+ ...event.selector === void 0 ? {} : { selector: event.selector },
34469
+ detail: {
34470
+ kind: "scroll",
34471
+ deltaX: event.deltaX,
34472
+ deltaY: event.deltaY
34473
+ }
34474
+ }
34475
+ ];
34476
+ case "select-option":
34477
+ return [
34478
+ {
34479
+ kind: "select-option",
34480
+ timestamp: event.timestamp,
34481
+ pageId: page.pageId,
34482
+ pageUrl: currentUrl,
34483
+ selector: event.selector,
34484
+ detail: {
34485
+ kind: "select-option",
34486
+ value: event.value,
34487
+ ...event.label === void 0 ? {} : { label: event.label }
34488
+ }
34489
+ }
34490
+ ];
34491
+ case "reload":
34492
+ return [
34493
+ {
34494
+ kind: "reload",
34495
+ timestamp: event.timestamp,
34496
+ pageId: page.pageId,
34497
+ pageUrl: event.url,
34498
+ detail: {
34499
+ kind: "reload",
34500
+ url: event.url
34501
+ }
34502
+ }
34503
+ ];
34504
+ case "go-back":
34505
+ return [
34506
+ {
34507
+ kind: "go-back",
34508
+ timestamp: event.timestamp,
34509
+ pageId: page.pageId,
34510
+ pageUrl: event.url,
34511
+ detail: {
34512
+ kind: "go-back",
34513
+ url: event.url
34514
+ }
34515
+ }
34516
+ ];
34517
+ case "go-forward":
34518
+ return [
34519
+ {
34520
+ kind: "go-forward",
34521
+ timestamp: event.timestamp,
34522
+ pageId: page.pageId,
34523
+ pageUrl: event.url,
34524
+ detail: {
34525
+ kind: "go-forward",
34526
+ url: event.url
34527
+ }
34528
+ }
34529
+ ];
34530
+ }
34531
+ }
34532
+ ensureKnownPage(pageRef, url, openerPageRef) {
34533
+ const existing = this.pages.get(pageRef);
34534
+ if (existing !== void 0) {
34535
+ return existing;
34536
+ }
34537
+ const openerPageId = openerPageRef === void 0 ? void 0 : this.pages.get(openerPageRef)?.pageId;
34538
+ const page = {
34539
+ pageId: `page${String(this.nextPageOrdinal++)}`,
34540
+ pageRef,
34541
+ currentUrl: url,
34542
+ ...openerPageRef === void 0 ? {} : { openerPageRef },
34543
+ ...openerPageId === void 0 ? {} : { openerPageId }
34544
+ };
34545
+ this.pages.set(pageRef, page);
34546
+ return page;
34547
+ }
34548
+ updateKnownPage(pageRef, url, openerPageRef) {
34549
+ const current = this.pages.get(pageRef);
34550
+ if (current === void 0) {
34551
+ this.ensureKnownPage(pageRef, url, openerPageRef);
34552
+ return;
34553
+ }
34554
+ const openerPageId = openerPageRef === void 0 ? void 0 : this.pages.get(openerPageRef)?.pageId;
34555
+ this.pages.set(pageRef, {
34556
+ ...current,
34557
+ currentUrl: url,
34558
+ ...openerPageRef === void 0 ? {} : { openerPageRef },
34559
+ ...openerPageId === void 0 ? {} : { openerPageId }
34560
+ });
34561
+ }
34562
+ };
34563
+ function normalizeSnapshot(value, fallbackUrl) {
34564
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
34565
+ return {
34566
+ url: fallbackUrl,
34567
+ focused: false,
34568
+ visibilityState: "hidden",
34569
+ stopRequested: false,
34570
+ events: []
34571
+ };
34572
+ }
34573
+ const snapshot = value;
34574
+ return {
34575
+ url: typeof snapshot.url === "string" ? snapshot.url : fallbackUrl,
34576
+ focused: snapshot.focused === true,
34577
+ visibilityState: snapshot.visibilityState === "visible" || snapshot.visibilityState === "prerender" || snapshot.visibilityState === "hidden" ? snapshot.visibilityState : "hidden",
34578
+ stopRequested: snapshot.stopRequested === true,
34579
+ events: Array.isArray(snapshot.events) ? snapshot.events : []
34580
+ };
34581
+ }
34582
+ function dedupeConsecutiveSwitchActions(actions) {
34583
+ const deduped = [];
34584
+ for (const action of actions) {
34585
+ const previous = deduped[deduped.length - 1];
34586
+ if (previous?.kind === "switch-tab" && action.kind === "switch-tab" && previous.pageId === action.pageId) {
34587
+ continue;
34588
+ }
34589
+ deduped.push(action);
34590
+ }
34591
+ return deduped;
34592
+ }
34593
+ function actionSortPriority(kind) {
34594
+ switch (kind) {
34595
+ case "new-tab":
34596
+ return 0;
34597
+ case "switch-tab":
34598
+ return 1;
34599
+ case "navigate":
34600
+ case "go-back":
34601
+ case "go-forward":
34602
+ case "reload":
34603
+ return 2;
34604
+ case "click":
34605
+ case "dblclick":
34606
+ return 3;
34607
+ case "type":
34608
+ case "keypress":
34609
+ case "select-option":
34610
+ return 4;
34611
+ case "scroll":
34612
+ return 5;
34613
+ case "close-tab":
34614
+ return 6;
34615
+ }
34616
+ }
34617
+ function comparePageIds(left, right) {
34618
+ const leftMatch = /^page(\d+)$/u.exec(left);
34619
+ const rightMatch = /^page(\d+)$/u.exec(right);
34620
+ if (leftMatch && rightMatch) {
34621
+ return Number(leftMatch[1]) - Number(rightMatch[1]);
34622
+ }
34623
+ return left.localeCompare(right);
34624
+ }
34625
+ function delay2(ms) {
34626
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
34627
+ }
34628
+
34629
+ // ../runtime-core/src/recorder/codegen.ts
34630
+ var DISPATCH_KEY_SCRIPT = String.raw`(key) => {
34631
+ const target = document.activeElement;
34632
+ if (!(target instanceof Element)) {
34633
+ throw new Error("No active element is available for key replay.");
34634
+ }
34635
+ const normalizedKey = String(key);
34636
+ const eventInit = {
34637
+ key: normalizedKey,
34638
+ code: normalizedKey,
34639
+ bubbles: true,
34640
+ cancelable: true,
34641
+ };
34642
+ target.dispatchEvent(new KeyboardEvent("keydown", eventInit));
34643
+ target.dispatchEvent(new KeyboardEvent("keyup", eventInit));
34644
+ }`;
34645
+ var SELECT_OPTION_SCRIPT = String.raw`(selector, value) => {
34646
+ const target = document.querySelector(String(selector));
34647
+ if (!(target instanceof HTMLSelectElement)) {
34648
+ throw new Error("Unable to find a <select> element for option replay.");
34649
+ }
34650
+ target.value = String(value);
34651
+ target.dispatchEvent(new Event("input", { bubbles: true }));
34652
+ target.dispatchEvent(new Event("change", { bubbles: true }));
34653
+ }`;
34654
+ var HISTORY_ACTION_SCRIPT = String.raw`(action) => {
34655
+ const normalized = String(action);
34656
+ if (normalized === "back") {
34657
+ history.back();
34658
+ return;
34659
+ }
34660
+ if (normalized === "forward") {
34661
+ history.forward();
34662
+ return;
34663
+ }
34664
+ if (normalized === "reload") {
34665
+ location.reload();
34666
+ return;
34667
+ }
34668
+ throw new Error("Unsupported history action: " + normalized);
34669
+ }`;
34670
+ var WINDOW_SCROLL_SCRIPT = String.raw`(deltaX, deltaY) => {
34671
+ window.scrollBy(Number(deltaX), Number(deltaY));
34672
+ }`;
34673
+ function generateReplayScript(options) {
34674
+ const replayTarget = resolveReplayTarget(options);
34675
+ const initialPages = orderInitialPages(resolveInitialPages(options));
34676
+ const activeInitialPageId = resolveActiveInitialPageId(options, initialPages);
34677
+ const initialPageId = initialPages[0]?.pageId ?? "page0";
34678
+ const lines = [
34679
+ `import { Opensteer } from "opensteer";`,
34680
+ ``,
34681
+ ...renderOpensteerBootstrap(replayTarget),
34682
+ ``,
34683
+ `const ${initialPageId} = (await opensteer.open(${JSON.stringify(initialPages[0]?.initialUrl ?? "")})).pageRef;`,
34684
+ `let activePageRef: string | undefined = ${initialPageId};`,
34685
+ ``,
34686
+ `async function ensureActive(pageRef: string): Promise<void> {`,
34687
+ ` if (activePageRef === pageRef) {`,
34688
+ ` return;`,
34689
+ ` }`,
34690
+ ` await opensteer.activatePage({ pageRef });`,
34691
+ ` activePageRef = pageRef;`,
34692
+ `}`,
34693
+ ``,
34694
+ `async function dispatchKey(pageRef: string, key: string): Promise<void> {`,
34695
+ ` await ensureActive(pageRef);`,
34696
+ ` await opensteer.evaluate({`,
34697
+ ` pageRef,`,
34698
+ ` script: ${JSON.stringify(DISPATCH_KEY_SCRIPT)},`,
34699
+ ` args: [key],`,
34700
+ ` });`,
34701
+ `}`,
34702
+ ``,
34703
+ `async function selectOption(pageRef: string, selector: string, value: string): Promise<void> {`,
34704
+ ` await ensureActive(pageRef);`,
34705
+ ` await opensteer.evaluate({`,
34706
+ ` pageRef,`,
34707
+ ` script: ${JSON.stringify(SELECT_OPTION_SCRIPT)},`,
34708
+ ` args: [selector, value],`,
34709
+ ` });`,
34710
+ `}`,
34711
+ ``,
34712
+ `async function runHistoryAction(pageRef: string, action: "back" | "forward" | "reload"): Promise<void> {`,
34713
+ ` await ensureActive(pageRef);`,
34714
+ ` await opensteer.evaluate({`,
34715
+ ` pageRef,`,
34716
+ ` script: ${JSON.stringify(HISTORY_ACTION_SCRIPT)},`,
34717
+ ` args: [action],`,
34718
+ ` });`,
34719
+ `}`,
34720
+ ``,
34721
+ `try {`
34722
+ ];
34723
+ const declaredPages = /* @__PURE__ */ new Set([initialPageId]);
34724
+ for (const page of initialPages.slice(1)) {
34725
+ const openerPageId = page.openerPageId !== void 0 && declaredPages.has(page.openerPageId) ? page.openerPageId : void 0;
34726
+ lines.push(
34727
+ ` const ${page.pageId} = (await opensteer.newPage(${renderNewPageInput(openerPageId, page.initialUrl)})).pageRef;`
34728
+ );
34729
+ lines.push(` activePageRef = ${page.pageId};`);
34730
+ declaredPages.add(page.pageId);
34731
+ }
34732
+ if (activeInitialPageId !== void 0 && activeInitialPageId !== initialPageId) {
34733
+ lines.push(` await ensureActive(${activeInitialPageId});`);
34734
+ }
34735
+ for (let index = 0; index < options.actions.length; index += 1) {
34736
+ const action = options.actions[index];
34737
+ const pageVar = action.pageId;
34738
+ if (action.kind === "type") {
34739
+ const nextAction = options.actions[index + 1];
34740
+ const mergedPressEnter = nextAction?.kind === "keypress" && nextAction.pageId === action.pageId && nextAction.selector === action.selector && nextAction.detail.kind === "keypress" && nextAction.detail.key === "Enter" && nextAction.detail.modifiers.length === 0;
34741
+ lines.push(` await ensureActive(${pageVar});`);
34742
+ lines.push(
34743
+ ` await opensteer.input({ selector: ${JSON.stringify(requireSelector(action))}, text: ${JSON.stringify(action.detail.text)}${mergedPressEnter ? `, pressEnter: true` : ``} });`
34744
+ );
34745
+ if (mergedPressEnter) {
34746
+ index += 1;
34747
+ }
34748
+ continue;
34749
+ }
34750
+ if (action.kind === "switch-tab" && options.actions[index - 1]?.kind === "new-tab" && options.actions[index - 1]?.pageId === action.pageId) {
34751
+ continue;
34752
+ }
34753
+ switch (action.kind) {
34754
+ case "navigate":
34755
+ lines.push(` await ensureActive(${pageVar});`);
34756
+ lines.push(` await opensteer.goto(${JSON.stringify(action.detail.url)});`);
34757
+ break;
34758
+ case "click":
34759
+ lines.push(` await ensureActive(${pageVar});`);
34760
+ lines.push(
34761
+ ` await opensteer.click({ selector: ${JSON.stringify(requireSelector(action))} });`
34762
+ );
34763
+ break;
34764
+ case "dblclick":
34765
+ lines.push(` await ensureActive(${pageVar});`);
34766
+ lines.push(
34767
+ ` await opensteer.click({ selector: ${JSON.stringify(requireSelector(action))}, clickCount: 2 });`
34768
+ );
34769
+ break;
34770
+ case "keypress":
34771
+ lines.push(` await dispatchKey(${pageVar}, ${JSON.stringify(action.detail.key)});`);
34772
+ break;
34773
+ case "scroll": {
34774
+ const { direction, amount, isWindowScroll } = normalizeScrollAction(action);
34775
+ lines.push(` await ensureActive(${pageVar});`);
34776
+ if (isWindowScroll) {
34777
+ lines.push(` await opensteer.evaluate({`);
34778
+ lines.push(` pageRef: ${pageVar},`);
34779
+ lines.push(` script: ${JSON.stringify(WINDOW_SCROLL_SCRIPT)},`);
34780
+ lines.push(
34781
+ ` args: [${String(action.detail.deltaX)}, ${String(action.detail.deltaY)}],`
34782
+ );
34783
+ lines.push(` });`);
34784
+ } else {
34785
+ lines.push(
34786
+ ` await opensteer.scroll({ selector: ${JSON.stringify(requireSelector(action))}, direction: ${JSON.stringify(direction)}, amount: ${String(amount)} });`
34787
+ );
34788
+ }
34789
+ break;
34790
+ }
34791
+ case "select-option":
34792
+ lines.push(
34793
+ ` await selectOption(${pageVar}, ${JSON.stringify(requireSelector(action))}, ${JSON.stringify(action.detail.value)});`
34794
+ );
34795
+ break;
34796
+ case "new-tab": {
34797
+ const openerPageVar = action.detail.openerPageId;
34798
+ const shouldUseWaitForPage = shouldUsePopupWait(options.actions, index, openerPageVar);
34799
+ const creationLine = shouldUseWaitForPage && openerPageVar !== void 0 ? ` const ${pageVar} = (await opensteer.waitForPage({ openerPageRef: ${openerPageVar}, timeoutMs: 30_000 })).pageRef;` : ` const ${pageVar} = (await opensteer.newPage(${renderNewPageInput(action.detail.openerPageId, action.detail.initialUrl)})).pageRef;`;
34800
+ lines.push(creationLine);
34801
+ lines.push(` activePageRef = ${pageVar};`);
34802
+ declaredPages.add(pageVar);
34803
+ break;
34804
+ }
34805
+ case "close-tab":
34806
+ lines.push(` await opensteer.closePage({ pageRef: ${pageVar} });`);
34807
+ lines.push(` if (activePageRef === ${pageVar}) {`);
34808
+ lines.push(` activePageRef = undefined;`);
34809
+ lines.push(` }`);
34810
+ break;
34811
+ case "switch-tab":
34812
+ lines.push(` await ensureActive(${pageVar});`);
34813
+ break;
34814
+ case "go-back":
34815
+ lines.push(` await runHistoryAction(${pageVar}, "back");`);
34816
+ break;
34817
+ case "go-forward":
34818
+ lines.push(` await runHistoryAction(${pageVar}, "forward");`);
34819
+ break;
34820
+ case "reload":
34821
+ lines.push(` await runHistoryAction(${pageVar}, "reload");`);
34822
+ break;
34823
+ }
34824
+ }
34825
+ lines.push(`} finally {`);
34826
+ lines.push(` await opensteer.close();`);
34827
+ lines.push(`}`);
34828
+ lines.push(``);
34829
+ return `${lines.join("\n")}
34830
+ `;
34831
+ }
34832
+ function resolveReplayTarget(options) {
34833
+ if (options.replayTarget !== void 0) {
34834
+ return options.replayTarget;
34835
+ }
34836
+ if (options.workspace !== void 0) {
34837
+ return {
34838
+ kind: "local",
34839
+ workspace: options.workspace
34840
+ };
34841
+ }
34842
+ throw new Error("Replay codegen requires either replayTarget or workspace.");
34843
+ }
34844
+ function resolveInitialPages(options) {
34845
+ if (options.initialPages !== void 0 && options.initialPages.length > 0) {
34846
+ const unique = /* @__PURE__ */ new Set();
34847
+ return options.initialPages.map((page) => {
34848
+ if (unique.has(page.pageId)) {
34849
+ throw new Error(`Duplicate initial page id "${page.pageId}" in recording bootstrap.`);
34850
+ }
34851
+ unique.add(page.pageId);
34852
+ return page;
34853
+ });
34854
+ }
34855
+ const startUrl = options.startUrl;
34856
+ if (startUrl === void 0) {
34857
+ throw new Error("Replay codegen requires startUrl when initialPages is not provided.");
34858
+ }
34859
+ return [
34860
+ {
34861
+ pageId: "page0",
34862
+ initialUrl: startUrl
34863
+ }
34864
+ ];
34865
+ }
34866
+ function resolveActiveInitialPageId(options, initialPages) {
34867
+ if (options.activePageId !== void 0) {
34868
+ return options.activePageId;
34869
+ }
34870
+ return initialPages[0]?.pageId;
34871
+ }
34872
+ function renderOpensteerBootstrap(replayTarget) {
34873
+ if (replayTarget.kind === "local") {
34874
+ return [
34875
+ `const opensteer = new Opensteer({`,
34876
+ ` workspace: ${JSON.stringify(replayTarget.workspace)},`,
34877
+ ` browser: "persistent",`,
34878
+ `});`
34879
+ ];
34880
+ }
34881
+ return [
34882
+ renderRequireEnvHelper(replayTarget),
34883
+ ``,
34884
+ `const opensteer = new Opensteer({`,
34885
+ ` provider: {`,
34886
+ ` mode: "cloud",`,
34887
+ ` baseUrl: requireEnv(${JSON.stringify(replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL")}),`,
34888
+ ` apiKey: requireEnv(${JSON.stringify(replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY")}),`,
34889
+ ...renderCloudBrowserProfile(replayTarget),
34890
+ ` },`,
34891
+ `});`
34892
+ ];
34893
+ }
34894
+ function renderRequireEnvHelper(replayTarget) {
34895
+ const baseUrlEnvVar = replayTarget.baseUrlEnvVar ?? "OPENSTEER_BASE_URL";
34896
+ const apiKeyEnvVar = replayTarget.apiKeyEnvVar ?? "OPENSTEER_API_KEY";
34897
+ return [
34898
+ `function requireEnv(name: string): string {`,
34899
+ ` const value = process.env[name];`,
34900
+ ` if (typeof value === "string" && value.trim().length > 0) {`,
34901
+ ` return value;`,
34902
+ ` }`,
34903
+ ` throw new Error(\`Missing environment variable \${name}. Set ${baseUrlEnvVar} and ${apiKeyEnvVar} before replaying this recording.\`);`,
34904
+ `}`
34905
+ ].join("\n");
34906
+ }
34907
+ function renderCloudBrowserProfile(replayTarget) {
34908
+ if (replayTarget.browserProfileId === void 0) {
34909
+ return [];
34910
+ }
34911
+ return [
34912
+ ` browserProfile: {`,
34913
+ ` profileId: ${JSON.stringify(replayTarget.browserProfileId)},`,
34914
+ ...replayTarget.reuseBrowserProfileIfActive ? [` reuseIfActive: true,`] : [],
34915
+ ` },`
34916
+ ];
34917
+ }
34918
+ function orderInitialPages(initialPages) {
34919
+ const ordered = [];
34920
+ const declared = /* @__PURE__ */ new Set();
34921
+ const remaining = [...initialPages];
34922
+ while (remaining.length > 0) {
34923
+ let advanced = false;
34924
+ for (let index = 0; index < remaining.length; index += 1) {
34925
+ const page = remaining[index];
34926
+ if (page.openerPageId !== void 0 && !declared.has(page.openerPageId)) {
34927
+ continue;
34928
+ }
34929
+ ordered.push(page);
34930
+ declared.add(page.pageId);
34931
+ remaining.splice(index, 1);
34932
+ advanced = true;
34933
+ break;
34934
+ }
34935
+ if (!advanced) {
34936
+ ordered.push(...remaining.splice(0, remaining.length));
34937
+ }
34938
+ }
34939
+ return ordered;
34940
+ }
34941
+ function requireSelector(action) {
34942
+ if (action.selector === void 0) {
34943
+ throw new Error(`Action "${action.kind}" on ${action.pageId} is missing a selector.`);
34944
+ }
34945
+ return action.selector;
34946
+ }
34947
+ function normalizeScrollAction(action) {
34948
+ const horizontal = Math.abs(action.detail.deltaX);
34949
+ const vertical = Math.abs(action.detail.deltaY);
34950
+ if (vertical >= horizontal) {
34951
+ return {
34952
+ direction: action.detail.deltaY < 0 ? "up" : "down",
34953
+ amount: Math.max(1, Math.round(vertical)),
34954
+ isWindowScroll: action.selector === void 0
34955
+ };
34956
+ }
34957
+ return {
34958
+ direction: action.detail.deltaX < 0 ? "left" : "right",
34959
+ amount: Math.max(1, Math.round(horizontal)),
34960
+ isWindowScroll: action.selector === void 0
34961
+ };
34962
+ }
34963
+ function shouldUsePopupWait(actions, newTabIndex, openerPageId) {
34964
+ if (openerPageId === void 0 || newTabIndex === 0) {
34965
+ return false;
34966
+ }
34967
+ const previousAction = actions[newTabIndex - 1];
34968
+ if (previousAction === void 0 || previousAction.pageId !== openerPageId) {
34969
+ return false;
34970
+ }
34971
+ return previousAction.kind === "click" || previousAction.kind === "dblclick" || previousAction.kind === "keypress";
34972
+ }
34973
+ function renderNewPageInput(openerPageId, initialUrl) {
34974
+ const argumentsList = [];
34975
+ if (openerPageId !== void 0) {
34976
+ argumentsList.push(`openerPageRef: ${openerPageId}`);
34977
+ }
34978
+ if (initialUrl.length > 0 && initialUrl !== "about:blank") {
34979
+ argumentsList.push(`url: ${JSON.stringify(initialUrl)}`);
34980
+ }
34981
+ if (argumentsList.length === 0) {
34982
+ return `{}`;
34983
+ }
34984
+ return `{ ${argumentsList.join(", ")} }`;
34985
+ }
33140
34986
  var OpensteerSemanticRestError = class extends Error {
33141
34987
  opensteerError;
33142
34988
  statusCode;
@@ -33152,6 +34998,9 @@ var OpensteerSemanticRestClient = class {
33152
34998
  this.connection = connection;
33153
34999
  }
33154
35000
  async invoke(operation, input) {
35001
+ return this.invokeInternal(operation, input, false);
35002
+ }
35003
+ async invokeInternal(operation, input, hasRetried) {
33155
35004
  const endpoint = opensteerSemanticRestEndpoints.find((entry) => entry.name === operation);
33156
35005
  if (!endpoint) {
33157
35006
  throw new Error(`unsupported semantic operation ${operation}`);
@@ -33161,7 +35010,7 @@ var OpensteerSemanticRestClient = class {
33161
35010
  });
33162
35011
  let response;
33163
35012
  try {
33164
- response = await fetch(`${this.connection.baseUrl}${endpoint.path}`, {
35013
+ response = await fetch(`${await this.connection.getBaseUrl()}${endpoint.path}`, {
33165
35014
  method: "POST",
33166
35015
  headers: {
33167
35016
  authorization: await this.connection.getAuthorizationHeader(),
@@ -33183,6 +35032,9 @@ var OpensteerSemanticRestClient = class {
33183
35032
  }
33184
35033
  return envelope.data;
33185
35034
  } catch (error) {
35035
+ if (!hasRetried && this.connection.handleError && await this.connection.handleError(error, { operation })) {
35036
+ return this.invokeInternal(operation, input, true);
35037
+ }
33186
35038
  if (operation === "session.close" && isFetchFailure(error)) {
33187
35039
  return { closed: true };
33188
35040
  }
@@ -33316,7 +35168,10 @@ var OpensteerCloudAutomationClient = class {
33316
35168
  }
33317
35169
  async connect() {
33318
35170
  const grant = await this.issueGrant("automation");
33319
- const wsUrl = new URL(grant.wsUrl);
35171
+ if (grant.transport !== "ws") {
35172
+ throw new Error(`cloud issued an invalid ${grant.kind} grant transport`);
35173
+ }
35174
+ const wsUrl = new URL(grant.url);
33320
35175
  wsUrl.searchParams.set("token", grant.token);
33321
35176
  const socket = new WebSocket2__default.default(wsUrl);
33322
35177
  this.socket = socket;
@@ -33681,7 +35536,7 @@ var CloudSessionProxy = class {
33681
35536
  cloud;
33682
35537
  observability;
33683
35538
  sessionId;
33684
- sessionBaseUrl;
35539
+ semanticGrant;
33685
35540
  client;
33686
35541
  automation;
33687
35542
  workspaceStore;
@@ -33732,7 +35587,7 @@ var CloudSessionProxy = class {
33732
35587
  reconnectable: this.workspace !== void 0 || this.sessionId !== void 0 || persisted !== void 0,
33733
35588
  capabilities: {
33734
35589
  semanticOperations: opensteerSemanticOperationNames,
33735
- sessionGrants: ["automation", "view", "cdp"],
35590
+ sessionGrants: ["semantic", "automation", "view", "cdp"],
33736
35591
  instrumentation: {
33737
35592
  route: true,
33738
35593
  interceptScript: true,
@@ -33974,13 +35829,12 @@ var CloudSessionProxy = class {
33974
35829
  return this.requireClient().invoke("computer.execute", input);
33975
35830
  }
33976
35831
  async close() {
33977
- const session = await this.loadPersistedSession() ?? (this.sessionId === void 0 || this.sessionBaseUrl === void 0 ? void 0 : {
35832
+ const session = await this.loadPersistedSession() ?? (this.sessionId === void 0 ? void 0 : {
33978
35833
  layout: "opensteer-session",
33979
35834
  version: 1,
33980
35835
  provider: "cloud",
33981
35836
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
33982
35837
  sessionId: this.sessionId,
33983
- baseUrl: this.sessionBaseUrl,
33984
35838
  startedAt: Date.now(),
33985
35839
  updatedAt: Date.now()
33986
35840
  });
@@ -33999,7 +35853,7 @@ var CloudSessionProxy = class {
33999
35853
  this.automation = void 0;
34000
35854
  this.client = void 0;
34001
35855
  this.sessionId = void 0;
34002
- this.sessionBaseUrl = void 0;
35856
+ this.semanticGrant = void 0;
34003
35857
  if (this.cleanupRootOnClose) {
34004
35858
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
34005
35859
  }
@@ -34015,39 +35869,56 @@ var CloudSessionProxy = class {
34015
35869
  await this.automation?.close().catch(() => void 0);
34016
35870
  this.automation = void 0;
34017
35871
  this.sessionId = void 0;
34018
- this.sessionBaseUrl = void 0;
35872
+ this.semanticGrant = void 0;
34019
35873
  }
34020
35874
  async ensureSession(input = {}) {
34021
35875
  if (this.client) {
34022
35876
  return;
34023
35877
  }
34024
35878
  assertSupportedCloudBrowserMode(input.browser);
35879
+ const localCloud = this.shouldUseLocalCloudTransport();
35880
+ const browserProfile = resolveCloudBrowserProfile(this.cloud, input);
34025
35881
  const persisted = await this.loadPersistedSession();
34026
35882
  if (persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
34027
- await this.syncRegistryToCloud();
35883
+ if (localCloud) {
35884
+ void this.syncRegistryToCloud();
35885
+ } else {
35886
+ await this.syncRegistryToCloud();
35887
+ }
34028
35888
  this.bindClient(persisted);
34029
35889
  return;
34030
35890
  }
34031
- await this.syncRegistryToCloud();
34032
- const session = await this.cloud.createSession({
35891
+ if (localCloud) {
35892
+ void this.syncRegistryToCloud();
35893
+ } else {
35894
+ await this.syncRegistryToCloud();
35895
+ }
35896
+ const baseCreateInput = {
34033
35897
  ...this.workspace === void 0 ? {} : { name: this.workspace },
34034
35898
  ...input.launch === void 0 ? {} : { browser: input.launch },
34035
35899
  ...input.context === void 0 ? {} : { context: input.context },
34036
35900
  ...this.observability === void 0 ? {} : { observability: this.observability },
34037
- ...resolveCloudBrowserProfile(this.cloud, input) === void 0 ? {} : { browserProfile: resolveCloudBrowserProfile(this.cloud, input) }
34038
- });
35901
+ ...browserProfile === void 0 ? {} : { browserProfile }
35902
+ };
35903
+ const createInput = localCloud && this.workspace !== void 0 ? {
35904
+ ...baseCreateInput,
35905
+ sourceType: "local-cloud",
35906
+ sourceRef: this.workspace,
35907
+ localWorkspaceRootPath: this.rootPath,
35908
+ locality: "auto"
35909
+ } : baseCreateInput;
35910
+ const session = await this.cloud.createSession(createInput);
34039
35911
  const record = {
34040
35912
  layout: "opensteer-session",
34041
35913
  version: 1,
34042
35914
  provider: "cloud",
34043
35915
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
34044
35916
  sessionId: session.sessionId,
34045
- baseUrl: session.baseUrl,
34046
35917
  startedAt: Date.now(),
34047
35918
  updatedAt: Date.now()
34048
35919
  };
34049
35920
  await this.writePersistedSession(record);
34050
- this.bindClient(record);
35921
+ this.bindClient(record, session.initialGrants?.semantic);
34051
35922
  }
34052
35923
  async syncRegistryToCloud() {
34053
35924
  if (this.workspace === void 0) {
@@ -34056,15 +35927,16 @@ var CloudSessionProxy = class {
34056
35927
  try {
34057
35928
  const workspaceStore = await this.ensureWorkspaceStore();
34058
35929
  await syncLocalRegistryToCloud(this.cloud, this.workspace, workspaceStore);
34059
- } catch (error) {
35930
+ } catch {
34060
35931
  }
34061
35932
  }
34062
- bindClient(record) {
35933
+ bindClient(record, initialSemanticGrant) {
34063
35934
  this.sessionId = record.sessionId;
34064
- this.sessionBaseUrl = record.baseUrl;
35935
+ this.semanticGrant = initialSemanticGrant?.kind === "semantic" ? initialSemanticGrant : void 0;
34065
35936
  this.client = new OpensteerSemanticRestClient({
34066
- baseUrl: record.baseUrl,
34067
- getAuthorizationHeader: async () => this.cloud.buildAuthorizationHeader()
35937
+ getBaseUrl: async () => (await this.ensureSemanticGrant()).url,
35938
+ getAuthorizationHeader: async () => `Bearer ${(await this.ensureSemanticGrant()).token}`,
35939
+ handleError: (error) => this.handleSemanticClientError(error)
34068
35940
  });
34069
35941
  this.automation = new OpensteerCloudAutomationClient(this.cloud, record.sessionId);
34070
35942
  }
@@ -34113,6 +35985,43 @@ var CloudSessionProxy = class {
34113
35985
  }
34114
35986
  return this.automation;
34115
35987
  }
35988
+ async ensureSemanticGrant(forceRefresh = false) {
35989
+ if (!forceRefresh && this.semanticGrant?.kind === "semantic" && this.semanticGrant.expiresAt > Date.now() + 1e4) {
35990
+ return this.semanticGrant;
35991
+ }
35992
+ if (!this.sessionId) {
35993
+ throw new Error("Cloud session has not been initialized.");
35994
+ }
35995
+ const issued = await this.cloud.issueAccess(this.sessionId, ["semantic"]);
35996
+ const grant = issued.grants.semantic;
35997
+ if (!grant || grant.transport !== "http") {
35998
+ throw new Error("cloud did not issue a valid semantic grant");
35999
+ }
36000
+ this.semanticGrant = grant;
36001
+ return grant;
36002
+ }
36003
+ async handleSemanticClientError(error) {
36004
+ if (!(error instanceof OpensteerSemanticRestError)) {
36005
+ return false;
36006
+ }
36007
+ if (error.statusCode !== 401 && error.statusCode !== 404) {
36008
+ return false;
36009
+ }
36010
+ this.semanticGrant = void 0;
36011
+ try {
36012
+ await this.ensureSemanticGrant(true);
36013
+ return true;
36014
+ } catch {
36015
+ return false;
36016
+ }
36017
+ }
36018
+ shouldUseLocalCloudTransport() {
36019
+ if (this.workspace === void 0) {
36020
+ return false;
36021
+ }
36022
+ const config = this.cloud.getConfig();
36023
+ return isLoopbackBaseUrl(config.baseUrl);
36024
+ }
34116
36025
  };
34117
36026
  function resolveCloudBrowserProfile(cloud, input) {
34118
36027
  return input.browserProfile ?? cloud.getConfig().browserProfile;
@@ -34128,6 +36037,15 @@ function assertSupportedCloudBrowserMode(browser) {
34128
36037
  function isMissingCloudSessionError(error) {
34129
36038
  return error instanceof Error && /\b404\b/.test(error.message);
34130
36039
  }
36040
+ function isLoopbackBaseUrl(baseUrl) {
36041
+ let url;
36042
+ try {
36043
+ url = new URL(baseUrl);
36044
+ } catch {
36045
+ return false;
36046
+ }
36047
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]";
36048
+ }
34131
36049
  var OpensteerRuntime = class extends OpensteerSessionRuntime {
34132
36050
  constructor(options = {}) {
34133
36051
  const publicWorkspace = normalizeWorkspace2(options.workspace);
@@ -34360,9 +36278,11 @@ async function describeCloudLane(input) {
34360
36278
  status: "connected",
34361
36279
  current: input.current,
34362
36280
  summary: input.record.sessionId,
34363
- detail: input.record.baseUrl,
34364
36281
  sessionId: input.record.sessionId,
34365
- baseUrl: input.record.baseUrl
36282
+ ...input.cloudConfig === void 0 ? {} : {
36283
+ detail: input.cloudConfig.baseUrl,
36284
+ baseUrl: input.cloudConfig.baseUrl
36285
+ }
34366
36286
  };
34367
36287
  if (input.cloudConfig === void 0) {
34368
36288
  return base;
@@ -34406,6 +36326,214 @@ function formatLaneRow(input) {
34406
36326
  const summary = input.summary.padEnd(16, " ");
34407
36327
  return `${input.marker} ${provider} ${status} ${summary}${input.detail ?? ""}`.trimEnd();
34408
36328
  }
36329
+ async function runOpensteerRecordCommand(input) {
36330
+ const stdout = input.stdout ?? process.stdout;
36331
+ const stderr = input.stderr ?? process.stderr;
36332
+ const outputPath = resolveRecordOutputPath({
36333
+ rootDir: input.rootDir,
36334
+ workspace: input.workspace,
36335
+ ...input.outputPath === void 0 ? {} : { outputPath: input.outputPath }
36336
+ });
36337
+ const runtime = input.runtime;
36338
+ const collector = new FlowRecorderCollector(createRecorderRuntimeAdapter(runtime), {
36339
+ ...input.pollIntervalMs === void 0 ? {} : { pollIntervalMs: input.pollIntervalMs },
36340
+ onAction: (action) => {
36341
+ stderr.write(`${formatRecordedAction(action)}
36342
+ `);
36343
+ }
36344
+ });
36345
+ stderr.write(
36346
+ `Recording browser actions for workspace "${input.workspace}". Click "Stop recording" in the browser when you're done.
36347
+ `
36348
+ );
36349
+ let closed = false;
36350
+ try {
36351
+ const opened = await runtime.open({
36352
+ url: input.url
36353
+ });
36354
+ await collector.install();
36355
+ collector.start();
36356
+ await collector.waitForStop();
36357
+ const actions = await collector.stop();
36358
+ const script = generateReplayScript({
36359
+ actions,
36360
+ workspace: input.workspace,
36361
+ startUrl: opened.url
36362
+ });
36363
+ await promises.mkdir(path7__default.default.dirname(outputPath), { recursive: true });
36364
+ await promises.writeFile(outputPath, script, "utf8");
36365
+ if (input.closeSession !== void 0) {
36366
+ await input.closeSession();
36367
+ closed = true;
36368
+ }
36369
+ stdout.write(`${outputPath}
36370
+ `);
36371
+ stderr.write(`Wrote replay script to ${outputPath}
36372
+ `);
36373
+ } finally {
36374
+ if (!closed) {
36375
+ await runtime.disconnect().catch(() => void 0);
36376
+ }
36377
+ }
36378
+ }
36379
+ async function runOpensteerCloudRecordCommand(input) {
36380
+ const stdout = input.stdout ?? process.stdout;
36381
+ const stderr = input.stderr ?? process.stderr;
36382
+ const outputPath = resolveRecordOutputPath({
36383
+ rootDir: input.rootDir,
36384
+ workspace: input.workspace,
36385
+ ...input.outputPath === void 0 ? {} : { outputPath: input.outputPath }
36386
+ });
36387
+ let cloud;
36388
+ const resolveCloud = () => {
36389
+ cloud ??= new OpensteerCloudClient(input.cloudConfig);
36390
+ return cloud;
36391
+ };
36392
+ const runtime = input.runtime ?? new CloudSessionProxy(resolveCloud(), {
36393
+ rootDir: input.rootDir,
36394
+ workspace: input.workspace
36395
+ });
36396
+ const client = input.client ?? resolveCloud();
36397
+ const sleep5 = input.sleep ?? delay3;
36398
+ let closed = false;
36399
+ try {
36400
+ await runtime.open({
36401
+ url: input.url,
36402
+ ...input.browser === void 0 ? {} : { browser: input.browser },
36403
+ ...input.launch === void 0 ? {} : { launch: input.launch },
36404
+ ...input.context === void 0 ? {} : { context: input.context }
36405
+ });
36406
+ const sessionId = await resolveCloudRecordingSessionId(runtime);
36407
+ const sessionUrl = buildCloudRecordingSessionUrl(input.cloudConfig, sessionId);
36408
+ await client.startSessionRecording(sessionId);
36409
+ stderr.write(
36410
+ `Recording browser actions for workspace "${input.workspace}". Open ${sessionUrl} and click "Stop recording" in the browser session toolbar when you're done.
36411
+ `
36412
+ );
36413
+ const completed = await waitForCloudRecordingCompletion({
36414
+ client,
36415
+ sessionId,
36416
+ ...input.pollIntervalMs === void 0 ? {} : { pollIntervalMs: input.pollIntervalMs },
36417
+ sleep: sleep5
36418
+ });
36419
+ if (completed.result === void 0) {
36420
+ throw new Error("Cloud recording completed without a replay script.");
36421
+ }
36422
+ await promises.mkdir(path7__default.default.dirname(outputPath), { recursive: true });
36423
+ await promises.writeFile(outputPath, completed.result.script, "utf8");
36424
+ await runtime.close();
36425
+ closed = true;
36426
+ stdout.write(`${outputPath}
36427
+ `);
36428
+ stderr.write(`Cloud browser session: ${sessionUrl}
36429
+ `);
36430
+ stderr.write(`Wrote replay script to ${outputPath}
36431
+ `);
36432
+ } finally {
36433
+ if (!closed) {
36434
+ await runtime.close().catch(() => void 0);
36435
+ }
36436
+ }
36437
+ }
36438
+ function resolveRecordOutputPath(input) {
36439
+ if (input.outputPath !== void 0) {
36440
+ return path7__default.default.resolve(input.rootDir, input.outputPath);
36441
+ }
36442
+ return path7__default.default.join(
36443
+ resolveFilesystemWorkspacePath({
36444
+ rootDir: input.rootDir,
36445
+ workspace: input.workspace
36446
+ }),
36447
+ "recorded-flow.ts"
36448
+ );
36449
+ }
36450
+ function createRecorderRuntimeAdapter(runtime) {
36451
+ return {
36452
+ addInitScript: (input) => runtime.addInitScript(input),
36453
+ evaluate: async (input) => {
36454
+ const output = await runtime.evaluate({
36455
+ script: input.script,
36456
+ ...input.pageRef === void 0 ? {} : { pageRef: input.pageRef }
36457
+ });
36458
+ return output.value;
36459
+ },
36460
+ listPages: async () => {
36461
+ const output = await runtime.listPages();
36462
+ return {
36463
+ pages: output.pages.map((page) => ({
36464
+ pageRef: page.pageRef,
36465
+ url: page.url,
36466
+ ...page.openerPageRef === void 0 ? {} : { openerPageRef: page.openerPageRef }
36467
+ }))
36468
+ };
36469
+ }
36470
+ };
36471
+ }
36472
+ function buildCloudRecordingSessionUrl(cloudConfig, sessionId) {
36473
+ const baseUrl = cloudConfig.appBaseUrl;
36474
+ if (!baseUrl || baseUrl.trim().length === 0) {
36475
+ throw new Error(
36476
+ 'record with provider=cloud requires OPENSTEER_CLOUD_APP_BASE_URL or "--cloud-app-base-url".'
36477
+ );
36478
+ }
36479
+ return `${baseUrl.replace(/\/+$/, "")}/browsers/${encodeURIComponent(sessionId)}`;
36480
+ }
36481
+ async function resolveCloudRecordingSessionId(runtime) {
36482
+ const info = await runtime.info();
36483
+ if (typeof info.sessionId !== "string" || info.sessionId.length === 0) {
36484
+ throw new Error("Cloud recording could not resolve the created session id.");
36485
+ }
36486
+ return info.sessionId;
36487
+ }
36488
+ async function waitForCloudRecordingCompletion(input) {
36489
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
36490
+ for (; ; ) {
36491
+ const state = await input.client.getSessionRecording(input.sessionId);
36492
+ if (state.status === "completed") {
36493
+ return state;
36494
+ }
36495
+ if (state.status === "failed") {
36496
+ throw new Error(state.error ?? "Cloud recording failed.");
36497
+ }
36498
+ await input.sleep(pollIntervalMs);
36499
+ }
36500
+ }
36501
+ function formatRecordedAction(action) {
36502
+ const time = new Date(action.timestamp).toISOString().slice(11, 19);
36503
+ switch (action.kind) {
36504
+ case "click":
36505
+ return `[${time}] click ${action.pageId} -> ${action.selector ?? "<unknown>"}`;
36506
+ case "dblclick":
36507
+ return `[${time}] dblclick ${action.pageId} -> ${action.selector ?? "<unknown>"}`;
36508
+ case "type":
36509
+ return `[${time}] type ${action.pageId} -> ${action.selector ?? "<unknown>"} -> ${JSON.stringify(action.detail.text)}`;
36510
+ case "keypress":
36511
+ return `[${time}] keypress ${action.pageId} -> ${action.detail.key}`;
36512
+ case "scroll":
36513
+ return `[${time}] scroll ${action.pageId} -> (${String(action.detail.deltaX)}, ${String(action.detail.deltaY)})`;
36514
+ case "select-option":
36515
+ return `[${time}] select ${action.pageId} -> ${action.selector ?? "<unknown>"} -> ${JSON.stringify(action.detail.value)}`;
36516
+ case "navigate":
36517
+ return `[${time}] navigate ${action.pageId} -> ${action.detail.url}`;
36518
+ case "new-tab":
36519
+ return `[${time}] new-tab ${action.pageId} -> ${action.detail.initialUrl}`;
36520
+ case "close-tab":
36521
+ return `[${time}] close-tab ${action.pageId}`;
36522
+ case "switch-tab":
36523
+ return `[${time}] switch-tab -> ${action.detail.toPageId}`;
36524
+ case "go-back":
36525
+ return `[${time}] go-back ${action.pageId}`;
36526
+ case "go-forward":
36527
+ return `[${time}] go-forward ${action.pageId}`;
36528
+ case "reload":
36529
+ return `[${time}] reload ${action.pageId}`;
36530
+ }
36531
+ }
36532
+ function delay3(ms) {
36533
+ return new Promise((resolve4) => {
36534
+ setTimeout(resolve4, ms);
36535
+ });
36536
+ }
34409
36537
 
34410
36538
  // src/cli/bin.ts
34411
36539
  var OPERATION_ALIASES = /* @__PURE__ */ new Map([
@@ -34497,6 +36625,10 @@ async function main() {
34497
36625
  await handleStatusCommand(parsed);
34498
36626
  return;
34499
36627
  }
36628
+ if (parsed.command[0] === "record") {
36629
+ await handleRecordCommandEntry(parsed);
36630
+ return;
36631
+ }
34500
36632
  const operation = parsed.command[0] === "run" ? parsed.rest[0] : resolveOperation(parsed.command);
34501
36633
  if (!operation) {
34502
36634
  throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
@@ -34641,6 +36773,100 @@ async function handleBrowserCommand(parsed) {
34641
36773
  throw new Error(`Unknown browser command: ${parsed.command.join(" ")}`);
34642
36774
  }
34643
36775
  }
36776
+ async function handleRecordCommandEntry(parsed) {
36777
+ if (parsed.options.workspace === void 0) {
36778
+ throw new Error('record requires "--workspace <id>".');
36779
+ }
36780
+ const url = parsed.options.url ?? parsed.rest[0];
36781
+ if (url === void 0) {
36782
+ throw new Error('record requires "--url <value>" or a positional URL.');
36783
+ }
36784
+ const provider = resolveCliProvider(parsed);
36785
+ assertCloudCliOptionsMatchProvider(parsed, provider.mode);
36786
+ const engineName = resolveCliEngineName(parsed);
36787
+ if (engineName !== "playwright") {
36788
+ throw new Error("record requires engine=playwright.");
36789
+ }
36790
+ const rootDir = process2__default.default.cwd();
36791
+ const recordBrowser = parsed.options.browser;
36792
+ if (provider.mode === "cloud") {
36793
+ if (typeof recordBrowser === "object") {
36794
+ throw new Error('record does not support browser.mode="attach".');
36795
+ }
36796
+ const runtimeProvider = buildCliRuntimeProvider(parsed, provider.mode);
36797
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
36798
+ ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
36799
+ environment: process2__default.default.env
36800
+ });
36801
+ await runOpensteerCloudRecordCommand({
36802
+ cloudConfig: runtimeConfig.cloud,
36803
+ workspace: parsed.options.workspace,
36804
+ url,
36805
+ rootDir,
36806
+ ...recordBrowser === void 0 ? {} : { browser: recordBrowser },
36807
+ ...parsed.options.launch === void 0 ? {} : { launch: parsed.options.launch },
36808
+ ...parsed.options.context === void 0 ? {} : { context: parsed.options.context },
36809
+ ...parsed.options.output === void 0 ? {} : { outputPath: parsed.options.output }
36810
+ });
36811
+ return;
36812
+ }
36813
+ if (parsed.options.launch?.headless === true) {
36814
+ throw new Error('record requires a headed browser. Remove "--headless true".');
36815
+ }
36816
+ if (recordBrowser !== void 0 && recordBrowser !== "persistent") {
36817
+ throw new Error('record only supports "--browser persistent".');
36818
+ }
36819
+ const launch = {
36820
+ ...parsed.options.launch ?? {},
36821
+ headless: false
36822
+ };
36823
+ const browserManager = new OpensteerBrowserManager({
36824
+ rootDir,
36825
+ workspace: parsed.options.workspace,
36826
+ engineName,
36827
+ browser: "persistent",
36828
+ launch,
36829
+ ...parsed.options.context === void 0 ? {} : { context: parsed.options.context }
36830
+ });
36831
+ const runtime = createOpensteerSemanticRuntime({
36832
+ provider: {
36833
+ mode: "local"
36834
+ },
36835
+ engine: engineName,
36836
+ runtimeOptions: {
36837
+ rootPath: browserManager.rootPath,
36838
+ cleanupRootOnClose: browserManager.cleanupRootOnDisconnect,
36839
+ workspace: parsed.options.workspace,
36840
+ browser: "persistent",
36841
+ launch,
36842
+ ...parsed.options.context === void 0 ? {} : { context: parsed.options.context }
36843
+ }
36844
+ });
36845
+ await runOpensteerRecordCommand({
36846
+ runtime,
36847
+ closeSession: () => closeOwnedLocalBrowserSession(runtime, browserManager),
36848
+ workspace: parsed.options.workspace,
36849
+ url,
36850
+ rootDir,
36851
+ ...parsed.options.output === void 0 ? {} : { outputPath: parsed.options.output }
36852
+ });
36853
+ }
36854
+ async function closeOwnedLocalBrowserSession(runtime, browserManager) {
36855
+ let closeError;
36856
+ try {
36857
+ await runtime.close();
36858
+ } catch (error) {
36859
+ closeError = error;
36860
+ }
36861
+ try {
36862
+ await browserManager.close();
36863
+ } catch (error) {
36864
+ closeError ??= error;
36865
+ }
36866
+ if (closeError !== void 0) {
36867
+ throw closeError;
36868
+ }
36869
+ }
34644
36870
  function buildOperationInput(operation, parsed) {
34645
36871
  if (parsed.options.inputJson !== void 0) {
34646
36872
  return parsed.options.inputJson;
@@ -34721,6 +36947,46 @@ function resolveOperation(command) {
34721
36947
  }
34722
36948
  return void 0;
34723
36949
  }
36950
+ var CLI_OPTION_SPECS = {
36951
+ workspace: { kind: "value" },
36952
+ url: { kind: "value" },
36953
+ output: { kind: "value" },
36954
+ engine: { kind: "value" },
36955
+ provider: { kind: "value" },
36956
+ "cloud-base-url": { kind: "value" },
36957
+ "cloud-api-key": { kind: "value" },
36958
+ "cloud-app-base-url": { kind: "value" },
36959
+ "cloud-profile-id": { kind: "value" },
36960
+ "cloud-profile-reuse-if-active": { kind: "boolean" },
36961
+ json: { kind: "boolean" },
36962
+ agent: { kind: "value", multiple: true },
36963
+ skill: { kind: "value", multiple: true },
36964
+ global: { kind: "boolean" },
36965
+ yes: { kind: "boolean" },
36966
+ copy: { kind: "boolean" },
36967
+ all: { kind: "boolean" },
36968
+ list: { kind: "boolean" },
36969
+ browser: { kind: "value" },
36970
+ "attach-endpoint": { kind: "value" },
36971
+ "attach-header": { kind: "value", multiple: true },
36972
+ "fresh-tab": { kind: "boolean" },
36973
+ headless: { kind: "boolean" },
36974
+ "executable-path": { kind: "value" },
36975
+ arg: { kind: "value", multiple: true },
36976
+ "timeout-ms": { kind: "value" },
36977
+ "context-json": { kind: "value" },
36978
+ "input-json": { kind: "value" },
36979
+ "schema-json": { kind: "value" },
36980
+ "source-user-data-dir": { kind: "value" },
36981
+ "source-profile-directory": { kind: "value" },
36982
+ selector: { kind: "value" },
36983
+ description: { kind: "value" },
36984
+ element: { kind: "value" },
36985
+ text: { kind: "value" },
36986
+ "press-enter": { kind: "boolean" },
36987
+ direction: { kind: "value" },
36988
+ amount: { kind: "value" }
36989
+ };
34724
36990
  function parseCommandLine(argv) {
34725
36991
  const leadingTokens = [];
34726
36992
  let index = 0;
@@ -34734,18 +37000,43 @@ function parseCommandLine(argv) {
34734
37000
  const rawOptions = /* @__PURE__ */ new Map();
34735
37001
  while (index < argv.length) {
34736
37002
  const token = argv[index];
37003
+ if (token === "--") {
37004
+ rest.push(...argv.slice(index + 1));
37005
+ break;
37006
+ }
34737
37007
  if (!token.startsWith("--")) {
34738
37008
  rest.push(token);
34739
37009
  index += 1;
34740
37010
  continue;
34741
37011
  }
34742
- const key = token.slice(2);
34743
- const next = argv[index + 1];
34744
- if (next === void 0 || next.startsWith("--")) {
34745
- rawOptions.set(key, [...rawOptions.get(key) ?? [], "true"]);
37012
+ const separator = token.indexOf("=");
37013
+ const key = token.slice(2, separator === -1 ? void 0 : separator);
37014
+ const spec = CLI_OPTION_SPECS[key];
37015
+ if (spec === void 0) {
37016
+ throw new Error(`Unknown option: --${key}.`);
37017
+ }
37018
+ if (separator !== -1) {
37019
+ const value = token.slice(separator + 1);
37020
+ rawOptions.set(key, [...rawOptions.get(key) ?? [], value]);
34746
37021
  index += 1;
34747
37022
  continue;
34748
37023
  }
37024
+ const next = argv[index + 1];
37025
+ if (spec.kind === "boolean") {
37026
+ if (next === void 0 || next.startsWith("--")) {
37027
+ rawOptions.set(key, [...rawOptions.get(key) ?? [], "true"]);
37028
+ index += 1;
37029
+ continue;
37030
+ }
37031
+ rawOptions.set(key, [...rawOptions.get(key) ?? [], next]);
37032
+ index += 2;
37033
+ continue;
37034
+ }
37035
+ if (next === void 0 || next.startsWith("--")) {
37036
+ throw new Error(
37037
+ `Option "--${key}" requires a value.${next?.startsWith("--") === true ? ` Use "--${key}=<value>" when the value begins with "--".` : ``}`
37038
+ );
37039
+ }
34749
37040
  rawOptions.set(key, [...rawOptions.get(key) ?? [], next]);
34750
37041
  index += 2;
34751
37042
  }
@@ -34776,6 +37067,8 @@ function parseCommandLine(argv) {
34776
37067
  ...timeoutMs === void 0 ? {} : { timeoutMs }
34777
37068
  };
34778
37069
  const workspace = readSingle(rawOptions, "workspace");
37070
+ const url = readSingle(rawOptions, "url");
37071
+ const output = readSingle(rawOptions, "output");
34779
37072
  const sourceUserDataDir = readSingle(rawOptions, "source-user-data-dir");
34780
37073
  const sourceProfileDirectory = readSingle(rawOptions, "source-profile-directory");
34781
37074
  const selector = readSingle(rawOptions, "selector");
@@ -34792,6 +37085,7 @@ function parseCommandLine(argv) {
34792
37085
  const provider = providerValue === void 0 ? void 0 : normalizeOpensteerProviderMode(providerValue, "--provider");
34793
37086
  const cloudBaseUrl = readSingle(rawOptions, "cloud-base-url");
34794
37087
  const cloudApiKey = readSingle(rawOptions, "cloud-api-key");
37088
+ const cloudAppBaseUrl = readSingle(rawOptions, "cloud-app-base-url");
34795
37089
  const cloudProfileId = readSingle(rawOptions, "cloud-profile-id");
34796
37090
  const cloudProfileReuseIfActive = readOptionalBoolean(
34797
37091
  rawOptions,
@@ -34807,10 +37101,13 @@ function parseCommandLine(argv) {
34807
37101
  const list = readOptionalBoolean(rawOptions, "list");
34808
37102
  const options = {
34809
37103
  ...workspace === void 0 ? {} : { workspace },
37104
+ ...url === void 0 ? {} : { url },
37105
+ ...output === void 0 ? {} : { output },
34810
37106
  ...requestedEngineName === void 0 ? {} : { requestedEngineName },
34811
37107
  ...provider === void 0 ? {} : { provider },
34812
37108
  ...cloudBaseUrl === void 0 ? {} : { cloudBaseUrl },
34813
37109
  ...cloudApiKey === void 0 ? {} : { cloudApiKey },
37110
+ ...cloudAppBaseUrl === void 0 ? {} : { cloudAppBaseUrl },
34814
37111
  ...cloudProfileId === void 0 ? {} : { cloudProfileId },
34815
37112
  ...cloudProfileReuseIfActive === void 0 ? {} : { cloudProfileReuseIfActive },
34816
37113
  ...json === void 0 ? {} : { json },
@@ -34895,7 +37192,7 @@ function buildCliRuntimeProvider(parsed, providerMode) {
34895
37192
  return explicitProvider?.mode === "local" ? explicitProvider : void 0;
34896
37193
  }
34897
37194
  const browserProfile = buildCliBrowserProfile(parsed);
34898
- const hasCloudOverrides = parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || browserProfile !== void 0;
37195
+ const hasCloudOverrides = parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudAppBaseUrl !== void 0 || browserProfile !== void 0;
34899
37196
  if (!hasCloudOverrides && explicitProvider?.mode !== "cloud") {
34900
37197
  return void 0;
34901
37198
  }
@@ -34903,11 +37200,12 @@ function buildCliRuntimeProvider(parsed, providerMode) {
34903
37200
  mode: "cloud",
34904
37201
  ...parsed.options.cloudBaseUrl === void 0 ? {} : { baseUrl: parsed.options.cloudBaseUrl },
34905
37202
  ...parsed.options.cloudApiKey === void 0 ? {} : { apiKey: parsed.options.cloudApiKey },
37203
+ ...parsed.options.cloudAppBaseUrl === void 0 ? {} : { appBaseUrl: parsed.options.cloudAppBaseUrl },
34906
37204
  ...browserProfile === void 0 ? {} : { browserProfile }
34907
37205
  };
34908
37206
  }
34909
37207
  function assertCloudCliOptionsMatchProvider(parsed, providerMode) {
34910
- if (providerMode !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
37208
+ if (providerMode !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudAppBaseUrl !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
34911
37209
  throw new Error(
34912
37210
  'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
34913
37211
  );
@@ -35006,6 +37304,9 @@ function resolveCommandLength(tokens) {
35006
37304
  if (tokens[0] === "status") {
35007
37305
  return 1;
35008
37306
  }
37307
+ if (tokens[0] === "record") {
37308
+ return 1;
37309
+ }
35009
37310
  for (let length = Math.min(3, tokens.length); length >= 1; length -= 1) {
35010
37311
  if (OPERATION_ALIASES.has(tokens.slice(0, length).join(" "))) {
35011
37312
  return length;
@@ -35023,6 +37324,7 @@ Usage:
35023
37324
  opensteer click --workspace <id> (--element <n> | --selector <css> | --description <text>)
35024
37325
  opensteer input --workspace <id> --text <value> (--element <n> | --selector <css> | --description <text>)
35025
37326
  opensteer extract --workspace <id> --description <text> [--schema-json <json>]
37327
+ opensteer record --workspace <id> --url <url> [--output <path>]
35026
37328
  opensteer close --workspace <id>
35027
37329
  opensteer status [--workspace <id>] [--json]
35028
37330
 
@@ -35040,9 +37342,12 @@ Common options:
35040
37342
  --help
35041
37343
  --version
35042
37344
  --workspace <id>
37345
+ --url <url>
37346
+ --output <path>
35043
37347
  --provider local|cloud
35044
37348
  --cloud-base-url <url>
35045
37349
  --cloud-api-key <key>
37350
+ --cloud-app-base-url <url>
35046
37351
  --cloud-profile-id <id>
35047
37352
  --cloud-profile-reuse-if-active <true|false>
35048
37353
  --json <true|false>