@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/README.md +38 -15
- package/dist/embed.js +16 -16
- package/dist/index.d.ts +25 -2
- package/dist/rover.js +379 -33
- package/dist/worker/rover-worker.js +41 -25
- package/package.json +1 -1
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
|
|
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
|
|
11141
|
-
this.externalNavigationPolicy = normalizeExternalNavigationPolicy(options.externalNavigationPolicy
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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}`,
|
|
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",
|
|
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
|
|
15632
|
-
const raw =
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
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
|
|
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 ? `
|
|
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
|
|
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
|
-
|
|
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.
|
|
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;
|