@rtrvr-ai/rover 1.1.0 → 1.1.1

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/rover.js CHANGED
@@ -11107,7 +11107,7 @@ var Bridge = class {
11107
11107
  this.runtimeId = opts.runtimeId;
11108
11108
  this.domainScopeMode = normalizeDomainScopeMode(opts.domainScopeMode);
11109
11109
  this.allowedDomains = normalizeAllowedDomains(opts.allowedDomains, window.location.hostname, this.domainScopeMode);
11110
- this.externalNavigationPolicy = normalizeExternalNavigationPolicy(opts.externalNavigationPolicy, opts.crossDomainPolicy);
11110
+ this.externalNavigationPolicy = normalizeExternalNavigationPolicy(opts.externalNavigationPolicy);
11111
11111
  this.registerOpenedTab = opts.registerOpenedTab;
11112
11112
  this.listKnownTabs = opts.listKnownTabs;
11113
11113
  this.switchToLogicalTab = opts.switchToLogicalTab;
@@ -11137,8 +11137,8 @@ var Bridge = class {
11137
11137
  if (options.allowedDomains) {
11138
11138
  this.allowedDomains = normalizeAllowedDomains(options.allowedDomains, window.location.hostname, this.domainScopeMode);
11139
11139
  }
11140
- if (options.externalNavigationPolicy || options.crossDomainPolicy) {
11141
- this.externalNavigationPolicy = normalizeExternalNavigationPolicy(options.externalNavigationPolicy, options.crossDomainPolicy);
11140
+ if (options.externalNavigationPolicy) {
11141
+ this.externalNavigationPolicy = normalizeExternalNavigationPolicy(options.externalNavigationPolicy);
11142
11142
  }
11143
11143
  }
11144
11144
  async getSnapshot() {
@@ -11465,7 +11465,8 @@ var Bridge = class {
11465
11465
  }
11466
11466
  async openUrlInNewTab(targetUrl, options) {
11467
11467
  const external = !isUrlAllowedByDomains(targetUrl, this.allowedDomains);
11468
- const popupAttempt = this.openVerifiedPopup(targetUrl);
11468
+ const knownTabIdsBeforeOpen = this.snapshotKnownTabIds();
11469
+ let popupAttempt = this.openVerifiedPopup(targetUrl);
11469
11470
  let logicalTabId;
11470
11471
  if (popupAttempt.opened && this.registerOpenedTab) {
11471
11472
  try {
@@ -11479,6 +11480,16 @@ var Bridge = class {
11479
11480
  } catch {
11480
11481
  }
11481
11482
  }
11483
+ if (popupAttempt.opened && !logicalTabId) {
11484
+ logicalTabId = await this.reconcileOpenedTab(targetUrl, knownTabIdsBeforeOpen);
11485
+ }
11486
+ if (!popupAttempt.opened) {
11487
+ const reconciledTabId = await this.reconcileOpenedTab(targetUrl, knownTabIdsBeforeOpen);
11488
+ if (reconciledTabId) {
11489
+ popupAttempt = { opened: true, verified: false };
11490
+ logicalTabId = logicalTabId || reconciledTabId;
11491
+ }
11492
+ }
11482
11493
  const registrationFailed = popupAttempt.opened && !logicalTabId && !!this.registerOpenedTab;
11483
11494
  if (!popupAttempt.opened) {
11484
11495
  const reason = "Browser popup settings blocked opening a new tab.";
@@ -11588,6 +11599,51 @@ var Bridge = class {
11588
11599
  return { opened: false, verified: false };
11589
11600
  }
11590
11601
  }
11602
+ snapshotKnownTabIds() {
11603
+ const ids = /* @__PURE__ */ new Set();
11604
+ if (!this.listKnownTabs)
11605
+ return ids;
11606
+ try {
11607
+ const tabs = this.listKnownTabs();
11608
+ for (const tab of tabs) {
11609
+ const logicalTabId = Number(tab?.logicalTabId);
11610
+ if (!Number.isFinite(logicalTabId) || logicalTabId <= 0)
11611
+ continue;
11612
+ ids.add(logicalTabId);
11613
+ }
11614
+ } catch {
11615
+ return ids;
11616
+ }
11617
+ return ids;
11618
+ }
11619
+ async reconcileOpenedTab(targetUrl, beforeIds) {
11620
+ if (!this.listKnownTabs)
11621
+ return void 0;
11622
+ const targetHost = extractHostname(targetUrl);
11623
+ const deadline = Date.now() + 2e3;
11624
+ while (Date.now() < deadline) {
11625
+ try {
11626
+ const knownTabs = this.listKnownTabs();
11627
+ for (const tab of knownTabs) {
11628
+ const logicalTabId = Number(tab?.logicalTabId);
11629
+ if (!Number.isFinite(logicalTabId) || logicalTabId <= 0)
11630
+ continue;
11631
+ if (beforeIds.has(logicalTabId))
11632
+ continue;
11633
+ const knownUrl = String(tab?.url || "").trim();
11634
+ if (!knownUrl)
11635
+ return logicalTabId;
11636
+ const knownHost = extractHostname(knownUrl);
11637
+ if (!targetHost || !knownHost || knownHost === targetHost) {
11638
+ return logicalTabId;
11639
+ }
11640
+ }
11641
+ } catch {
11642
+ }
11643
+ await new Promise((resolve) => setTimeout(resolve, 120));
11644
+ }
11645
+ return void 0;
11646
+ }
11591
11647
  async openElementInNewTab(args) {
11592
11648
  const elementId = args.element_id ?? args.source_element_id ?? args.target_element_id ?? args.center_element_id ?? null;
11593
11649
  if (!elementId) {
@@ -11721,18 +11777,11 @@ function normalizeToolName(name) {
11721
11777
  function normalizeDomainScopeMode(mode) {
11722
11778
  return mode === "host_only" ? "host_only" : "registrable_domain";
11723
11779
  }
11724
- function mapLegacyCrossDomainPolicy(policy) {
11725
- if (policy === "allow")
11726
- return "allow";
11727
- if (policy === "block_new_tab")
11728
- return "open_new_tab_notice";
11729
- return void 0;
11730
- }
11731
- function normalizeExternalNavigationPolicy(policy, legacy) {
11780
+ function normalizeExternalNavigationPolicy(policy) {
11732
11781
  if (policy === "allow" || policy === "block" || policy === "open_new_tab_notice") {
11733
11782
  return policy;
11734
11783
  }
11735
- return mapLegacyCrossDomainPolicy(legacy) || "open_new_tab_notice";
11784
+ return "open_new_tab_notice";
11736
11785
  }
11737
11786
  function normalizeAllowedDomains(input, currentHost, scopeMode) {
11738
11787
  const candidates = Array.isArray(input) ? input : [];
@@ -14639,6 +14688,14 @@ var SessionCoordinator = class _SessionCoordinator {
14639
14688
  const normalizedUrl = normalizeUrl2(payload.url);
14640
14689
  let logicalTabId = 0;
14641
14690
  this.mutate("local", (draft) => {
14691
+ const existing = draft.tabs.find((tab) => !tab.runtimeId && !!tab.external && tab.url === normalizedUrl);
14692
+ if (existing) {
14693
+ existing.title = payload.title || existing.title;
14694
+ existing.updatedAt = now();
14695
+ existing.openerRuntimeId = payload.openerRuntimeId || existing.openerRuntimeId;
14696
+ logicalTabId = existing.logicalTabId;
14697
+ return;
14698
+ }
14642
14699
  logicalTabId = draft.nextLogicalTabId++;
14643
14700
  draft.tabs.push({
14644
14701
  logicalTabId,
@@ -15091,6 +15148,7 @@ var RoverCloudCheckpointClient = class {
15091
15148
  shouldWrite;
15092
15149
  buildCheckpoint;
15093
15150
  onCheckpoint;
15151
+ onStateChange;
15094
15152
  onError;
15095
15153
  started = false;
15096
15154
  dirty = false;
@@ -15102,8 +15160,9 @@ var RoverCloudCheckpointClient = class {
15102
15160
  lastAppliedRemoteUpdatedAt = 0;
15103
15161
  pushInFlight = false;
15104
15162
  pullInFlight = false;
15163
+ state = "active";
15105
15164
  constructor(options) {
15106
- const token = String(options.apiKey || options.authToken || "").trim();
15165
+ const token = String(options.authToken || options.apiKey || "").trim();
15107
15166
  if (!token) {
15108
15167
  throw toError("Rover cloud checkpoint requires apiKey or authToken.");
15109
15168
  }
@@ -15118,12 +15177,14 @@ var RoverCloudCheckpointClient = class {
15118
15177
  this.shouldWrite = options.shouldWrite || (() => true);
15119
15178
  this.buildCheckpoint = options.buildCheckpoint;
15120
15179
  this.onCheckpoint = options.onCheckpoint;
15180
+ this.onStateChange = options.onStateChange;
15121
15181
  this.onError = options.onError;
15122
15182
  }
15123
15183
  start() {
15124
15184
  if (this.started)
15125
15185
  return;
15126
15186
  this.started = true;
15187
+ this.setState("active");
15127
15188
  this.flushTimer = window.setInterval(() => {
15128
15189
  void this.flush(false);
15129
15190
  }, this.flushIntervalMs);
@@ -15156,6 +15217,8 @@ var RoverCloudCheckpointClient = class {
15156
15217
  async flush(force) {
15157
15218
  if (this.pushInFlight)
15158
15219
  return;
15220
+ if (this.state === "paused_auth")
15221
+ return;
15159
15222
  if (!this.dirty && !force)
15160
15223
  return;
15161
15224
  if (!this.shouldWrite())
@@ -15189,8 +15252,9 @@ var RoverCloudCheckpointClient = class {
15189
15252
  this.applyRemoteCheckpoint(response.checkpoint, "push_stale");
15190
15253
  this.dirty = false;
15191
15254
  }
15255
+ this.setState("active");
15192
15256
  } catch (error) {
15193
- this.onError?.(error);
15257
+ this.handleCheckpointError(error, "roverSessionCheckpointUpsert");
15194
15258
  } finally {
15195
15259
  this.pushInFlight = false;
15196
15260
  }
@@ -15198,6 +15262,8 @@ var RoverCloudCheckpointClient = class {
15198
15262
  async pull(force) {
15199
15263
  if (this.pullInFlight)
15200
15264
  return;
15265
+ if (this.state === "paused_auth")
15266
+ return;
15201
15267
  if (!force && Date.now() - this.lastPullAt < this.pullIntervalMs)
15202
15268
  return;
15203
15269
  this.pullInFlight = true;
@@ -15210,8 +15276,9 @@ var RoverCloudCheckpointClient = class {
15210
15276
  if (!response?.found || !response?.checkpoint || typeof response.checkpoint !== "object")
15211
15277
  return;
15212
15278
  this.applyRemoteCheckpoint(response.checkpoint, "pull");
15279
+ this.setState("active");
15213
15280
  } catch (error) {
15214
- this.onError?.(error);
15281
+ this.handleCheckpointError(error, "roverSessionCheckpointGet");
15215
15282
  } finally {
15216
15283
  this.pullInFlight = false;
15217
15284
  }
@@ -15240,14 +15307,68 @@ var RoverCloudCheckpointClient = class {
15240
15307
  const text = await response.text().catch(() => "");
15241
15308
  payload2 = text ? { error: truncateText(text, 2e3) } : void 0;
15242
15309
  }
15243
- throw toError(`Checkpoint HTTP ${response.status}`, payload2);
15310
+ throw toError(`Checkpoint HTTP ${response.status}`, {
15311
+ action,
15312
+ status: response.status,
15313
+ payload: payload2
15314
+ });
15244
15315
  }
15245
15316
  const payload = await response.json();
15246
15317
  if (payload?.success === false) {
15247
- throw toError("Checkpoint extensionRouter returned success=false", payload);
15318
+ throw toError("Checkpoint extensionRouter returned success=false", {
15319
+ action,
15320
+ status: response.status,
15321
+ payload
15322
+ });
15248
15323
  }
15249
15324
  return payload?.data;
15250
15325
  }
15326
+ setState(next, context) {
15327
+ if (this.state === next)
15328
+ return;
15329
+ this.state = next;
15330
+ this.onStateChange?.(next, context || {});
15331
+ }
15332
+ handleCheckpointError(error, action) {
15333
+ const details = this.normalizeCheckpointError(error);
15334
+ const isAuthFailure = this.isAuthFailure(details);
15335
+ if (isAuthFailure) {
15336
+ this.setState("paused_auth", {
15337
+ reason: "auth_failed",
15338
+ action,
15339
+ code: details.code,
15340
+ message: details.message
15341
+ });
15342
+ }
15343
+ this.onError?.(error, {
15344
+ action,
15345
+ state: this.state,
15346
+ code: details.code,
15347
+ message: details.message,
15348
+ status: details.status,
15349
+ paused: isAuthFailure
15350
+ });
15351
+ }
15352
+ normalizeCheckpointError(error) {
15353
+ const anyError = error;
15354
+ const details = anyError?.details;
15355
+ const payload = details?.payload || details?.response || details;
15356
+ const candidateCode = payload?.errorCode || payload?.errorDetails?.code || payload?.error?.code || payload?.code;
15357
+ const candidateMessage = payload?.error || payload?.errorDetails?.message || payload?.error?.message || anyError?.message || "Checkpoint request failed";
15358
+ const status = Number(details?.status);
15359
+ return {
15360
+ code: typeof candidateCode === "string" ? candidateCode : void 0,
15361
+ message: truncateText(candidateMessage, 1e3),
15362
+ status: Number.isFinite(status) ? status : void 0
15363
+ };
15364
+ }
15365
+ isAuthFailure(details) {
15366
+ const code = String(details.code || "").toUpperCase();
15367
+ if (code === "INVALID_API_KEY" || code === "MISSING_API_KEY" || code === "UNAUTHENTICATED" || code === "PERMISSION_DENIED") {
15368
+ return true;
15369
+ }
15370
+ return details.status === 401 || details.status === 403;
15371
+ }
15251
15372
  };
15252
15373
 
15253
15374
  // dist/runtimeStorage.js
@@ -15387,6 +15508,9 @@ var VISITOR_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
15387
15508
  var CHECKPOINT_PAYLOAD_VERSION = 1;
15388
15509
  var ACTIVE_PENDING_RUN_GRACE_MS = 3e3;
15389
15510
  var STALE_PENDING_RUN_MS = 9e4;
15511
+ var TELEMETRY_DEFAULT_FLUSH_INTERVAL_MS = 12e3;
15512
+ var TELEMETRY_DEFAULT_MAX_BATCH_SIZE = 30;
15513
+ var TELEMETRY_MAX_BUFFER_SIZE = 240;
15390
15514
  var instance = null;
15391
15515
  var bridge = null;
15392
15516
  var worker = null;
@@ -15398,6 +15522,11 @@ var runtimeStateStore = null;
15398
15522
  var runtimeId = "";
15399
15523
  var sessionCoordinator = null;
15400
15524
  var cloudCheckpointClient = null;
15525
+ var telemetryFlushTimer = null;
15526
+ var telemetryBuffer = [];
15527
+ var telemetryInFlight = false;
15528
+ var telemetryPausedAuth = false;
15529
+ var telemetrySeq = 0;
15401
15530
  var resolvedVisitorId = void 0;
15402
15531
  var suppressCheckpointSync = false;
15403
15532
  var currentMode = "controller";
@@ -15423,6 +15552,7 @@ var RUN_SCOPED_WORKER_MESSAGE_TYPES = /* @__PURE__ */ new Set([
15423
15552
  var pendingToolRegistrations = [];
15424
15553
  var eventHandlers = /* @__PURE__ */ new Map();
15425
15554
  function emit(event, payload) {
15555
+ recordTelemetryEvent(event, payload);
15426
15556
  const handlers = eventHandlers.get(event);
15427
15557
  if (!handlers)
15428
15558
  return;
@@ -15628,20 +15758,189 @@ function safeSerialize(value) {
15628
15758
  }
15629
15759
  }
15630
15760
  }
15631
- function resolveEmbeddedDomainLabel(allowedDomains) {
15632
- const raw = String((allowedDomains || [])[0] || "").trim();
15633
- if (!raw)
15634
- return "the configured domain";
15635
- return raw.replace(/^\*?\./, "").replace(/^=/, "");
15761
+ function normalizeTelemetryConfig(cfg) {
15762
+ const raw = cfg?.telemetry;
15763
+ const sampleRateRaw = Number(raw?.sampleRate);
15764
+ const sampleRate = Number.isFinite(sampleRateRaw) ? Math.min(1, Math.max(0, sampleRateRaw)) : 1;
15765
+ const flushRaw = Number(raw?.flushIntervalMs);
15766
+ const flushIntervalMs = Number.isFinite(flushRaw) ? Math.min(6e4, Math.max(2e3, Math.floor(flushRaw))) : TELEMETRY_DEFAULT_FLUSH_INTERVAL_MS;
15767
+ const batchRaw = Number(raw?.maxBatchSize);
15768
+ const maxBatchSize = Number.isFinite(batchRaw) ? Math.min(80, Math.max(1, Math.floor(batchRaw))) : TELEMETRY_DEFAULT_MAX_BATCH_SIZE;
15769
+ return {
15770
+ enabled: raw?.enabled !== false,
15771
+ sampleRate,
15772
+ flushIntervalMs,
15773
+ maxBatchSize,
15774
+ includePayloads: raw?.includePayloads === true
15775
+ };
15636
15776
  }
15637
- function buildInaccessibleTabPageData(cfg, tab, reason = "tab_not_accessible") {
15777
+ function canUseTelemetry(cfg) {
15778
+ if (!cfg)
15779
+ return false;
15780
+ const telemetry = normalizeTelemetryConfig(cfg);
15781
+ if (!telemetry.enabled)
15782
+ return false;
15783
+ if (!(cfg.authToken || cfg.apiKey))
15784
+ return false;
15785
+ return true;
15786
+ }
15787
+ function summarizeTelemetryPayload(payload) {
15788
+ if (payload == null)
15789
+ return void 0;
15790
+ if (typeof payload === "string")
15791
+ return truncateText2(payload, 260);
15792
+ if (typeof payload === "number" || typeof payload === "boolean")
15793
+ return payload;
15794
+ if (Array.isArray(payload)) {
15795
+ return { type: "array", length: payload.length };
15796
+ }
15797
+ if (typeof payload === "object") {
15798
+ const keys = Object.keys(payload).slice(0, 20);
15799
+ const summary = { type: "object", keys };
15800
+ const preferredKeys = ["code", "message", "stage", "status", "reason", "taskId", "runId", "policyAction"];
15801
+ for (const key of preferredKeys) {
15802
+ const value = payload?.[key];
15803
+ if (value == null)
15804
+ continue;
15805
+ if (typeof value === "string")
15806
+ summary[key] = truncateText2(value, 180);
15807
+ else if (typeof value === "number" || typeof value === "boolean")
15808
+ summary[key] = value;
15809
+ }
15810
+ return summary;
15811
+ }
15812
+ return void 0;
15813
+ }
15814
+ function buildTelemetryPayload(payload, includePayloads) {
15815
+ if (!includePayloads) {
15816
+ return summarizeTelemetryPayload(payload);
15817
+ }
15818
+ const cloned = cloneUnknown2(payload);
15819
+ if (cloned == null)
15820
+ return void 0;
15821
+ if (typeof cloned === "string")
15822
+ return truncateText2(cloned, 1e3);
15823
+ if (typeof cloned === "number" || typeof cloned === "boolean")
15824
+ return cloned;
15825
+ if (Array.isArray(cloned))
15826
+ return { type: "array", length: cloned.length };
15827
+ if (typeof cloned === "object")
15828
+ return cloned;
15829
+ return summarizeTelemetryPayload(payload);
15830
+ }
15831
+ function stopTelemetry() {
15832
+ if (telemetryFlushTimer) {
15833
+ clearInterval(telemetryFlushTimer);
15834
+ telemetryFlushTimer = null;
15835
+ }
15836
+ }
15837
+ function getTelemetryEndpoint(cfg) {
15838
+ const base = (cfg.apiBase || "https://us-central1-rtrvr-extension-functions.cloudfunctions.net").replace(/\/$/, "");
15839
+ return base.endsWith("/extensionRouter") ? base : `${base}/extensionRouter`;
15840
+ }
15841
+ async function flushTelemetry(force = false) {
15842
+ if (telemetryInFlight)
15843
+ return;
15844
+ if (telemetryPausedAuth)
15845
+ return;
15846
+ if (!currentConfig || !canUseTelemetry(currentConfig)) {
15847
+ telemetryBuffer = [];
15848
+ return;
15849
+ }
15850
+ if (!telemetryBuffer.length)
15851
+ return;
15852
+ const telemetry = normalizeTelemetryConfig(currentConfig);
15853
+ const token = String(currentConfig.authToken || currentConfig.apiKey || "").trim();
15854
+ if (!token)
15855
+ return;
15856
+ const batch = telemetryBuffer.splice(0, telemetry.maxBatchSize);
15857
+ if (!batch.length)
15858
+ return;
15859
+ telemetryInFlight = true;
15860
+ try {
15861
+ const response = await fetch(getTelemetryEndpoint(currentConfig), {
15862
+ method: "POST",
15863
+ headers: {
15864
+ Authorization: `Bearer ${token}`,
15865
+ "Content-Type": "application/json"
15866
+ },
15867
+ body: JSON.stringify({
15868
+ action: "roverTelemetryIngest",
15869
+ data: {
15870
+ siteId: currentConfig.siteId,
15871
+ runtimeId,
15872
+ sessionId: runtimeState?.sessionId,
15873
+ visitorId: resolvedVisitorId,
15874
+ flushReason: force ? "manual" : "interval",
15875
+ sdkVersion: "rover_sdk_v1",
15876
+ pageUrl: window.location.href,
15877
+ userAgent: navigator.userAgent,
15878
+ sampleRate: telemetry.sampleRate,
15879
+ events: batch
15880
+ }
15881
+ })
15882
+ });
15883
+ if (!response.ok) {
15884
+ if (response.status === 401 || response.status === 403) {
15885
+ telemetryPausedAuth = true;
15886
+ } else {
15887
+ telemetryBuffer = [...batch, ...telemetryBuffer].slice(-TELEMETRY_MAX_BUFFER_SIZE);
15888
+ }
15889
+ return;
15890
+ }
15891
+ const payload = await response.json().catch(() => void 0);
15892
+ if (payload?.success === false) {
15893
+ const code = String(payload?.errorCode || payload?.errorDetails?.code || "").toUpperCase();
15894
+ if (code === "INVALID_API_KEY" || code === "MISSING_API_KEY" || code === "UNAUTHENTICATED" || code === "PERMISSION_DENIED") {
15895
+ telemetryPausedAuth = true;
15896
+ }
15897
+ return;
15898
+ }
15899
+ } catch {
15900
+ telemetryBuffer = [...batch, ...telemetryBuffer].slice(-TELEMETRY_MAX_BUFFER_SIZE);
15901
+ } finally {
15902
+ telemetryInFlight = false;
15903
+ }
15904
+ }
15905
+ function setupTelemetry(cfg) {
15906
+ stopTelemetry();
15907
+ telemetryPausedAuth = false;
15908
+ if (!canUseTelemetry(cfg)) {
15909
+ telemetryBuffer = [];
15910
+ return;
15911
+ }
15912
+ const telemetry = normalizeTelemetryConfig(cfg);
15913
+ telemetryFlushTimer = setInterval(() => {
15914
+ void flushTelemetry(false);
15915
+ }, telemetry.flushIntervalMs);
15916
+ }
15917
+ function recordTelemetryEvent(event, payload) {
15918
+ if (!canUseTelemetry(currentConfig) || telemetryPausedAuth)
15919
+ return;
15920
+ const telemetry = normalizeTelemetryConfig(currentConfig);
15921
+ if (telemetry.sampleRate < 1 && Math.random() > telemetry.sampleRate)
15922
+ return;
15923
+ const next = {
15924
+ name: event,
15925
+ ts: Date.now(),
15926
+ seq: ++telemetrySeq,
15927
+ payload: buildTelemetryPayload(payload, telemetry.includePayloads)
15928
+ };
15929
+ telemetryBuffer.push(next);
15930
+ if (telemetryBuffer.length > TELEMETRY_MAX_BUFFER_SIZE) {
15931
+ telemetryBuffer = telemetryBuffer.slice(-TELEMETRY_MAX_BUFFER_SIZE);
15932
+ }
15933
+ if (telemetryBuffer.length >= telemetry.maxBatchSize) {
15934
+ void flushTelemetry(false);
15935
+ }
15936
+ }
15937
+ function buildInaccessibleTabPageData(_cfg, tab, reason = "tab_not_accessible") {
15638
15938
  const logicalTabId = Number(tab?.logicalTabId) || void 0;
15639
15939
  const url = tab?.url || "";
15640
15940
  const title = tab?.title || (tab?.external ? "External Tab (Inaccessible)" : "Inactive Tab");
15641
- const embeddedDomain = resolveEmbeddedDomainLabel(cfg.allowedDomains);
15642
15941
  const normalizedReason = String(reason || "").trim();
15643
15942
  const reasonLine = normalizedReason ? ` Reason: ${normalizedReason}.` : "";
15644
- const content = tab?.external ? `Rover is embedded inside ${embeddedDomain}. This tab is outside direct DOM access and only virtual tab context is available.${reasonLine}` : `This tab is currently not attached to an active Rover runtime. Switch to a live tab or reopen it.${reasonLine}`;
15943
+ const content = tab?.external ? `This external tab is tracked in virtual mode only. Live DOM control and accessibility-tree access are unavailable here.${reasonLine}` : `This tab is currently not attached to an active Rover runtime. Switch to a live tab or reopen it.${reasonLine}`;
15645
15944
  return {
15646
15945
  url,
15647
15946
  title,
@@ -15659,8 +15958,7 @@ function buildInaccessibleTabPageData(cfg, tab, reason = "tab_not_accessible") {
15659
15958
  function buildTabAccessToolError(cfg, tab, reason = "tab_not_accessible") {
15660
15959
  const logicalTabId = Number(tab?.logicalTabId) || 0;
15661
15960
  const blockedUrl = tab?.url || "";
15662
- const embeddedDomain = resolveEmbeddedDomainLabel(cfg.allowedDomains);
15663
- const message = tab?.external ? `Tab ${logicalTabId} is outside Rover's embedded domain scope (${embeddedDomain}) and cannot be controlled directly.` : `Tab ${logicalTabId} is not attached to an active Rover runtime.`;
15961
+ const message = tab?.external ? `Tab ${logicalTabId} is external to the active runtime and cannot be controlled directly.` : `Tab ${logicalTabId} is not attached to an active Rover runtime.`;
15664
15962
  const code = tab?.external ? "DOMAIN_SCOPE_BLOCKED" : "TAB_NOT_ACCESSIBLE";
15665
15963
  return {
15666
15964
  success: false,
@@ -16010,6 +16308,8 @@ function ensureUnloadHandler() {
16010
16308
  sessionCoordinator?.releaseWorkflowLock(runtimeState.pendingRun.id);
16011
16309
  }
16012
16310
  persistRuntimeState();
16311
+ void flushTelemetry(true);
16312
+ stopTelemetry();
16013
16313
  cloudCheckpointClient?.markDirty();
16014
16314
  cloudCheckpointClient?.syncNow();
16015
16315
  cloudCheckpointClient?.stop();
@@ -16025,6 +16325,9 @@ function ensureUnloadHandler() {
16025
16325
  sessionCoordinator.claimLease(false);
16026
16326
  }
16027
16327
  autoResumeAttempted = false;
16328
+ if (currentConfig) {
16329
+ setupTelemetry(currentConfig);
16330
+ }
16028
16331
  };
16029
16332
  window.addEventListener("pageshow", onPageShow);
16030
16333
  }
@@ -16641,6 +16944,10 @@ function setupCloudCheckpointing(cfg) {
16641
16944
  if (!resolvedVisitorId)
16642
16945
  return;
16643
16946
  try {
16947
+ const emitCheckpointState = (payload) => {
16948
+ emit("checkpoint_state", payload);
16949
+ cfg.checkpointing?.onStateChange?.(payload);
16950
+ };
16644
16951
  cloudCheckpointClient = new RoverCloudCheckpointClient({
16645
16952
  apiBase: cfg.apiBase,
16646
16953
  apiKey: cfg.apiKey,
@@ -16656,7 +16963,36 @@ function setupCloudCheckpointing(cfg) {
16656
16963
  onCheckpoint: (payload) => {
16657
16964
  applyCloudCheckpointPayload(payload);
16658
16965
  },
16659
- onError: () => {
16966
+ onStateChange: (state, context) => {
16967
+ emitCheckpointState({
16968
+ state,
16969
+ reason: context.reason,
16970
+ action: context.action,
16971
+ code: context.code,
16972
+ message: context.message
16973
+ });
16974
+ if (state === "paused_auth") {
16975
+ emit("checkpoint_error", {
16976
+ action: context.action,
16977
+ code: context.code || "INVALID_API_KEY",
16978
+ message: context.message || "Checkpoint sync paused due to auth failure.",
16979
+ disabled: true,
16980
+ reason: context.reason || "auth_failed"
16981
+ });
16982
+ }
16983
+ },
16984
+ onError: (_error, context) => {
16985
+ cfg.checkpointing?.onError?.(context);
16986
+ if (!context.paused) {
16987
+ emit("checkpoint_error", {
16988
+ action: context.action,
16989
+ code: context.code,
16990
+ message: context.message,
16991
+ status: context.status,
16992
+ disabled: false,
16993
+ reason: "transient_failure"
16994
+ });
16995
+ }
16660
16996
  }
16661
16997
  });
16662
16998
  cloudCheckpointClient.start();
@@ -16965,6 +17301,10 @@ function handleWorkerMessage(msg) {
16965
17301
  const needsUserInput = completionState.needsUserInput;
16966
17302
  if (taskComplete) {
16967
17303
  markTaskCompleted("worker_task_complete");
17304
+ sessionCoordinator?.pruneTabs({
17305
+ dropRuntimeDetached: true,
17306
+ dropAllDetachedExternal: true
17307
+ });
16968
17308
  setUiStatus("Task completed");
16969
17309
  finalizeSuccessfulRunTimeline(typeof msg.runId === "string" ? msg.runId : void 0);
16970
17310
  } else {
@@ -17022,6 +17362,7 @@ function createRuntime(cfg) {
17022
17362
  }
17023
17363
  }
17024
17364
  setupCloudCheckpointing(cfg);
17365
+ setupTelemetry(cfg);
17025
17366
  const initialAllowActions = (cfg.allowActions ?? true) && (sessionCoordinator ? sessionCoordinator.isController() : true);
17026
17367
  bridge = new Bridge({
17027
17368
  allowActions: initialAllowActions,
@@ -17029,7 +17370,6 @@ function createRuntime(cfg) {
17029
17370
  allowedDomains: cfg.allowedDomains,
17030
17371
  domainScopeMode: cfg.domainScopeMode,
17031
17372
  externalNavigationPolicy: cfg.externalNavigationPolicy,
17032
- crossDomainPolicy: cfg.crossDomainPolicy,
17033
17373
  registerOpenedTab: (payload) => sessionCoordinator?.registerOpenedTab(payload),
17034
17374
  switchToLogicalTab: (logicalTabId) => sessionCoordinator?.switchToLogicalTab(logicalTabId) || { ok: false, reason: "No session coordinator" },
17035
17375
  listKnownTabs: () => (sessionCoordinator?.listTabs() || []).map((tab) => ({
@@ -17406,16 +17746,16 @@ function update(cfg) {
17406
17746
  setupSessionCoordinator(currentConfig);
17407
17747
  }
17408
17748
  setupCloudCheckpointing(currentConfig);
17749
+ setupTelemetry(currentConfig);
17409
17750
  if (bridge) {
17410
17751
  if (typeof cfg.allowActions === "boolean") {
17411
17752
  bridge.setAllowActions(cfg.allowActions && currentMode === "controller");
17412
17753
  }
17413
- if (cfg.allowedDomains || cfg.crossDomainPolicy || cfg.domainScopeMode || cfg.externalNavigationPolicy) {
17754
+ if (cfg.allowedDomains || cfg.domainScopeMode || cfg.externalNavigationPolicy) {
17414
17755
  bridge.setNavigationPolicy({
17415
17756
  allowedDomains: cfg.allowedDomains,
17416
17757
  domainScopeMode: cfg.domainScopeMode,
17417
- externalNavigationPolicy: cfg.externalNavigationPolicy,
17418
- crossDomainPolicy: cfg.crossDomainPolicy
17758
+ externalNavigationPolicy: cfg.externalNavigationPolicy
17419
17759
  });
17420
17760
  }
17421
17761
  }
@@ -17430,6 +17770,8 @@ function update(cfg) {
17430
17770
  function shutdown() {
17431
17771
  hideTaskSuggestion();
17432
17772
  persistRuntimeState();
17773
+ void flushTelemetry(true);
17774
+ stopTelemetry();
17433
17775
  cloudCheckpointClient?.markDirty();
17434
17776
  cloudCheckpointClient?.syncNow();
17435
17777
  cloudCheckpointClient?.stop();
@@ -17447,6 +17789,10 @@ function shutdown() {
17447
17789
  runtimeId = "";
17448
17790
  resolvedVisitorId = void 0;
17449
17791
  suppressCheckpointSync = false;
17792
+ telemetryInFlight = false;
17793
+ telemetryPausedAuth = false;
17794
+ telemetryBuffer = [];
17795
+ telemetrySeq = 0;
17450
17796
  currentMode = "controller";
17451
17797
  pendingTaskSuggestion = null;
17452
17798
  runtimeStateStore = null;