opensteer 0.8.17 → 0.9.0

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/index.cjs CHANGED
@@ -2392,6 +2392,16 @@ var opensteerNetworkQueryOutputSchema = objectSchema(
2392
2392
  required: ["records"]
2393
2393
  }
2394
2394
  );
2395
+ var opensteerNetworkDetailInputSchema = objectSchema(
2396
+ {
2397
+ recordId: stringSchema({ minLength: 1 }),
2398
+ probe: { type: "boolean" }
2399
+ },
2400
+ {
2401
+ title: "OpensteerNetworkDetailInput",
2402
+ required: ["recordId"]
2403
+ }
2404
+ );
2395
2405
  var opensteerParsedCookieSchema = objectSchema(
2396
2406
  {
2397
2407
  name: stringSchema({ minLength: 1 }),
@@ -2497,7 +2507,7 @@ objectSchema(
2497
2507
  }
2498
2508
  );
2499
2509
  var opensteerSessionFetchTransportSchema = enumSchema(
2500
- ["auto", "direct", "matched-tls", "page"],
2510
+ ["auto", "direct", "matched-tls", "context", "page"],
2501
2511
  {
2502
2512
  title: "OpensteerSessionFetchTransport"
2503
2513
  }
@@ -6281,15 +6291,6 @@ var opensteerPageSnapshotOutputSchema = objectSchema(
6281
6291
  required: ["url", "title", "mode", "html", "counters"]
6282
6292
  }
6283
6293
  );
6284
- var opensteerNetworkDetailInputSchema = objectSchema(
6285
- {
6286
- recordId: stringSchema({ minLength: 1 })
6287
- },
6288
- {
6289
- title: "OpensteerNetworkDetailInput",
6290
- required: ["recordId"]
6291
- }
6292
- );
6293
6294
  var opensteerComputerMouseButtonSchema = enumSchema(
6294
6295
  ["left", "middle", "right"],
6295
6296
  {
@@ -6859,6 +6860,7 @@ var opensteerSemanticOperationSpecificationsBase = [
6859
6860
  case "direct":
6860
6861
  return [];
6861
6862
  case "matched-tls":
6863
+ case "context":
6862
6864
  return ["inspect.cookies"];
6863
6865
  case "page":
6864
6866
  return ["pages.manage"];
@@ -7480,13 +7482,6 @@ var SqliteSavedNetworkStore = class {
7480
7482
  }
7481
7483
  async save(records, options) {
7482
7484
  const database = await this.requireDatabase();
7483
- const readExisting = database.prepare(`
7484
- SELECT record_id
7485
- FROM saved_network_records
7486
- WHERE session_ref = @session_ref
7487
- AND page_ref_key = @page_ref_key
7488
- AND request_id = @request_id
7489
- `);
7490
7485
  const upsertRecord = database.prepare(buildSavedNetworkUpsertSql(options.bodyWriteMode));
7491
7486
  const insertTag = database.prepare(`
7492
7487
  INSERT OR IGNORE INTO saved_network_tags (record_id, tag)
@@ -7497,14 +7492,8 @@ var SqliteSavedNetworkStore = class {
7497
7492
  for (const entry of records) {
7498
7493
  const url = new URL(entry.record.url);
7499
7494
  const pageRefKey = entry.record.pageRef ?? "";
7500
- const existing = readExisting.get({
7501
- session_ref: entry.record.sessionRef,
7502
- page_ref_key: pageRefKey,
7503
- request_id: entry.record.requestId
7504
- }) ?? void 0;
7505
- const recordId = existing?.record_id ?? entry.recordId;
7506
7495
  upsertRecord.run({
7507
- record_id: recordId,
7496
+ record_id: entry.recordId,
7508
7497
  request_id: entry.record.requestId,
7509
7498
  session_ref: entry.record.sessionRef,
7510
7499
  page_ref: entry.record.pageRef ?? null,
@@ -7549,7 +7538,7 @@ var SqliteSavedNetworkStore = class {
7549
7538
  }
7550
7539
  for (const currentTag of tags) {
7551
7540
  const result = insertTag.run({
7552
- record_id: recordId,
7541
+ record_id: entry.recordId,
7553
7542
  tag: currentTag
7554
7543
  });
7555
7544
  savedCount += result.changes ?? 0;
@@ -7652,6 +7641,49 @@ var SqliteSavedNetworkStore = class {
7652
7641
  return cleared;
7653
7642
  });
7654
7643
  }
7644
+ async *iterateBatches(options = {}) {
7645
+ const database = await this.requireDatabase();
7646
+ const batchSize = Math.max(1, Math.min(options.batchSize ?? 500, 1e3));
7647
+ let cursor;
7648
+ while (true) {
7649
+ const rows = database.prepare(
7650
+ `
7651
+ SELECT
7652
+ r.*,
7653
+ GROUP_CONCAT(t.tag, '${TAG_DELIMITER}') AS tags
7654
+ FROM saved_network_records r
7655
+ LEFT JOIN saved_network_tags t
7656
+ ON t.record_id = r.record_id
7657
+ ${cursor === void 0 ? "" : "WHERE r.saved_at > ? OR (r.saved_at = ? AND r.record_id > ?)"}
7658
+ GROUP BY r.record_id
7659
+ ORDER BY r.saved_at ASC, r.record_id ASC
7660
+ LIMIT ?
7661
+ `
7662
+ ).all(
7663
+ ...cursor === void 0 ? [] : [cursor.savedAt, cursor.savedAt, cursor.recordId],
7664
+ batchSize
7665
+ );
7666
+ if (rows.length === 0) {
7667
+ return;
7668
+ }
7669
+ yield rows.map((row) => inflateSavedNetworkRow(row, options.includeBodies ?? true));
7670
+ const lastRow = rows.at(-1);
7671
+ if (lastRow === void 0) {
7672
+ return;
7673
+ }
7674
+ cursor = {
7675
+ savedAt: lastRow.saved_at,
7676
+ recordId: lastRow.record_id
7677
+ };
7678
+ }
7679
+ }
7680
+ close() {
7681
+ if (this.database !== void 0) {
7682
+ closeSqliteDatabase(this.database);
7683
+ this.database = void 0;
7684
+ this.databaseInitialization = void 0;
7685
+ }
7686
+ }
7655
7687
  async requireDatabase() {
7656
7688
  if (this.database) {
7657
7689
  return this.database;
@@ -7736,15 +7768,6 @@ var SqliteSavedNetworkStore = class {
7736
7768
  saved_at INTEGER NOT NULL
7737
7769
  );
7738
7770
 
7739
- CREATE UNIQUE INDEX IF NOT EXISTS saved_network_records_scope_request
7740
- ON saved_network_records (session_ref, page_ref_key, request_id);
7741
-
7742
- CREATE INDEX IF NOT EXISTS saved_network_records_saved_at
7743
- ON saved_network_records (saved_at DESC);
7744
-
7745
- CREATE INDEX IF NOT EXISTS saved_network_records_capture
7746
- ON saved_network_records (capture);
7747
-
7748
7771
  CREATE TABLE IF NOT EXISTS saved_network_tags (
7749
7772
  record_id TEXT NOT NULL REFERENCES saved_network_records(record_id) ON DELETE CASCADE,
7750
7773
  tag TEXT NOT NULL,
@@ -7754,6 +7777,19 @@ var SqliteSavedNetworkStore = class {
7754
7777
  CREATE INDEX IF NOT EXISTS saved_network_tags_tag
7755
7778
  ON saved_network_tags (tag);
7756
7779
  `);
7780
+ database.exec(`DROP INDEX IF EXISTS saved_network_records_scope_request`);
7781
+ database.exec(`
7782
+ CREATE INDEX IF NOT EXISTS saved_network_records_scope_request
7783
+ ON saved_network_records (session_ref, page_ref_key, request_id)
7784
+ `);
7785
+ database.exec(`
7786
+ CREATE INDEX IF NOT EXISTS saved_network_records_saved_at
7787
+ ON saved_network_records (saved_at DESC)
7788
+ `);
7789
+ database.exec(`
7790
+ CREATE INDEX IF NOT EXISTS saved_network_records_capture
7791
+ ON saved_network_records (capture)
7792
+ `);
7757
7793
  this.ensureColumn(
7758
7794
  database,
7759
7795
  "saved_network_records",
@@ -8941,7 +8977,6 @@ var DEFAULT_TIMEOUTS = {
8941
8977
  "dom.extract": 15e3,
8942
8978
  "network.query": 15e3,
8943
8979
  "network.detail": 15e3,
8944
- "network.replay": 3e4,
8945
8980
  "scripts.capture": 15e3,
8946
8981
  "session.cookies": 1e4,
8947
8982
  "session.storage": 1e4,
@@ -15976,8 +16011,14 @@ async function launchOwnedBrowser(input) {
15976
16011
  await ensureDirectory(input.userDataDir);
15977
16012
  await clearChromeSingletonEntries(input.userDataDir);
15978
16013
  await sanitizeChromeProfile(input.userDataDir);
16014
+ const requestedRemoteDebuggingPort = readRequestedRemoteDebuggingPort(input.launch?.args);
15979
16015
  const executablePath = resolveChromeExecutablePath(input.launch?.executablePath);
15980
- const args = buildChromeArgs(input.userDataDir, input.launch, input.viewport);
16016
+ const args = buildChromeArgs(
16017
+ input.userDataDir,
16018
+ input.launch,
16019
+ input.viewport,
16020
+ requestedRemoteDebuggingPort
16021
+ );
15981
16022
  const child = child_process.spawn(executablePath, args, {
15982
16023
  stdio: ["ignore", "ignore", "pipe"],
15983
16024
  detached: process.platform !== "win32"
@@ -15993,7 +16034,8 @@ async function launchOwnedBrowser(input) {
15993
16034
  userDataDir: input.userDataDir,
15994
16035
  timeoutMs: input.launch?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
15995
16036
  childExited: async () => child.exitCode,
15996
- stderrLines
16037
+ stderrLines,
16038
+ ...requestedRemoteDebuggingPort !== void 0 && requestedRemoteDebuggingPort > 0 ? { requestedRemoteDebuggingPort } : {}
15997
16039
  }).catch(async (error) => {
15998
16040
  child.kill("SIGKILL");
15999
16041
  throw error;
@@ -16004,10 +16046,10 @@ async function launchOwnedBrowser(input) {
16004
16046
  executablePath
16005
16047
  };
16006
16048
  }
16007
- function buildChromeArgs(userDataDir, launch, viewport) {
16049
+ function buildChromeArgs(userDataDir, launch, viewport, requestedRemoteDebuggingPort) {
16008
16050
  const isHeadless = launch?.headless ?? true;
16009
16051
  const args = [
16010
- "--remote-debugging-port=0",
16052
+ ...requestedRemoteDebuggingPort === void 0 ? ["--remote-debugging-port=0"] : [],
16011
16053
  "--no-first-run",
16012
16054
  "--no-default-browser-check",
16013
16055
  "--disable-blink-features=AutomationControlled",
@@ -16058,6 +16100,15 @@ async function waitForDevToolsEndpoint(input) {
16058
16100
  return `ws://127.0.0.1:${String(activePort.port)}${activePort.webSocketPath}`;
16059
16101
  }
16060
16102
  }
16103
+ if (input.requestedRemoteDebuggingPort !== void 0) {
16104
+ const endpoint = await tryInspectRemoteDebuggingPort(
16105
+ input.requestedRemoteDebuggingPort,
16106
+ input.timeoutMs
16107
+ );
16108
+ if (endpoint !== void 0) {
16109
+ return endpoint;
16110
+ }
16111
+ }
16061
16112
  const exitCode = await input.childExited();
16062
16113
  if (exitCode !== null) {
16063
16114
  throw new Error(formatChromeLaunchError(input.stderrLines));
@@ -16066,6 +16117,52 @@ async function waitForDevToolsEndpoint(input) {
16066
16117
  }
16067
16118
  throw new Error(formatChromeLaunchError(input.stderrLines));
16068
16119
  }
16120
+ function readRequestedRemoteDebuggingPort(args) {
16121
+ if (args === void 0 || args.length === 0) {
16122
+ return void 0;
16123
+ }
16124
+ let explicitFlagFound = false;
16125
+ let port;
16126
+ for (let index = 0; index < args.length; index += 1) {
16127
+ const entry = args[index];
16128
+ if (entry === "--remote-debugging-port") {
16129
+ explicitFlagFound = true;
16130
+ const next = args[index + 1];
16131
+ if (next !== void 0) {
16132
+ port = parseRemoteDebuggingPort(next);
16133
+ index += 1;
16134
+ }
16135
+ continue;
16136
+ }
16137
+ if (entry.startsWith("--remote-debugging-port=")) {
16138
+ explicitFlagFound = true;
16139
+ port = parseRemoteDebuggingPort(entry.slice("--remote-debugging-port=".length));
16140
+ }
16141
+ }
16142
+ return explicitFlagFound ? port : void 0;
16143
+ }
16144
+ function parseRemoteDebuggingPort(value) {
16145
+ const trimmed = value.trim();
16146
+ if (!/^\d+$/.test(trimmed)) {
16147
+ return void 0;
16148
+ }
16149
+ const parsed = Number.parseInt(trimmed, 10);
16150
+ if (!Number.isInteger(parsed) || parsed < 0) {
16151
+ return void 0;
16152
+ }
16153
+ return parsed;
16154
+ }
16155
+ async function tryInspectRemoteDebuggingPort(port, timeoutMs) {
16156
+ try {
16157
+ const inspected = await inspectCdpEndpoint({
16158
+ endpoint: `http://127.0.0.1:${String(port)}`,
16159
+ timeoutMs: Math.min(2e3, timeoutMs)
16160
+ });
16161
+ return inspected.endpoint;
16162
+ } catch {
16163
+ return void 0;
16164
+ }
16165
+ }
16069
16166
  function formatChromeLaunchError(stderrLines) {
16070
16167
  const collapsed = stderrLines.join("").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
16071
16168
  if (collapsed.length === 0) {
@@ -16790,21 +16887,24 @@ var OpensteerCloudClient = class {
16790
16887
  getConfig() {
16791
16888
  return this.config;
16792
16889
  }
16793
- async createSession(input = {}) {
16794
- const response = await this.request("/v1/sessions", {
16795
- method: "POST",
16796
- body: {
16797
- ...input.name === void 0 ? {} : { name: input.name },
16798
- ...input.browser === void 0 ? {} : { browser: input.browser },
16799
- ...input.context === void 0 ? {} : { context: input.context },
16800
- ...input.browserProfile === void 0 ? {} : { browserProfile: input.browserProfile },
16801
- ...input.observability === void 0 ? {} : { observability: input.observability },
16802
- ...input.sourceType === void 0 ? {} : { sourceType: input.sourceType },
16803
- ...input.sourceRef === void 0 ? {} : { sourceRef: input.sourceRef },
16804
- ...input.localWorkspaceRootPath === void 0 ? {} : { localWorkspaceRootPath: input.localWorkspaceRootPath },
16805
- ...input.locality === void 0 ? {} : { locality: input.locality }
16806
- }
16807
- });
16890
+ async createSession(input = {}, options = {}) {
16891
+ const response = await this.request(
16892
+ "/v1/sessions",
16893
+ {
16894
+ method: "POST",
16895
+ body: {
16896
+ ...input.name === void 0 ? {} : { name: input.name },
16897
+ ...input.browser === void 0 ? {} : { browser: input.browser },
16898
+ ...input.context === void 0 ? {} : { context: input.context },
16899
+ ...input.browserProfile === void 0 ? {} : { browserProfile: input.browserProfile },
16900
+ ...input.observability === void 0 ? {} : { observability: input.observability },
16901
+ ...input.sourceType === void 0 ? {} : { sourceType: input.sourceType },
16902
+ ...input.sourceRef === void 0 ? {} : { sourceRef: input.sourceRef },
16903
+ ...input.localWorkspaceRootPath === void 0 ? {} : { localWorkspaceRootPath: input.localWorkspaceRootPath }
16904
+ }
16905
+ },
16906
+ options
16907
+ );
16808
16908
  return await response.json();
16809
16909
  }
16810
16910
  async listSessions() {
@@ -16813,19 +16913,27 @@ var OpensteerCloudClient = class {
16813
16913
  });
16814
16914
  return response.json();
16815
16915
  }
16816
- async getSession(sessionId) {
16817
- const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}`, {
16818
- method: "GET"
16819
- });
16916
+ async getSession(sessionId, options = {}) {
16917
+ const response = await this.request(
16918
+ `/v1/sessions/${encodeURIComponent(sessionId)}`,
16919
+ {
16920
+ method: "GET"
16921
+ },
16922
+ options
16923
+ );
16820
16924
  return await response.json();
16821
16925
  }
16822
- async issueAccess(sessionId, capabilities) {
16823
- const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}/access`, {
16824
- method: "POST",
16825
- body: {
16826
- capabilities
16827
- }
16828
- });
16926
+ async issueAccess(sessionId, capabilities, options = {}) {
16927
+ const response = await this.request(
16928
+ `/v1/sessions/${encodeURIComponent(sessionId)}/access`,
16929
+ {
16930
+ method: "POST",
16931
+ body: {
16932
+ capabilities
16933
+ }
16934
+ },
16935
+ options
16936
+ );
16829
16937
  return await response.json();
16830
16938
  }
16831
16939
  async getSessionRecording(sessionId) {
@@ -16907,28 +17015,17 @@ var OpensteerCloudClient = class {
16907
17015
  async syncBrowserProfileCookies(input) {
16908
17016
  return syncBrowserProfileCookies(this, input);
16909
17017
  }
16910
- async importSelectorCache(entries) {
16911
- const response = await this.request("/selector-cache/import", {
17018
+ async importDescriptors(entries) {
17019
+ const response = await this.request("/registry/descriptors/import", {
16912
17020
  method: "POST",
16913
- body: {
16914
- entries: entries.map((entry) => ({
16915
- workspace: entry.workspace,
16916
- method: entry.method,
16917
- persistHash: entry.persistHash,
16918
- ...entry.persist === void 0 ? {} : { persist: entry.persist },
16919
- path: entry.path,
16920
- ...entry.schemaHash === void 0 ? {} : { schemaHash: entry.schemaHash },
16921
- createdAt: entry.createdAt,
16922
- updatedAt: entry.updatedAt
16923
- }))
16924
- }
17021
+ body: { entries }
16925
17022
  });
16926
17023
  return await response.json();
16927
17024
  }
16928
- async importDescriptors(entries) {
16929
- const response = await this.request("/registry/descriptors/import", {
17025
+ async importRequestPlans(input) {
17026
+ const response = await this.request("/registry/request-plans/import", {
16930
17027
  method: "POST",
16931
- body: { entries }
17028
+ body: input
16932
17029
  });
16933
17030
  return await response.json();
16934
17031
  }
@@ -16941,7 +17038,7 @@ var OpensteerCloudClient = class {
16941
17038
  "content-type": "application/json; charset=utf-8"
16942
17039
  };
16943
17040
  }
16944
- async request(pathname, init) {
17041
+ async request(pathname, init, options = {}) {
16945
17042
  const url = `${this.config.baseUrl}${pathname}`;
16946
17043
  let response;
16947
17044
  try {
@@ -16949,7 +17046,7 @@ var OpensteerCloudClient = class {
16949
17046
  method: init.method,
16950
17047
  headers: this.buildHeaders(),
16951
17048
  ...init.body === void 0 ? {} : { body: JSON.stringify(init.body) },
16952
- signal: AbortSignal.timeout(3e4)
17049
+ signal: createRequestSignal(options)
16953
17050
  });
16954
17051
  } catch (error) {
16955
17052
  throw wrapCloudFetchError(error, {
@@ -16979,6 +17076,13 @@ var OpensteerCloudClient = class {
16979
17076
  throw new Error(`Timed out waiting for cloud session ${sessionId} to close.`);
16980
17077
  }
16981
17078
  };
17079
+ function createRequestSignal(options) {
17080
+ const timeoutSignal = AbortSignal.timeout(options.timeoutMs ?? 3e4);
17081
+ if (options.signal === void 0) {
17082
+ return timeoutSignal;
17083
+ }
17084
+ return AbortSignal.any([options.signal, timeoutSignal]);
17085
+ }
16982
17086
  function delay(ms) {
16983
17087
  return new Promise((resolve4) => {
16984
17088
  setTimeout(resolve4, ms);
@@ -17029,7 +17133,7 @@ function resolveCloudConfig(input = {}) {
17029
17133
 
17030
17134
  // ../runtime-core/package.json
17031
17135
  var package_default = {
17032
- version: "0.1.7"};
17136
+ version: "0.2.0"};
17033
17137
 
17034
17138
  // ../runtime-core/src/version.ts
17035
17139
  var OPENSTEER_RUNTIME_CORE_VERSION = package_default.version;
@@ -20847,6 +20951,9 @@ var MUTATION_CAPTURE_FINALIZE_TIMEOUT_MS = 5e3;
20847
20951
  var PERSISTED_NETWORK_FLUSH_TIMEOUT_MS = 5e3;
20848
20952
  var PENDING_OPERATION_EVENT_CAPTURE_LIMIT = 64;
20849
20953
  var PENDING_OPERATION_EVENT_CAPTURE_SKEW_MS = 1e3;
20954
+ var REPLAY_PROBE_MIN_ATTEMPT_TIMEOUT_MS = 3e3;
20955
+ var REPLAY_PROBE_MAX_ATTEMPT_TIMEOUT_MS = 15e3;
20956
+ var REPLAY_PROBE_POST_SUCCESS_ATTEMPT_TIMEOUT_MS = 5e3;
20850
20957
  var OpensteerSessionRuntime = class {
20851
20958
  workspace;
20852
20959
  rootPath;
@@ -21708,26 +21815,27 @@ var OpensteerSessionRuntime = class {
21708
21815
  }
21709
21816
  }
21710
21817
  async queryNetwork(input = {}, options = {}) {
21711
- assertValidSemanticOperationInput("network.query", input);
21818
+ const normalizedInput = normalizeNetworkQueryInput(input);
21819
+ assertValidSemanticOperationInput("network.query", normalizedInput);
21712
21820
  const root = await this.ensureRoot();
21713
21821
  const startedAt = Date.now();
21714
21822
  try {
21715
21823
  const output = await this.runWithOperationTimeout(
21716
21824
  "network.query",
21717
21825
  async (timeout) => {
21718
- await this.syncPersistedNetworkSelection(timeout, input, {
21826
+ await this.syncPersistedNetworkSelection(timeout, normalizedInput, {
21719
21827
  includeBodies: false
21720
21828
  });
21721
21829
  const rawRecords = await timeout.runStep(
21722
21830
  () => root.registry.savedNetwork.query({
21723
- ...this.toSavedNetworkQueryInput(input),
21724
- limit: Math.max(input.limit ?? 50, 1e3)
21831
+ ...this.toSavedNetworkQueryInput(normalizedInput),
21832
+ limit: Math.max(normalizedInput.limit ?? 50, 1e3)
21725
21833
  })
21726
21834
  );
21727
- const filtered = filterNetworkSummaryRecords(rawRecords, input);
21835
+ const filtered = filterNetworkSummaryRecords(rawRecords, normalizedInput);
21728
21836
  const sorted = sortPersistedNetworkRecordsChronologically(filtered);
21729
- const sliced = sliceNetworkSummaryWindow(sorted, input);
21730
- const limited = sliced.slice(0, Math.max(1, Math.min(input.limit ?? 50, 200)));
21837
+ const sliced = sliceNetworkSummaryWindow(sorted, normalizedInput);
21838
+ const limited = sliced.slice(0, Math.max(1, Math.min(normalizedInput.limit ?? 50, 200)));
21731
21839
  const summaries = await this.buildNetworkSummaryRecords(limited, timeout);
21732
21840
  return {
21733
21841
  records: summaries
@@ -21741,9 +21849,9 @@ var OpensteerSessionRuntime = class {
21741
21849
  completedAt: Date.now(),
21742
21850
  outcome: "ok",
21743
21851
  data: {
21744
- limit: input.limit ?? 50,
21745
- ...input.capture === void 0 ? {} : { capture: input.capture },
21746
- ...input.json === true ? { json: true } : {},
21852
+ limit: normalizedInput.limit ?? 50,
21853
+ ...normalizedInput.capture === void 0 ? {} : { capture: normalizedInput.capture },
21854
+ ...normalizedInput.json === true ? { json: true } : {},
21747
21855
  count: output.records.length
21748
21856
  },
21749
21857
  context: buildRuntimeTraceContext({
@@ -21768,12 +21876,13 @@ var OpensteerSessionRuntime = class {
21768
21876
  }
21769
21877
  }
21770
21878
  async getNetworkDetail(input, options = {}) {
21879
+ const normalizedRecordId = normalizeNetworkRecordId(input.recordId);
21771
21880
  const startedAt = Date.now();
21772
21881
  try {
21773
21882
  const output = await this.runWithOperationTimeout(
21774
21883
  "network.detail",
21775
21884
  async (timeout) => {
21776
- const record = await this.resolveNetworkRecordByRecordId(input.recordId, timeout, {
21885
+ const record = await this.resolveNetworkRecordByRecordId(normalizedRecordId, timeout, {
21777
21886
  includeBodies: true,
21778
21887
  redactSecretHeaders: false
21779
21888
  });
@@ -21792,8 +21901,8 @@ var OpensteerSessionRuntime = class {
21792
21901
  completedAt: Date.now(),
21793
21902
  outcome: "ok",
21794
21903
  data: {
21795
- recordId: input.recordId,
21796
- status: output.summary.status,
21904
+ recordId: normalizedRecordId,
21905
+ ...output.summary.status === void 0 ? {} : { status: output.summary.status },
21797
21906
  url: output.summary.url
21798
21907
  },
21799
21908
  context: buildRuntimeTraceContext({
@@ -23197,7 +23306,9 @@ var OpensteerSessionRuntime = class {
23197
23306
  ...graphql.persisted === void 0 ? {} : { persisted: graphql.persisted },
23198
23307
  ...graphqlVariables === void 0 ? {} : { variables: graphqlVariables }
23199
23308
  };
23200
- const requestBody = shouldShowRequestBody(record.record.method) && record.record.requestBody !== void 0 ? buildStructuredBodyPreview(record.record.requestBody, record.record.requestHeaders) : void 0;
23309
+ const requestBody = shouldShowRequestBody(record.record.method) && record.record.requestBody !== void 0 ? buildStructuredBodyPreview(record.record.requestBody, record.record.requestHeaders, {
23310
+ truncateData: false
23311
+ }) : void 0;
23201
23312
  const responseBody = record.record.responseBody === void 0 ? void 0 : buildStructuredBodyPreview(record.record.responseBody, record.record.responseHeaders);
23202
23313
  const notes = detectNetworkRecordNotes(record);
23203
23314
  return {
@@ -23227,8 +23338,18 @@ var OpensteerSessionRuntime = class {
23227
23338
  let recommended;
23228
23339
  for (const transport of REPLAY_TRANSPORT_LADDER) {
23229
23340
  const attemptStartedAt = Date.now();
23341
+ const attemptTimeoutMs = resolveReplayProbeAttemptTimeoutMs({
23342
+ remainingMs: timeout.remainingMs(),
23343
+ transportsRemaining: REPLAY_TRANSPORT_LADDER.length - attempts.length,
23344
+ recommendedFound: recommended !== void 0
23345
+ });
23230
23346
  try {
23231
- const output = await this.executeReplayTransportAttempt(transport, request, timeout);
23347
+ const output = await this.executeReplayTransportAttemptWithinBudget(
23348
+ transport,
23349
+ request,
23350
+ timeout,
23351
+ attemptTimeoutMs
23352
+ );
23232
23353
  const ok = matchesSuccessFingerprintFromProtocolResponse(output.response, fingerprint);
23233
23354
  attempts.push({
23234
23355
  transport,
@@ -23244,7 +23365,7 @@ var OpensteerSessionRuntime = class {
23244
23365
  transport,
23245
23366
  ok: false,
23246
23367
  durationMs: Date.now() - attemptStartedAt,
23247
- error: normalizeRuntimeErrorMessage(error)
23368
+ error: normalizeProbeTransportAttemptError(transport, error, attemptTimeoutMs)
23248
23369
  });
23249
23370
  }
23250
23371
  }
@@ -23448,6 +23569,23 @@ var OpensteerSessionRuntime = class {
23448
23569
  }
23449
23570
  }
23450
23571
  }
23572
+ async executeReplayTransportAttemptWithinBudget(transport, request, timeout, attemptTimeoutMs) {
23573
+ if (attemptTimeoutMs === void 0) {
23574
+ return this.executeReplayTransportAttempt(transport, request, timeout);
23575
+ }
23576
+ return runWithPolicyTimeout(
23577
+ {
23578
+ resolveTimeoutMs() {
23579
+ return attemptTimeoutMs;
23580
+ }
23581
+ },
23582
+ {
23583
+ operation: timeout.operation,
23584
+ signal: timeout.signal
23585
+ },
23586
+ (attemptTimeout) => this.executeReplayTransportAttempt(transport, request, attemptTimeout)
23587
+ );
23588
+ }
23451
23589
  async executeFetchTransportAttempt(transport, request, timeout, input) {
23452
23590
  let prepared = finalizeMaterializedTransportRequest(request, transport);
23453
23591
  if (input.cookies !== false && transport === "direct-http" && this.currentBinding() !== void 0) {
@@ -24362,10 +24500,15 @@ var OpensteerSessionRuntime = class {
24362
24500
  return this.observationSessionId ?? this.sessionRef;
24363
24501
  }
24364
24502
  runWithOperationTimeout(operation, callback, options = {}) {
24503
+ const timeoutPolicy = options.timeoutMs === void 0 ? this.policy.timeout : {
24504
+ resolveTimeoutMs() {
24505
+ return options.timeoutMs;
24506
+ }
24507
+ };
24365
24508
  const existingCollector = this.operationEventStorage.getStore();
24366
24509
  if (existingCollector !== void 0) {
24367
24510
  return runWithPolicyTimeout(
24368
- this.policy.timeout,
24511
+ timeoutPolicy,
24369
24512
  {
24370
24513
  operation,
24371
24514
  ...options.signal === void 0 ? {} : { signal: options.signal }
@@ -24378,7 +24521,7 @@ var OpensteerSessionRuntime = class {
24378
24521
  return this.operationEventStorage.run(collector, async () => {
24379
24522
  try {
24380
24523
  return await runWithPolicyTimeout(
24381
- this.policy.timeout,
24524
+ timeoutPolicy,
24382
24525
  {
24383
24526
  operation,
24384
24527
  ...options.signal === void 0 ? {} : { signal: options.signal }
@@ -24548,6 +24691,21 @@ function buildEngineNetworkRecordFilters(input) {
24548
24691
  function normalizeNetworkStatusFilter(status) {
24549
24692
  return String(status);
24550
24693
  }
24694
+ function normalizeNetworkQueryInput(input) {
24695
+ return {
24696
+ ...input,
24697
+ ...input.recordId === void 0 ? {} : { recordId: normalizeNetworkRecordId(input.recordId) },
24698
+ ...input.before === void 0 ? {} : { before: normalizeNetworkRecordId(input.before) },
24699
+ ...input.after === void 0 ? {} : { after: normalizeNetworkRecordId(input.after) }
24700
+ };
24701
+ }
24702
+ function normalizeNetworkRecordId(recordId) {
24703
+ const trimmed = recordId.trim();
24704
+ if (trimmed.length === 0 || trimmed.startsWith("record:")) {
24705
+ return trimmed;
24706
+ }
24707
+ return `record:${trimmed}`;
24708
+ }
24551
24709
  function resolveLiveQueryRequestIds(input, history) {
24552
24710
  const requestIdCandidates = [];
24553
24711
  if (input.recordId !== void 0) {
@@ -24760,6 +24918,20 @@ var REPLAY_TRANSPORT_LADDER = [
24760
24918
  "context-http",
24761
24919
  "page-http"
24762
24920
  ];
24921
+ function resolveReplayProbeAttemptTimeoutMs(input) {
24922
+ const attemptCapMs = input.recommendedFound ? REPLAY_PROBE_POST_SUCCESS_ATTEMPT_TIMEOUT_MS : REPLAY_PROBE_MAX_ATTEMPT_TIMEOUT_MS;
24923
+ const clampedRemaining = input.remainingMs === void 0 ? void 0 : Math.max(0, input.remainingMs);
24924
+ if (clampedRemaining === 0) {
24925
+ return 0;
24926
+ }
24927
+ if (clampedRemaining === void 0) {
24928
+ return attemptCapMs;
24929
+ }
24930
+ const sliceMs = Math.floor(clampedRemaining / Math.max(1, input.transportsRemaining));
24931
+ const minimumBudgetAffordable = clampedRemaining >= REPLAY_PROBE_MIN_ATTEMPT_TIMEOUT_MS * input.transportsRemaining;
24932
+ const attemptBudgetMs = minimumBudgetAffordable ? Math.max(REPLAY_PROBE_MIN_ATTEMPT_TIMEOUT_MS, sliceMs) : sliceMs;
24933
+ return Math.min(clampedRemaining, attemptCapMs, Math.max(1, attemptBudgetMs));
24934
+ }
24763
24935
  function filterNetworkSummaryRecords(records, input) {
24764
24936
  return records.filter((record) => {
24765
24937
  if (record.record.resourceType === "preflight" || record.record.method === "OPTIONS") {
@@ -24932,10 +25104,10 @@ function extractGraphqlOperationName(queryText) {
24932
25104
  function shouldShowRequestBody(method) {
24933
25105
  return !["GET", "HEAD", "DELETE", "OPTIONS"].includes(method.trim().toUpperCase());
24934
25106
  }
24935
- function buildStructuredBodyPreview(body, headers) {
25107
+ function buildStructuredBodyPreview(body, headers, options = {}) {
24936
25108
  const contentType = headerValue(headers, "content-type") ?? body?.mimeType;
24937
25109
  const parsed = parseStructuredPayload(body, contentType);
24938
- const data = parsed === void 0 ? void 0 : typeof parsed === "string" ? truncateInlineText(parsed) : truncateStructuredValue(parsed);
25110
+ const data = parsed === void 0 ? void 0 : options.truncateData === false ? parsed : typeof parsed === "string" ? truncateInlineText(parsed) : truncateStructuredValue(parsed);
24939
25111
  return {
24940
25112
  bytes: body?.originalByteLength ?? body?.capturedByteLength ?? 0,
24941
25113
  ...contentType === void 0 ? {} : { contentType },
@@ -25148,10 +25320,12 @@ function resolveSessionFetchTransportLadder(transport) {
25148
25320
  return ["direct-http"];
25149
25321
  case "matched-tls":
25150
25322
  return ["matched-tls"];
25323
+ case "context":
25324
+ return ["context-http"];
25151
25325
  case "page":
25152
25326
  return ["page-http"];
25153
25327
  case "auto":
25154
- return ["direct-http", "matched-tls", "page-http"];
25328
+ return ["direct-http", "matched-tls", "context-http", "page-http"];
25155
25329
  }
25156
25330
  }
25157
25331
  function detectChallengeNoteFromRecord(record) {
@@ -25296,6 +25470,12 @@ function diffStorageSnapshot(left, right) {
25296
25470
  function normalizeRuntimeErrorMessage(error) {
25297
25471
  return error instanceof Error ? error.message : String(error);
25298
25472
  }
25473
+ function normalizeProbeTransportAttemptError(transport, error, attemptTimeoutMs) {
25474
+ if (attemptTimeoutMs !== void 0 && error instanceof OpensteerProtocolError && error.code === "timeout") {
25475
+ return `${transport} probe exceeded ${String(attemptTimeoutMs)}ms`;
25476
+ }
25477
+ return normalizeRuntimeErrorMessage(error);
25478
+ }
25299
25479
  function applyBrowserCookiesToTransportRequest(request, cookies) {
25300
25480
  if (cookies.length === 0) {
25301
25481
  return request;
@@ -26063,10 +26243,10 @@ var OpensteerSemanticRestClient = class {
26063
26243
  constructor(connection) {
26064
26244
  this.connection = connection;
26065
26245
  }
26066
- async invoke(operation, input) {
26067
- return this.invokeInternal(operation, input, false);
26246
+ async invoke(operation, input, options = {}) {
26247
+ return this.invokeInternal(operation, input, false, options);
26068
26248
  }
26069
- async invokeInternal(operation, input, hasRetried) {
26249
+ async invokeInternal(operation, input, hasRetried, options) {
26070
26250
  const endpoint = opensteerSemanticRestEndpoints.find((entry) => entry.name === operation);
26071
26251
  if (!endpoint) {
26072
26252
  throw new Error(`unsupported semantic operation ${operation}`);
@@ -26080,10 +26260,11 @@ var OpensteerSemanticRestClient = class {
26080
26260
  method: "POST",
26081
26261
  headers: {
26082
26262
  authorization: await this.connection.getAuthorizationHeader(),
26083
- "content-type": "application/json; charset=utf-8"
26263
+ "content-type": "application/json; charset=utf-8",
26264
+ ...options.timeoutMs === void 0 ? {} : { "x-opensteer-timeout-ms": String(options.timeoutMs) }
26084
26265
  },
26085
26266
  body: JSON.stringify(request),
26086
- signal: AbortSignal.timeout(3e4)
26267
+ signal: createRequestSignal2(options)
26087
26268
  });
26088
26269
  } catch (error) {
26089
26270
  if (operation === "session.close" && isFetchFailure(error)) {
@@ -26099,7 +26280,7 @@ var OpensteerSemanticRestClient = class {
26099
26280
  return envelope.data;
26100
26281
  } catch (error) {
26101
26282
  if (!hasRetried && this.connection.handleError && await this.connection.handleError(error, { operation })) {
26102
- return this.invokeInternal(operation, input, true);
26283
+ return this.invokeInternal(operation, input, true, options);
26103
26284
  }
26104
26285
  if (operation === "session.close" && isFetchFailure(error)) {
26105
26286
  return { closed: true };
@@ -26111,6 +26292,13 @@ var OpensteerSemanticRestClient = class {
26111
26292
  return this.invoke("session.close", {});
26112
26293
  }
26113
26294
  };
26295
+ function createRequestSignal2(options) {
26296
+ const timeoutSignal = AbortSignal.timeout(options.timeoutMs ?? 3e4);
26297
+ if (options.signal === void 0) {
26298
+ return timeoutSignal;
26299
+ }
26300
+ return AbortSignal.any([options.signal, timeoutSignal]);
26301
+ }
26114
26302
  function isFetchFailure(error) {
26115
26303
  if (!(error instanceof Error)) {
26116
26304
  return false;
@@ -26477,13 +26665,44 @@ function asRecord(value) {
26477
26665
  return value;
26478
26666
  }
26479
26667
 
26480
- // src/cloud/registry-sync.ts
26481
- var REGISTRY_SYNC_MAX_PAYLOAD_BYTES = 15e5;
26482
- var REGISTRY_SYNC_MAX_ENTRIES_PER_BATCH = 100;
26483
- async function syncLocalRegistryToCloud(client, workspace, store) {
26668
+ // src/cloud/workspace-sync.ts
26669
+ var WORKSPACE_SYNC_MAX_PAYLOAD_BYTES = 15e5;
26670
+ var WORKSPACE_SYNC_MAX_ENTRIES_PER_BATCH = 100;
26671
+ async function syncLocalWorkspaceToCloud(client, workspace, store) {
26672
+ await syncDescriptorRegistryToCloud(client, workspace, store);
26673
+ await syncRequestPlansToCloud(client, workspace, store);
26674
+ }
26675
+ async function syncDescriptorRegistryToCloud(client, workspace, store) {
26484
26676
  const descriptors = await store.registry.descriptors.list();
26485
- const descriptorEntries = descriptors.map((record) => toDescriptorImportEntry(workspace, record));
26486
- await importInBatches(descriptorEntries, (entries) => client.importDescriptors(entries));
26677
+ const entries = descriptors.map((record) => toDescriptorImportEntry(workspace, record));
26678
+ await importInBatches(entries, {
26679
+ getPayloadByteLength: (batch) => payloadByteLength({ entries: batch }),
26680
+ importBatch: (batch) => client.importDescriptors(batch)
26681
+ });
26682
+ }
26683
+ async function syncRequestPlansToCloud(client, workspace, store) {
26684
+ const requestPlans = await store.registry.requestPlans.list();
26685
+ const entries = requestPlans.map((record) => toRequestPlanImportEntry(workspace, record)).filter((entry) => entry !== void 0);
26686
+ await importInBatches(entries, {
26687
+ getPayloadByteLength: (batch) => payloadByteLength({ entries: batch }),
26688
+ importBatch: (batch) => client.importRequestPlans({ entries: batch })
26689
+ });
26690
+ }
26691
+ function toRequestPlanImportEntry(workspace, record) {
26692
+ const entry = {
26693
+ workspace,
26694
+ recordId: record.id,
26695
+ key: record.key,
26696
+ version: record.version,
26697
+ contentHash: record.contentHash,
26698
+ tags: [...record.tags],
26699
+ ...record.provenance === void 0 ? {} : { provenance: record.provenance },
26700
+ ...record.freshness === void 0 ? {} : { freshness: record.freshness },
26701
+ payload: record.payload,
26702
+ createdAt: record.createdAt,
26703
+ updatedAt: record.updatedAt
26704
+ };
26705
+ return payloadByteLength({ entries: [entry] }) <= WORKSPACE_SYNC_MAX_PAYLOAD_BYTES ? entry : void 0;
26487
26706
  }
26488
26707
  function toDescriptorImportEntry(workspace, record) {
26489
26708
  return {
@@ -26499,19 +26718,19 @@ function toDescriptorImportEntry(workspace, record) {
26499
26718
  updatedAt: record.updatedAt
26500
26719
  };
26501
26720
  }
26502
- async function importInBatches(entries, importBatch) {
26721
+ async function importInBatches(entries, options) {
26503
26722
  if (entries.length === 0) {
26504
26723
  return;
26505
26724
  }
26506
- for (const batch of chunkEntries(entries)) {
26507
- await importBatch(batch);
26725
+ for (const batch of chunkEntries(entries, options.getPayloadByteLength)) {
26726
+ await options.importBatch(batch);
26508
26727
  }
26509
26728
  }
26510
- function chunkEntries(entries) {
26729
+ function chunkEntries(entries, getPayloadByteLength) {
26511
26730
  const batches = [];
26512
26731
  let currentBatch = [];
26513
26732
  for (const entry of entries) {
26514
- if (payloadByteLength([entry]) > REGISTRY_SYNC_MAX_PAYLOAD_BYTES) {
26733
+ if (getPayloadByteLength([entry]) > WORKSPACE_SYNC_MAX_PAYLOAD_BYTES) {
26515
26734
  continue;
26516
26735
  }
26517
26736
  if (currentBatch.length === 0) {
@@ -26519,7 +26738,7 @@ function chunkEntries(entries) {
26519
26738
  continue;
26520
26739
  }
26521
26740
  const nextBatch = [...currentBatch, entry];
26522
- if (nextBatch.length > REGISTRY_SYNC_MAX_ENTRIES_PER_BATCH || payloadByteLength(nextBatch) > REGISTRY_SYNC_MAX_PAYLOAD_BYTES) {
26741
+ if (nextBatch.length > WORKSPACE_SYNC_MAX_ENTRIES_PER_BATCH || getPayloadByteLength(nextBatch) > WORKSPACE_SYNC_MAX_PAYLOAD_BYTES) {
26523
26742
  batches.push(currentBatch);
26524
26743
  currentBatch = [entry];
26525
26744
  continue;
@@ -26531,8 +26750,8 @@ function chunkEntries(entries) {
26531
26750
  }
26532
26751
  return batches;
26533
26752
  }
26534
- function payloadByteLength(entries) {
26535
- return Buffer.byteLength(JSON.stringify({ entries }), "utf8");
26753
+ function payloadByteLength(value) {
26754
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
26536
26755
  }
26537
26756
 
26538
26757
  // src/cloud/session-proxy.ts
@@ -26543,14 +26762,17 @@ var CloudSessionProxy = class {
26543
26762
  cleanupRootOnClose;
26544
26763
  cloud;
26545
26764
  observability;
26765
+ policy;
26546
26766
  sessionId;
26547
26767
  semanticGrant;
26548
26768
  client;
26549
26769
  automation;
26550
26770
  workspaceStore;
26771
+ syncWorkspaceOnClose = false;
26551
26772
  constructor(cloud, options = {}) {
26552
26773
  this.cloud = cloud;
26553
26774
  this.workspace = options.workspace;
26775
+ this.policy = options.policy ?? defaultPolicy();
26554
26776
  this.observability = options.observability;
26555
26777
  this.rootPath = options.rootPath ?? (this.workspace === void 0 ? path7__default.default.join(os.tmpdir(), `${TEMPORARY_CLOUD_WORKSPACE_PREFIX}${crypto.randomUUID()}`) : resolveFilesystemWorkspacePath({
26556
26778
  rootDir: path7__default.default.resolve(options.rootDir ?? process.cwd()),
@@ -26559,14 +26781,17 @@ var CloudSessionProxy = class {
26559
26781
  this.cleanupRootOnClose = options.cleanupRootOnClose ?? this.workspace === void 0;
26560
26782
  }
26561
26783
  async open(input = {}) {
26562
- await this.ensureSession({
26563
- ...input.browser === void 0 ? {} : { browser: input.browser },
26564
- ...input.launch === void 0 ? {} : { launch: input.launch },
26565
- ...input.context === void 0 ? {} : { context: input.context }
26566
- });
26567
- return this.requireClient().invoke("session.open", {
26568
- ...input.url === void 0 ? {} : { url: input.url }
26569
- });
26784
+ return this.invokeSemanticOperation(
26785
+ "session.open",
26786
+ {
26787
+ ...input.url === void 0 ? {} : { url: input.url }
26788
+ },
26789
+ {
26790
+ ...input.browser === void 0 ? {} : { browser: input.browser },
26791
+ ...input.launch === void 0 ? {} : { launch: input.launch },
26792
+ ...input.context === void 0 ? {} : { context: input.context }
26793
+ }
26794
+ );
26570
26795
  }
26571
26796
  async info() {
26572
26797
  const persisted = this.client !== void 0 || this.sessionId !== void 0 ? void 0 : await this.loadPersistedSession();
@@ -26609,108 +26834,88 @@ var CloudSessionProxy = class {
26609
26834
  };
26610
26835
  }
26611
26836
  async listPages(input = {}) {
26612
- await this.ensureSession();
26613
- return this.requireClient().invoke("page.list", input);
26837
+ return this.invokeSemanticOperation("page.list", input);
26614
26838
  }
26615
26839
  async newPage(input = {}) {
26616
- await this.ensureSession();
26617
- return this.requireAutomation().invoke("page.new", input);
26840
+ return this.invokeAutomationOperation(
26841
+ "page.new",
26842
+ (automation) => automation.invoke("page.new", input)
26843
+ );
26618
26844
  }
26619
26845
  async activatePage(input) {
26620
- await this.ensureSession();
26621
- return this.requireClient().invoke("page.activate", input);
26846
+ return this.invokeSemanticOperation("page.activate", input);
26622
26847
  }
26623
26848
  async closePage(input = {}) {
26624
- await this.ensureSession();
26625
- return this.requireClient().invoke("page.close", input);
26849
+ return this.invokeSemanticOperation("page.close", input);
26626
26850
  }
26627
26851
  async goto(input) {
26628
- await this.ensureSession();
26629
- return this.requireClient().invoke("page.goto", input);
26852
+ return this.invokeSemanticOperation("page.goto", input);
26630
26853
  }
26631
26854
  async evaluate(input) {
26632
- await this.ensureSession();
26633
- return this.requireAutomation().invoke("page.evaluate", input);
26855
+ return this.invokeAutomationOperation(
26856
+ "page.evaluate",
26857
+ (automation) => automation.invoke("page.evaluate", input)
26858
+ );
26634
26859
  }
26635
26860
  async addInitScript(input) {
26636
- await this.ensureSession();
26637
- return this.requireClient().invoke("page.add-init-script", input);
26861
+ return this.invokeSemanticOperation("page.add-init-script", input);
26638
26862
  }
26639
26863
  async snapshot(input = {}) {
26640
- await this.ensureSession();
26641
- return this.requireClient().invoke("page.snapshot", input);
26864
+ return this.invokeSemanticOperation("page.snapshot", input);
26642
26865
  }
26643
26866
  async click(input) {
26644
- await this.ensureSession();
26645
- return this.requireClient().invoke("dom.click", input);
26867
+ return this.invokeSemanticOperation("dom.click", input);
26646
26868
  }
26647
26869
  async hover(input) {
26648
- await this.ensureSession();
26649
- return this.requireClient().invoke("dom.hover", input);
26870
+ return this.invokeSemanticOperation("dom.hover", input);
26650
26871
  }
26651
26872
  async input(input) {
26652
- await this.ensureSession();
26653
- return this.requireClient().invoke("dom.input", input);
26873
+ return this.invokeSemanticOperation("dom.input", input);
26654
26874
  }
26655
26875
  async scroll(input) {
26656
- await this.ensureSession();
26657
- return this.requireClient().invoke("dom.scroll", input);
26876
+ return this.invokeSemanticOperation("dom.scroll", input);
26658
26877
  }
26659
26878
  async extract(input) {
26660
- await this.ensureSession();
26661
- return this.requireClient().invoke("dom.extract", input);
26879
+ return this.invokeSemanticOperation("dom.extract", input);
26662
26880
  }
26663
26881
  async queryNetwork(input = {}) {
26664
- await this.ensureSession();
26665
- return this.requireClient().invoke("network.query", input);
26882
+ return this.invokeSemanticOperation("network.query", input);
26666
26883
  }
26667
26884
  async getNetworkDetail(input) {
26668
- await this.ensureSession();
26669
- return this.requireClient().invoke("network.detail", input);
26885
+ return this.invokeSemanticOperation("network.detail", input);
26670
26886
  }
26671
26887
  async captureInteraction(input) {
26672
- await this.ensureSession();
26673
- return this.requireClient().invoke("interaction.capture", input);
26888
+ return this.invokeSemanticOperation("interaction.capture", input);
26674
26889
  }
26675
26890
  async getInteraction(input) {
26676
- await this.ensureSession();
26677
- return this.requireClient().invoke("interaction.get", input);
26891
+ return this.invokeSemanticOperation("interaction.get", input);
26678
26892
  }
26679
26893
  async diffInteraction(input) {
26680
- await this.ensureSession();
26681
- return this.requireClient().invoke("interaction.diff", input);
26894
+ return this.invokeSemanticOperation("interaction.diff", input);
26682
26895
  }
26683
26896
  async replayInteraction(input) {
26684
- await this.ensureSession();
26685
- return this.requireClient().invoke("interaction.replay", input);
26897
+ return this.invokeSemanticOperation("interaction.replay", input);
26686
26898
  }
26687
26899
  async captureScripts(input = {}) {
26688
- await this.ensureSession();
26689
- return this.requireClient().invoke("scripts.capture", input);
26900
+ return this.invokeSemanticOperation("scripts.capture", input);
26690
26901
  }
26691
26902
  async readArtifact(input) {
26692
- await this.ensureSession();
26693
- return this.requireClient().invoke("artifact.read", input);
26903
+ return this.invokeSemanticOperation("artifact.read", input);
26694
26904
  }
26695
26905
  async beautifyScript(input) {
26696
- await this.ensureSession();
26697
- return this.requireClient().invoke("scripts.beautify", input);
26906
+ return this.invokeSemanticOperation("scripts.beautify", input);
26698
26907
  }
26699
26908
  async deobfuscateScript(input) {
26700
- await this.ensureSession();
26701
- return this.requireClient().invoke("scripts.deobfuscate", input);
26909
+ return this.invokeSemanticOperation("scripts.deobfuscate", input);
26702
26910
  }
26703
26911
  async sandboxScript(input) {
26704
- await this.ensureSession();
26705
- return this.requireClient().invoke("scripts.sandbox", input);
26912
+ return this.invokeSemanticOperation("scripts.sandbox", input);
26706
26913
  }
26707
26914
  async solveCaptcha(input) {
26708
- await this.ensureSession();
26709
- return this.requireClient().invoke("captcha.solve", input);
26915
+ return this.invokeSemanticOperation("captcha.solve", input);
26710
26916
  }
26711
26917
  async getCookies(input = {}) {
26712
- await this.ensureSession();
26713
- return this.requireClient().invoke("session.cookies", input);
26918
+ return this.invokeSemanticOperation("session.cookies", input);
26714
26919
  }
26715
26920
  async route(input) {
26716
26921
  await this.ensureSession();
@@ -26721,20 +26926,16 @@ var CloudSessionProxy = class {
26721
26926
  return this.requireAutomation().interceptScript(input);
26722
26927
  }
26723
26928
  async getStorageSnapshot(input = {}) {
26724
- await this.ensureSession();
26725
- return this.requireClient().invoke("session.storage", input);
26929
+ return this.invokeSemanticOperation("session.storage", input);
26726
26930
  }
26727
26931
  async getBrowserState(input = {}) {
26728
- await this.ensureSession();
26729
- return this.requireClient().invoke("session.state", input);
26932
+ return this.invokeSemanticOperation("session.state", input);
26730
26933
  }
26731
26934
  async fetch(input) {
26732
- await this.ensureSession();
26733
- return this.requireClient().invoke("session.fetch", input);
26935
+ return this.invokeSemanticOperation("session.fetch", input);
26734
26936
  }
26735
26937
  async computerExecute(input) {
26736
- await this.ensureSession();
26737
- return this.requireClient().invoke("computer.execute", input);
26938
+ return this.invokeSemanticOperation("computer.execute", input);
26738
26939
  }
26739
26940
  async close() {
26740
26941
  const session = await this.loadPersistedSession() ?? (this.sessionId === void 0 ? void 0 : {
@@ -26746,6 +26947,14 @@ var CloudSessionProxy = class {
26746
26947
  startedAt: Date.now(),
26747
26948
  updatedAt: Date.now()
26748
26949
  });
26950
+ let syncError;
26951
+ if (this.syncWorkspaceOnClose) {
26952
+ try {
26953
+ await this.syncWorkspaceToCloud();
26954
+ } catch (error) {
26955
+ syncError = error;
26956
+ }
26957
+ }
26749
26958
  try {
26750
26959
  if (session !== void 0) {
26751
26960
  await this.cloud.closeSession(session.sessionId).catch((error) => {
@@ -26766,6 +26975,9 @@ var CloudSessionProxy = class {
26766
26975
  await promises.rm(this.rootPath, { recursive: true, force: true }).catch(() => void 0);
26767
26976
  }
26768
26977
  }
26978
+ if (syncError !== void 0) {
26979
+ throw syncError;
26980
+ }
26769
26981
  return { closed: true };
26770
26982
  }
26771
26983
  async disconnect() {
@@ -26773,34 +26985,38 @@ var CloudSessionProxy = class {
26773
26985
  await this.close();
26774
26986
  return;
26775
26987
  }
26988
+ let syncError;
26989
+ if (this.syncWorkspaceOnClose) {
26990
+ try {
26991
+ await this.syncWorkspaceToCloud();
26992
+ } catch (error) {
26993
+ syncError = error;
26994
+ }
26995
+ }
26776
26996
  this.client = void 0;
26777
26997
  await this.automation?.close().catch(() => void 0);
26778
26998
  this.automation = void 0;
26779
26999
  this.sessionId = void 0;
26780
27000
  this.semanticGrant = void 0;
27001
+ if (syncError !== void 0) {
27002
+ throw syncError;
27003
+ }
26781
27004
  }
26782
- async ensureSession(input = {}) {
27005
+ async ensureSession(input = {}, timeout) {
26783
27006
  if (this.client) {
26784
27007
  return;
26785
27008
  }
26786
27009
  assertSupportedCloudBrowserMode(input.browser);
26787
27010
  const localCloud = this.shouldUseLocalCloudTransport();
27011
+ this.syncWorkspaceOnClose = localCloud && this.workspace !== void 0;
26788
27012
  const browserProfile = resolveCloudBrowserProfile(this.cloud, input);
26789
27013
  const persisted = await this.loadPersistedSession();
26790
- if (persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId)) {
26791
- if (localCloud) {
26792
- void this.syncRegistryToCloud();
26793
- } else {
26794
- await this.syncRegistryToCloud();
26795
- }
27014
+ if (persisted !== void 0 && await this.isReusableCloudSession(persisted.sessionId, timeout)) {
27015
+ await this.syncWorkspaceToCloud();
26796
27016
  this.bindClient(persisted);
26797
27017
  return;
26798
27018
  }
26799
- if (localCloud) {
26800
- void this.syncRegistryToCloud();
26801
- } else {
26802
- await this.syncRegistryToCloud();
26803
- }
27019
+ await this.syncWorkspaceToCloud();
26804
27020
  const baseCreateInput = {
26805
27021
  ...this.workspace === void 0 ? {} : { name: this.workspace },
26806
27022
  ...input.launch === void 0 ? {} : { browser: input.launch },
@@ -26812,10 +27028,12 @@ var CloudSessionProxy = class {
26812
27028
  ...baseCreateInput,
26813
27029
  sourceType: "local-cloud",
26814
27030
  sourceRef: this.workspace,
26815
- localWorkspaceRootPath: this.rootPath,
26816
- locality: "auto"
27031
+ localWorkspaceRootPath: this.rootPath
26817
27032
  } : baseCreateInput;
26818
- const session = await this.cloud.createSession(createInput);
27033
+ const session = await this.cloud.createSession(createInput, {
27034
+ signal: timeout?.signal,
27035
+ timeoutMs: timeout?.remainingMs()
27036
+ });
26819
27037
  const record = {
26820
27038
  layout: "opensteer-session",
26821
27039
  version: 1,
@@ -26828,15 +27046,12 @@ var CloudSessionProxy = class {
26828
27046
  await this.writePersistedSession(record);
26829
27047
  this.bindClient(record, session.initialGrants?.semantic);
26830
27048
  }
26831
- async syncRegistryToCloud() {
27049
+ async syncWorkspaceToCloud() {
26832
27050
  if (this.workspace === void 0) {
26833
27051
  return;
26834
27052
  }
26835
- try {
26836
- const workspaceStore = await this.ensureWorkspaceStore();
26837
- await syncLocalRegistryToCloud(this.cloud, this.workspace, workspaceStore);
26838
- } catch {
26839
- }
27053
+ const workspaceStore = await this.ensureWorkspaceStore();
27054
+ await syncLocalWorkspaceToCloud(this.cloud, this.workspace, workspaceStore);
26840
27055
  }
26841
27056
  bindClient(record, initialSemanticGrant) {
26842
27057
  this.sessionId = record.sessionId;
@@ -26870,9 +27085,12 @@ var CloudSessionProxy = class {
26870
27085
  async clearPersistedSession() {
26871
27086
  await clearPersistedSessionRecord(this.rootPath, "cloud").catch(() => void 0);
26872
27087
  }
26873
- async isReusableCloudSession(sessionId) {
27088
+ async isReusableCloudSession(sessionId, timeout) {
26874
27089
  try {
26875
- const session = await this.cloud.getSession(sessionId);
27090
+ const session = await this.cloud.getSession(sessionId, {
27091
+ signal: timeout?.signal,
27092
+ timeoutMs: timeout?.remainingMs()
27093
+ });
26876
27094
  return session.status !== "closed" && session.status !== "failed";
26877
27095
  } catch (error) {
26878
27096
  if (isMissingCloudSessionError(error)) {
@@ -26893,14 +27111,17 @@ var CloudSessionProxy = class {
26893
27111
  }
26894
27112
  return this.automation;
26895
27113
  }
26896
- async ensureSemanticGrant(forceRefresh = false) {
27114
+ async ensureSemanticGrant(forceRefresh = false, timeout) {
26897
27115
  if (!forceRefresh && this.semanticGrant?.kind === "semantic" && this.semanticGrant.expiresAt > Date.now() + 1e4) {
26898
27116
  return this.semanticGrant;
26899
27117
  }
26900
27118
  if (!this.sessionId) {
26901
27119
  throw new Error("Cloud session has not been initialized.");
26902
27120
  }
26903
- const issued = await this.cloud.issueAccess(this.sessionId, ["semantic"]);
27121
+ const issued = await this.cloud.issueAccess(this.sessionId, ["semantic"], {
27122
+ signal: timeout?.signal,
27123
+ timeoutMs: timeout?.remainingMs()
27124
+ });
26904
27125
  const grant = issued.grants.semantic;
26905
27126
  if (!grant || grant.transport !== "http") {
26906
27127
  throw new Error("cloud did not issue a valid semantic grant");
@@ -26923,6 +27144,25 @@ var CloudSessionProxy = class {
26923
27144
  return false;
26924
27145
  }
26925
27146
  }
27147
+ async invokeSemanticOperation(operation, input, sessionInit = {}) {
27148
+ return this.runOperationWithPolicy(operation, async (timeout) => {
27149
+ await this.ensureSession(sessionInit, timeout);
27150
+ await this.ensureSemanticGrant(false, timeout);
27151
+ return this.requireClient().invoke(operation, input, {
27152
+ signal: timeout.signal,
27153
+ timeoutMs: timeout.remainingMs()
27154
+ });
27155
+ });
27156
+ }
27157
+ async invokeAutomationOperation(operation, invoke, sessionInit = {}) {
27158
+ return this.runOperationWithPolicy(operation, async (timeout) => {
27159
+ await this.ensureSession(sessionInit, timeout);
27160
+ return invoke(this.requireAutomation());
27161
+ });
27162
+ }
27163
+ async runOperationWithPolicy(operation, invoke) {
27164
+ return runWithPolicyTimeout(this.policy.timeout, { operation }, invoke);
27165
+ }
26926
27166
  shouldUseLocalCloudTransport() {
26927
27167
  if (this.workspace === void 0) {
26928
27168
  return false;
@@ -27097,6 +27337,7 @@ function createOpensteerSemanticRuntime(input = {}) {
27097
27337
  ...runtimeOptions.rootDir === void 0 ? {} : { rootDir: runtimeOptions.rootDir },
27098
27338
  ...runtimeOptions.rootPath === void 0 ? {} : { rootPath: runtimeOptions.rootPath },
27099
27339
  ...runtimeOptions.workspace === void 0 ? {} : { workspace: runtimeOptions.workspace },
27340
+ ...runtimeOptions.policy === void 0 ? {} : { policy: runtimeOptions.policy },
27100
27341
  ...runtimeOptions.cleanupRootOnClose === void 0 ? {} : { cleanupRootOnClose: runtimeOptions.cleanupRootOnClose },
27101
27342
  ...runtimeOptions.observability === void 0 ? {} : { observability: runtimeOptions.observability }
27102
27343
  });
@@ -27426,23 +27667,28 @@ function pickStorageDomainSnapshot(snapshot, domain) {
27426
27667
  return snapshot.domains.find((entry) => entry.domain === domain);
27427
27668
  }
27428
27669
  function buildFetchInput(url, options) {
27670
+ const { body, ...rest } = options;
27429
27671
  return {
27430
27672
  url,
27431
- ...options.method !== void 0 && { method: options.method },
27432
- ...options.headers !== void 0 && { headers: options.headers },
27433
- ...options.query !== void 0 && { query: options.query },
27434
- ...options.transport !== void 0 && { transport: options.transport },
27435
- ...options.cookies !== void 0 && { cookies: options.cookies },
27436
- ...options.followRedirects !== void 0 && { followRedirects: options.followRedirects },
27437
- ...options.body !== void 0 && { body: toRuntimeBody(options.body) }
27673
+ ...rest,
27674
+ ...body === void 0 ? {} : { body: normalizeFetchBody(body, rest.headers) }
27438
27675
  };
27439
27676
  }
27440
- function toRuntimeBody(body) {
27441
- try {
27442
- return { json: JSON.parse(body) };
27443
- } catch {
27444
- return { text: body };
27677
+ function normalizeFetchBody(body, headers) {
27678
+ if (typeof body !== "string") {
27679
+ return body;
27445
27680
  }
27681
+ const contentType = findHeaderValue2(headers, "content-type");
27682
+ return contentType === void 0 ? { text: body } : { text: body, contentType };
27683
+ }
27684
+ function findHeaderValue2(headers, headerName) {
27685
+ if (headers === void 0) {
27686
+ return void 0;
27687
+ }
27688
+ const match = Object.entries(headers).find(
27689
+ ([name]) => name.toLowerCase() === headerName.toLowerCase()
27690
+ );
27691
+ return match === void 0 ? void 0 : String(match[1]);
27446
27692
  }
27447
27693
  function toResponse(response) {
27448
27694
  return new Response(decodeBody(response), {