@rtrvr-ai/rover 1.0.3 → 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 +40 -8
- package/dist/embed.js +16 -16
- package/dist/index.d.ts +36 -2
- package/dist/rover.js +739 -127
- package/dist/worker/rover-worker.js +853 -76
- 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,9 +11465,10 @@ 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
|
-
if (this.registerOpenedTab) {
|
|
11471
|
+
if (popupAttempt.opened && this.registerOpenedTab) {
|
|
11471
11472
|
try {
|
|
11472
11473
|
const registered = await this.registerOpenedTab({
|
|
11473
11474
|
url: targetUrl,
|
|
@@ -11479,8 +11480,18 @@ var Bridge = class {
|
|
|
11479
11480
|
} catch {
|
|
11480
11481
|
}
|
|
11481
11482
|
}
|
|
11482
|
-
|
|
11483
|
-
|
|
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
|
+
}
|
|
11493
|
+
const registrationFailed = popupAttempt.opened && !logicalTabId && !!this.registerOpenedTab;
|
|
11494
|
+
if (!popupAttempt.opened) {
|
|
11484
11495
|
const reason = "Browser popup settings blocked opening a new tab.";
|
|
11485
11496
|
if (options?.policyBlocked) {
|
|
11486
11497
|
this.emitNavigationGuardrail({
|
|
@@ -11516,6 +11527,13 @@ var Bridge = class {
|
|
|
11516
11527
|
openedInNewTab: true
|
|
11517
11528
|
});
|
|
11518
11529
|
}
|
|
11530
|
+
const warningMessages = [];
|
|
11531
|
+
if (registrationFailed) {
|
|
11532
|
+
warningMessages.push("Tab opened but registration failed; tab may not be targetable.");
|
|
11533
|
+
}
|
|
11534
|
+
if (!popupAttempt.verified) {
|
|
11535
|
+
warningMessages.push("Tab open was triggered, but browser did not return a popup handle.");
|
|
11536
|
+
}
|
|
11519
11537
|
return {
|
|
11520
11538
|
success: true,
|
|
11521
11539
|
output: {
|
|
@@ -11523,13 +11541,109 @@ var Bridge = class {
|
|
|
11523
11541
|
external,
|
|
11524
11542
|
logicalTabId,
|
|
11525
11543
|
openedInNewTab: true,
|
|
11544
|
+
openVerification: popupAttempt.verified ? "verified" : "unverified",
|
|
11526
11545
|
policyBlocked: !!options?.policyBlocked,
|
|
11527
11546
|
message,
|
|
11528
|
-
...
|
|
11547
|
+
...warningMessages.length ? { warning: warningMessages.join(" ") } : {}
|
|
11529
11548
|
},
|
|
11530
11549
|
allowFallback: true
|
|
11531
11550
|
};
|
|
11532
11551
|
}
|
|
11552
|
+
openVerifiedPopup(targetUrl) {
|
|
11553
|
+
const popup = window.open("about:blank", "_blank");
|
|
11554
|
+
if (popup) {
|
|
11555
|
+
try {
|
|
11556
|
+
popup.opener = null;
|
|
11557
|
+
} catch {
|
|
11558
|
+
}
|
|
11559
|
+
try {
|
|
11560
|
+
popup.location.href = targetUrl;
|
|
11561
|
+
} catch {
|
|
11562
|
+
}
|
|
11563
|
+
return { opened: true, verified: true };
|
|
11564
|
+
}
|
|
11565
|
+
try {
|
|
11566
|
+
const directPopup = window.open(targetUrl, "_blank");
|
|
11567
|
+
if (directPopup) {
|
|
11568
|
+
try {
|
|
11569
|
+
directPopup.opener = null;
|
|
11570
|
+
} catch {
|
|
11571
|
+
}
|
|
11572
|
+
return { opened: true, verified: true };
|
|
11573
|
+
}
|
|
11574
|
+
} catch {
|
|
11575
|
+
}
|
|
11576
|
+
try {
|
|
11577
|
+
const noOpenerPopup = window.open(targetUrl, "_blank", "noopener,noreferrer");
|
|
11578
|
+
if (noOpenerPopup) {
|
|
11579
|
+
try {
|
|
11580
|
+
noOpenerPopup.opener = null;
|
|
11581
|
+
} catch {
|
|
11582
|
+
}
|
|
11583
|
+
return { opened: true, verified: true };
|
|
11584
|
+
}
|
|
11585
|
+
return { opened: true, verified: false };
|
|
11586
|
+
} catch {
|
|
11587
|
+
}
|
|
11588
|
+
try {
|
|
11589
|
+
const anchor = document.createElement("a");
|
|
11590
|
+
anchor.href = targetUrl;
|
|
11591
|
+
anchor.target = "_blank";
|
|
11592
|
+
anchor.rel = "noopener noreferrer";
|
|
11593
|
+
anchor.style.display = "none";
|
|
11594
|
+
document.body.appendChild(anchor);
|
|
11595
|
+
anchor.click();
|
|
11596
|
+
anchor.remove();
|
|
11597
|
+
return { opened: true, verified: false };
|
|
11598
|
+
} catch {
|
|
11599
|
+
return { opened: false, verified: false };
|
|
11600
|
+
}
|
|
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
|
+
}
|
|
11533
11647
|
async openElementInNewTab(args) {
|
|
11534
11648
|
const elementId = args.element_id ?? args.source_element_id ?? args.target_element_id ?? args.center_element_id ?? null;
|
|
11535
11649
|
if (!elementId) {
|
|
@@ -11663,18 +11777,11 @@ function normalizeToolName(name) {
|
|
|
11663
11777
|
function normalizeDomainScopeMode(mode) {
|
|
11664
11778
|
return mode === "host_only" ? "host_only" : "registrable_domain";
|
|
11665
11779
|
}
|
|
11666
|
-
function
|
|
11667
|
-
if (policy === "allow")
|
|
11668
|
-
return "allow";
|
|
11669
|
-
if (policy === "block_new_tab")
|
|
11670
|
-
return "open_new_tab_notice";
|
|
11671
|
-
return void 0;
|
|
11672
|
-
}
|
|
11673
|
-
function normalizeExternalNavigationPolicy(policy, legacy) {
|
|
11780
|
+
function normalizeExternalNavigationPolicy(policy) {
|
|
11674
11781
|
if (policy === "allow" || policy === "block" || policy === "open_new_tab_notice") {
|
|
11675
11782
|
return policy;
|
|
11676
11783
|
}
|
|
11677
|
-
return
|
|
11784
|
+
return "open_new_tab_notice";
|
|
11678
11785
|
}
|
|
11679
11786
|
function normalizeAllowedDomains(input, currentHost, scopeMode) {
|
|
11680
11787
|
const candidates = Array.isArray(input) ? input : [];
|
|
@@ -11950,6 +12057,7 @@ function bindRpc(port, handlers) {
|
|
|
11950
12057
|
}
|
|
11951
12058
|
|
|
11952
12059
|
// ../ui/dist/mount.js
|
|
12060
|
+
var DEFAULT_AGENT_NAME = "Rover";
|
|
11953
12061
|
var DEFAULT_MASCOT_MP4 = "https://www.rtrvr.ai/rover/mascot.mp4";
|
|
11954
12062
|
var DEFAULT_MASCOT_WEBM = "https://www.rtrvr.ai/rover/mascot.webm";
|
|
11955
12063
|
var EXPAND_THRESHOLD_OUTPUT = 280;
|
|
@@ -11999,6 +12107,22 @@ function deriveTraceKey(event) {
|
|
|
11999
12107
|
function sanitizeText(text) {
|
|
12000
12108
|
return String(text || "").trim();
|
|
12001
12109
|
}
|
|
12110
|
+
function resolveAgentName(input) {
|
|
12111
|
+
const normalized = String(input || "").trim();
|
|
12112
|
+
if (!normalized)
|
|
12113
|
+
return DEFAULT_AGENT_NAME;
|
|
12114
|
+
return normalized.slice(0, 64);
|
|
12115
|
+
}
|
|
12116
|
+
function deriveAgentInitial(name) {
|
|
12117
|
+
const normalized = String(name || "").trim();
|
|
12118
|
+
if (!normalized)
|
|
12119
|
+
return "R";
|
|
12120
|
+
return normalized[0].toUpperCase();
|
|
12121
|
+
}
|
|
12122
|
+
function deriveLauncherToken(name) {
|
|
12123
|
+
const compact = String(name || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 3).toUpperCase();
|
|
12124
|
+
return compact || "RVR";
|
|
12125
|
+
}
|
|
12002
12126
|
function parseStageFromTitle(title) {
|
|
12003
12127
|
const clean = sanitizeText(title);
|
|
12004
12128
|
const match = /^(Analyze|Route|Execute|Verify|Complete):\s*(.*)$/i.exec(clean);
|
|
@@ -12174,6 +12298,9 @@ function createExpandableRichContent(text, threshold) {
|
|
|
12174
12298
|
return wrapper;
|
|
12175
12299
|
}
|
|
12176
12300
|
function mountWidget(opts) {
|
|
12301
|
+
const agentName = resolveAgentName(opts.agent?.name);
|
|
12302
|
+
const agentInitial = deriveAgentInitial(agentName);
|
|
12303
|
+
const launcherToken = deriveLauncherToken(agentName);
|
|
12177
12304
|
const host = document.createElement("div");
|
|
12178
12305
|
host.id = "rover-widget-root";
|
|
12179
12306
|
document.documentElement.appendChild(host);
|
|
@@ -13220,7 +13347,7 @@ function mountWidget(opts) {
|
|
|
13220
13347
|
wrapper.dataset.mood = "idle";
|
|
13221
13348
|
const launcher = document.createElement("button");
|
|
13222
13349
|
launcher.className = "launcher";
|
|
13223
|
-
launcher.setAttribute("aria-label",
|
|
13350
|
+
launcher.setAttribute("aria-label", `Open ${agentName} assistant`);
|
|
13224
13351
|
const mascotDisabled = opts.mascot?.disabled === true;
|
|
13225
13352
|
let launcherVideo = null;
|
|
13226
13353
|
if (!mascotDisabled) {
|
|
@@ -13242,7 +13369,7 @@ function mountWidget(opts) {
|
|
|
13242
13369
|
}
|
|
13243
13370
|
const launcherFallback = document.createElement("span");
|
|
13244
13371
|
launcherFallback.className = "launcherFallback";
|
|
13245
|
-
launcherFallback.textContent =
|
|
13372
|
+
launcherFallback.textContent = launcherToken;
|
|
13246
13373
|
const launcherShine = document.createElement("div");
|
|
13247
13374
|
launcherShine.className = "launcherShine";
|
|
13248
13375
|
launcher.appendChild(launcherFallback);
|
|
@@ -13260,13 +13387,13 @@ function mountWidget(opts) {
|
|
|
13260
13387
|
}
|
|
13261
13388
|
const avatarFallback = document.createElement("span");
|
|
13262
13389
|
avatarFallback.className = "avatarFallback";
|
|
13263
|
-
avatarFallback.textContent =
|
|
13390
|
+
avatarFallback.textContent = agentInitial;
|
|
13264
13391
|
avatar.appendChild(avatarFallback);
|
|
13265
13392
|
const meta = document.createElement("div");
|
|
13266
13393
|
meta.className = "meta";
|
|
13267
13394
|
const titleEl = document.createElement("div");
|
|
13268
13395
|
titleEl.className = "title";
|
|
13269
|
-
titleEl.textContent =
|
|
13396
|
+
titleEl.textContent = agentName;
|
|
13270
13397
|
const statusEl = document.createElement("div");
|
|
13271
13398
|
statusEl.className = "status";
|
|
13272
13399
|
const statusDot = document.createElement("span");
|
|
@@ -13399,7 +13526,7 @@ function mountWidget(opts) {
|
|
|
13399
13526
|
composer.className = "composer";
|
|
13400
13527
|
const composerTextarea = document.createElement("textarea");
|
|
13401
13528
|
composerTextarea.rows = 1;
|
|
13402
|
-
composerTextarea.placeholder =
|
|
13529
|
+
composerTextarea.placeholder = `Ask ${agentName} to act on this website...`;
|
|
13403
13530
|
composer.appendChild(composerTextarea);
|
|
13404
13531
|
const sendButton = document.createElement("button");
|
|
13405
13532
|
sendButton.type = "submit";
|
|
@@ -13707,7 +13834,7 @@ function mountWidget(opts) {
|
|
|
13707
13834
|
if (mode === "controller") {
|
|
13708
13835
|
modeBadge.textContent = "active";
|
|
13709
13836
|
menuTakeControl.style.display = "none";
|
|
13710
|
-
inputEl.placeholder =
|
|
13837
|
+
inputEl.placeholder = `Ask ${agentName} to act on this website...`;
|
|
13711
13838
|
} else {
|
|
13712
13839
|
modeBadge.textContent = "observer";
|
|
13713
13840
|
if (executionMeta?.canTakeControl !== false) {
|
|
@@ -13720,7 +13847,7 @@ function mountWidget(opts) {
|
|
|
13720
13847
|
} else if (canComposeInObserver) {
|
|
13721
13848
|
inputEl.placeholder = "Send to take control and run here.";
|
|
13722
13849
|
} else if (executionMeta?.activeLogicalTabId && executionMeta?.localLogicalTabId && executionMeta.activeLogicalTabId !== executionMeta.localLogicalTabId) {
|
|
13723
|
-
inputEl.placeholder = `Observing:
|
|
13850
|
+
inputEl.placeholder = `Observing: ${agentName} is acting in tab #${executionMeta.activeLogicalTabId}`;
|
|
13724
13851
|
} else {
|
|
13725
13852
|
inputEl.placeholder = "Observer mode. Take control to run actions here.";
|
|
13726
13853
|
}
|
|
@@ -13920,6 +14047,9 @@ function mountWidget(opts) {
|
|
|
13920
14047
|
var SHARED_VERSION = 2;
|
|
13921
14048
|
var SHARED_KEY_PREFIX = "rover:shared:";
|
|
13922
14049
|
var SHARED_CHANNEL_PREFIX = "rover:channel:";
|
|
14050
|
+
var STALE_DETACHED_EXTERNAL_TAB_MS = 2 * 6e4;
|
|
14051
|
+
var STALE_DETACHED_TAB_MS = 10 * 6e4;
|
|
14052
|
+
var STALE_RUNTIME_TAB_MS = 45e3;
|
|
13923
14053
|
function now() {
|
|
13924
14054
|
return Date.now();
|
|
13925
14055
|
}
|
|
@@ -14125,6 +14255,36 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14125
14255
|
lastNotifiedRole;
|
|
14126
14256
|
roleChangeTimer = null;
|
|
14127
14257
|
static ROLE_CHANGE_DEBOUNCE_MS = 200;
|
|
14258
|
+
pruneDetachedTabs(draft, options) {
|
|
14259
|
+
const dropRuntimeDetached = !!options?.dropRuntimeDetached;
|
|
14260
|
+
const dropAllDetachedExternal = !!options?.dropAllDetachedExternal;
|
|
14261
|
+
const nowMs2 = now();
|
|
14262
|
+
const before = draft.tabs.length;
|
|
14263
|
+
draft.tabs = draft.tabs.filter((tab) => {
|
|
14264
|
+
if (tab.runtimeId) {
|
|
14265
|
+
if (tab.runtimeId === this.runtimeId)
|
|
14266
|
+
return true;
|
|
14267
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
|
|
14268
|
+
}
|
|
14269
|
+
if (dropRuntimeDetached && tab.openerRuntimeId === this.runtimeId) {
|
|
14270
|
+
return false;
|
|
14271
|
+
}
|
|
14272
|
+
if (tab.external) {
|
|
14273
|
+
if (dropAllDetachedExternal)
|
|
14274
|
+
return false;
|
|
14275
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
|
|
14276
|
+
}
|
|
14277
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
|
|
14278
|
+
});
|
|
14279
|
+
if (before !== draft.tabs.length) {
|
|
14280
|
+
if (draft.activeLogicalTabId && !draft.tabs.some((tab) => tab.logicalTabId === draft.activeLogicalTabId)) {
|
|
14281
|
+
draft.activeLogicalTabId = this.localLogicalTabId || draft.tabs[0]?.logicalTabId;
|
|
14282
|
+
}
|
|
14283
|
+
if (draft.nextLogicalTabId <= (draft.tabs.at(-1)?.logicalTabId ?? 0)) {
|
|
14284
|
+
draft.nextLogicalTabId = draft.tabs.reduce((max, tab) => Math.max(max, tab.logicalTabId), 0) + 1;
|
|
14285
|
+
}
|
|
14286
|
+
}
|
|
14287
|
+
}
|
|
14128
14288
|
constructor(options) {
|
|
14129
14289
|
this.siteId = options.siteId;
|
|
14130
14290
|
this.sessionId = options.sessionId;
|
|
@@ -14144,6 +14304,9 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14144
14304
|
if (this.started)
|
|
14145
14305
|
return;
|
|
14146
14306
|
this.started = true;
|
|
14307
|
+
this.mutate("local", (draft) => {
|
|
14308
|
+
this.pruneDetachedTabs(draft, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14309
|
+
});
|
|
14147
14310
|
this.registerCurrentTab(window.location.href, document.title || void 0);
|
|
14148
14311
|
this.claimLease(false);
|
|
14149
14312
|
if (!this.state.task) {
|
|
@@ -14278,10 +14441,27 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14278
14441
|
}
|
|
14279
14442
|
hydrateExternalState(raw) {
|
|
14280
14443
|
const incoming = sanitizeSharedState(raw, this.siteId, this.sessionId);
|
|
14444
|
+
this.pruneDetachedTabs(incoming, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14281
14445
|
const beforeSeq = this.state.seq;
|
|
14282
14446
|
const beforeUpdatedAt = this.state.updatedAt;
|
|
14283
14447
|
this.applyIncomingState(incoming);
|
|
14284
|
-
|
|
14448
|
+
let changed = this.state.seq !== beforeSeq || this.state.updatedAt !== beforeUpdatedAt;
|
|
14449
|
+
if (!changed) {
|
|
14450
|
+
const hasDetachedCandidates = this.state.tabs.some((tab) => {
|
|
14451
|
+
if (tab.runtimeId)
|
|
14452
|
+
return false;
|
|
14453
|
+
if (tab.external)
|
|
14454
|
+
return true;
|
|
14455
|
+
return tab.openerRuntimeId === this.runtimeId;
|
|
14456
|
+
});
|
|
14457
|
+
if (hasDetachedCandidates) {
|
|
14458
|
+
const beforeTabCount = this.state.tabs.length;
|
|
14459
|
+
this.mutate("local", (draft) => {
|
|
14460
|
+
this.pruneDetachedTabs(draft, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14461
|
+
});
|
|
14462
|
+
changed = this.state.tabs.length !== beforeTabCount;
|
|
14463
|
+
}
|
|
14464
|
+
}
|
|
14285
14465
|
if (!changed)
|
|
14286
14466
|
return false;
|
|
14287
14467
|
this.persistState();
|
|
@@ -14291,7 +14471,23 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14291
14471
|
return true;
|
|
14292
14472
|
}
|
|
14293
14473
|
listTabs() {
|
|
14294
|
-
|
|
14474
|
+
const nowMs2 = now();
|
|
14475
|
+
return this.state.tabs.filter((tab) => {
|
|
14476
|
+
if (tab.runtimeId) {
|
|
14477
|
+
if (tab.runtimeId === this.runtimeId)
|
|
14478
|
+
return true;
|
|
14479
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
|
|
14480
|
+
}
|
|
14481
|
+
if (tab.external) {
|
|
14482
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
|
|
14483
|
+
}
|
|
14484
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
|
|
14485
|
+
});
|
|
14486
|
+
}
|
|
14487
|
+
pruneTabs(options) {
|
|
14488
|
+
this.mutate("local", (draft) => {
|
|
14489
|
+
this.pruneDetachedTabs(draft, options);
|
|
14490
|
+
});
|
|
14295
14491
|
}
|
|
14296
14492
|
startNewTask(task) {
|
|
14297
14493
|
const startedAt = Number(task.startedAt) || now();
|
|
@@ -14312,6 +14508,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14312
14508
|
draft.uiStatus = void 0;
|
|
14313
14509
|
draft.activeRun = void 0;
|
|
14314
14510
|
draft.workerContext = void 0;
|
|
14511
|
+
this.pruneDetachedTabs(draft, { dropAllDetachedExternal: true });
|
|
14315
14512
|
});
|
|
14316
14513
|
return nextTask;
|
|
14317
14514
|
}
|
|
@@ -14426,9 +14623,6 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14426
14623
|
this.mutate("local", (draft) => {
|
|
14427
14624
|
if (!activeRun) {
|
|
14428
14625
|
draft.activeRun = void 0;
|
|
14429
|
-
if (draft.task && draft.task.status === "running") {
|
|
14430
|
-
draft.task.status = "completed";
|
|
14431
|
-
}
|
|
14432
14626
|
return;
|
|
14433
14627
|
}
|
|
14434
14628
|
draft.activeRun = {
|
|
@@ -14494,6 +14688,14 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14494
14688
|
const normalizedUrl = normalizeUrl2(payload.url);
|
|
14495
14689
|
let logicalTabId = 0;
|
|
14496
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
|
+
}
|
|
14497
14699
|
logicalTabId = draft.nextLogicalTabId++;
|
|
14498
14700
|
draft.tabs.push({
|
|
14499
14701
|
logicalTabId,
|
|
@@ -14781,6 +14983,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14781
14983
|
if (draft.workflowLock?.runtimeId === this.runtimeId) {
|
|
14782
14984
|
draft.workflowLock.expiresAt = now() + this.leaseMs * 5;
|
|
14783
14985
|
}
|
|
14986
|
+
this.pruneDetachedTabs(draft);
|
|
14784
14987
|
});
|
|
14785
14988
|
this.notifyRoleChange();
|
|
14786
14989
|
}
|
|
@@ -14872,6 +15075,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14872
15075
|
return;
|
|
14873
15076
|
if (incoming.seq === this.state.seq && incoming.updatedAt <= this.state.updatedAt)
|
|
14874
15077
|
return;
|
|
15078
|
+
this.pruneDetachedTabs(incoming);
|
|
14875
15079
|
this.state = incoming;
|
|
14876
15080
|
this.syncLocalLogicalTabId();
|
|
14877
15081
|
this.onStateChange?.(this.state, "remote");
|
|
@@ -14944,6 +15148,7 @@ var RoverCloudCheckpointClient = class {
|
|
|
14944
15148
|
shouldWrite;
|
|
14945
15149
|
buildCheckpoint;
|
|
14946
15150
|
onCheckpoint;
|
|
15151
|
+
onStateChange;
|
|
14947
15152
|
onError;
|
|
14948
15153
|
started = false;
|
|
14949
15154
|
dirty = false;
|
|
@@ -14955,8 +15160,9 @@ var RoverCloudCheckpointClient = class {
|
|
|
14955
15160
|
lastAppliedRemoteUpdatedAt = 0;
|
|
14956
15161
|
pushInFlight = false;
|
|
14957
15162
|
pullInFlight = false;
|
|
15163
|
+
state = "active";
|
|
14958
15164
|
constructor(options) {
|
|
14959
|
-
const token = String(options.
|
|
15165
|
+
const token = String(options.authToken || options.apiKey || "").trim();
|
|
14960
15166
|
if (!token) {
|
|
14961
15167
|
throw toError("Rover cloud checkpoint requires apiKey or authToken.");
|
|
14962
15168
|
}
|
|
@@ -14971,12 +15177,14 @@ var RoverCloudCheckpointClient = class {
|
|
|
14971
15177
|
this.shouldWrite = options.shouldWrite || (() => true);
|
|
14972
15178
|
this.buildCheckpoint = options.buildCheckpoint;
|
|
14973
15179
|
this.onCheckpoint = options.onCheckpoint;
|
|
15180
|
+
this.onStateChange = options.onStateChange;
|
|
14974
15181
|
this.onError = options.onError;
|
|
14975
15182
|
}
|
|
14976
15183
|
start() {
|
|
14977
15184
|
if (this.started)
|
|
14978
15185
|
return;
|
|
14979
15186
|
this.started = true;
|
|
15187
|
+
this.setState("active");
|
|
14980
15188
|
this.flushTimer = window.setInterval(() => {
|
|
14981
15189
|
void this.flush(false);
|
|
14982
15190
|
}, this.flushIntervalMs);
|
|
@@ -15009,6 +15217,8 @@ var RoverCloudCheckpointClient = class {
|
|
|
15009
15217
|
async flush(force) {
|
|
15010
15218
|
if (this.pushInFlight)
|
|
15011
15219
|
return;
|
|
15220
|
+
if (this.state === "paused_auth")
|
|
15221
|
+
return;
|
|
15012
15222
|
if (!this.dirty && !force)
|
|
15013
15223
|
return;
|
|
15014
15224
|
if (!this.shouldWrite())
|
|
@@ -15042,8 +15252,9 @@ var RoverCloudCheckpointClient = class {
|
|
|
15042
15252
|
this.applyRemoteCheckpoint(response.checkpoint, "push_stale");
|
|
15043
15253
|
this.dirty = false;
|
|
15044
15254
|
}
|
|
15255
|
+
this.setState("active");
|
|
15045
15256
|
} catch (error) {
|
|
15046
|
-
this.
|
|
15257
|
+
this.handleCheckpointError(error, "roverSessionCheckpointUpsert");
|
|
15047
15258
|
} finally {
|
|
15048
15259
|
this.pushInFlight = false;
|
|
15049
15260
|
}
|
|
@@ -15051,6 +15262,8 @@ var RoverCloudCheckpointClient = class {
|
|
|
15051
15262
|
async pull(force) {
|
|
15052
15263
|
if (this.pullInFlight)
|
|
15053
15264
|
return;
|
|
15265
|
+
if (this.state === "paused_auth")
|
|
15266
|
+
return;
|
|
15054
15267
|
if (!force && Date.now() - this.lastPullAt < this.pullIntervalMs)
|
|
15055
15268
|
return;
|
|
15056
15269
|
this.pullInFlight = true;
|
|
@@ -15063,8 +15276,9 @@ var RoverCloudCheckpointClient = class {
|
|
|
15063
15276
|
if (!response?.found || !response?.checkpoint || typeof response.checkpoint !== "object")
|
|
15064
15277
|
return;
|
|
15065
15278
|
this.applyRemoteCheckpoint(response.checkpoint, "pull");
|
|
15279
|
+
this.setState("active");
|
|
15066
15280
|
} catch (error) {
|
|
15067
|
-
this.
|
|
15281
|
+
this.handleCheckpointError(error, "roverSessionCheckpointGet");
|
|
15068
15282
|
} finally {
|
|
15069
15283
|
this.pullInFlight = false;
|
|
15070
15284
|
}
|
|
@@ -15093,14 +15307,68 @@ var RoverCloudCheckpointClient = class {
|
|
|
15093
15307
|
const text = await response.text().catch(() => "");
|
|
15094
15308
|
payload2 = text ? { error: truncateText(text, 2e3) } : void 0;
|
|
15095
15309
|
}
|
|
15096
|
-
throw toError(`Checkpoint HTTP ${response.status}`,
|
|
15310
|
+
throw toError(`Checkpoint HTTP ${response.status}`, {
|
|
15311
|
+
action,
|
|
15312
|
+
status: response.status,
|
|
15313
|
+
payload: payload2
|
|
15314
|
+
});
|
|
15097
15315
|
}
|
|
15098
15316
|
const payload = await response.json();
|
|
15099
15317
|
if (payload?.success === false) {
|
|
15100
|
-
throw toError("Checkpoint extensionRouter returned success=false",
|
|
15318
|
+
throw toError("Checkpoint extensionRouter returned success=false", {
|
|
15319
|
+
action,
|
|
15320
|
+
status: response.status,
|
|
15321
|
+
payload
|
|
15322
|
+
});
|
|
15101
15323
|
}
|
|
15102
15324
|
return payload?.data;
|
|
15103
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
|
+
}
|
|
15104
15372
|
};
|
|
15105
15373
|
|
|
15106
15374
|
// dist/runtimeStorage.js
|
|
@@ -15238,9 +15506,11 @@ var MAX_AUTO_RESUME_AGE_MS = 15 * 6e4;
|
|
|
15238
15506
|
var MAX_AUTO_RESUME_ATTEMPTS = 12;
|
|
15239
15507
|
var VISITOR_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
15240
15508
|
var CHECKPOINT_PAYLOAD_VERSION = 1;
|
|
15241
|
-
var
|
|
15242
|
-
var
|
|
15243
|
-
var
|
|
15509
|
+
var ACTIVE_PENDING_RUN_GRACE_MS = 3e3;
|
|
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;
|
|
15244
15514
|
var instance = null;
|
|
15245
15515
|
var bridge = null;
|
|
15246
15516
|
var worker = null;
|
|
@@ -15252,6 +15522,11 @@ var runtimeStateStore = null;
|
|
|
15252
15522
|
var runtimeId = "";
|
|
15253
15523
|
var sessionCoordinator = null;
|
|
15254
15524
|
var cloudCheckpointClient = null;
|
|
15525
|
+
var telemetryFlushTimer = null;
|
|
15526
|
+
var telemetryBuffer = [];
|
|
15527
|
+
var telemetryInFlight = false;
|
|
15528
|
+
var telemetryPausedAuth = false;
|
|
15529
|
+
var telemetrySeq = 0;
|
|
15255
15530
|
var resolvedVisitorId = void 0;
|
|
15256
15531
|
var suppressCheckpointSync = false;
|
|
15257
15532
|
var currentMode = "controller";
|
|
@@ -15277,6 +15552,7 @@ var RUN_SCOPED_WORKER_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
15277
15552
|
var pendingToolRegistrations = [];
|
|
15278
15553
|
var eventHandlers = /* @__PURE__ */ new Map();
|
|
15279
15554
|
function emit(event, payload) {
|
|
15555
|
+
recordTelemetryEvent(event, payload);
|
|
15280
15556
|
const handlers = eventHandlers.get(event);
|
|
15281
15557
|
if (!handlers)
|
|
15282
15558
|
return;
|
|
@@ -15482,6 +15758,240 @@ function safeSerialize(value) {
|
|
|
15482
15758
|
}
|
|
15483
15759
|
}
|
|
15484
15760
|
}
|
|
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
|
+
};
|
|
15776
|
+
}
|
|
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") {
|
|
15938
|
+
const logicalTabId = Number(tab?.logicalTabId) || void 0;
|
|
15939
|
+
const url = tab?.url || "";
|
|
15940
|
+
const title = tab?.title || (tab?.external ? "External Tab (Inaccessible)" : "Inactive Tab");
|
|
15941
|
+
const normalizedReason = String(reason || "").trim();
|
|
15942
|
+
const reasonLine = normalizedReason ? ` Reason: ${normalizedReason}.` : "";
|
|
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}`;
|
|
15944
|
+
return {
|
|
15945
|
+
url,
|
|
15946
|
+
title,
|
|
15947
|
+
contentType: "text/html",
|
|
15948
|
+
content,
|
|
15949
|
+
metadata: {
|
|
15950
|
+
inaccessible: true,
|
|
15951
|
+
external: !!tab?.external,
|
|
15952
|
+
accessMode: tab?.external ? "external_placeholder" : "inactive_tab",
|
|
15953
|
+
reason,
|
|
15954
|
+
logicalTabId
|
|
15955
|
+
}
|
|
15956
|
+
};
|
|
15957
|
+
}
|
|
15958
|
+
function buildTabAccessToolError(cfg, tab, reason = "tab_not_accessible") {
|
|
15959
|
+
const logicalTabId = Number(tab?.logicalTabId) || 0;
|
|
15960
|
+
const blockedUrl = tab?.url || "";
|
|
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.`;
|
|
15962
|
+
const code = tab?.external ? "DOMAIN_SCOPE_BLOCKED" : "TAB_NOT_ACCESSIBLE";
|
|
15963
|
+
return {
|
|
15964
|
+
success: false,
|
|
15965
|
+
error: message,
|
|
15966
|
+
allowFallback: true,
|
|
15967
|
+
output: {
|
|
15968
|
+
success: false,
|
|
15969
|
+
error: {
|
|
15970
|
+
code,
|
|
15971
|
+
message,
|
|
15972
|
+
missing: [],
|
|
15973
|
+
next_action: tab?.external ? "Use open_new_tab for external context or continue on an in-scope tab." : "Switch to an active tab and retry.",
|
|
15974
|
+
retryable: false
|
|
15975
|
+
},
|
|
15976
|
+
blocked_url: blockedUrl || void 0,
|
|
15977
|
+
logical_tab_id: logicalTabId || void 0,
|
|
15978
|
+
external: !!tab?.external,
|
|
15979
|
+
policy_action: tab?.external ? cfg.externalNavigationPolicy || "open_new_tab_notice" : void 0,
|
|
15980
|
+
reason
|
|
15981
|
+
},
|
|
15982
|
+
errorDetails: {
|
|
15983
|
+
code,
|
|
15984
|
+
message,
|
|
15985
|
+
retryable: false,
|
|
15986
|
+
details: {
|
|
15987
|
+
logicalTabId,
|
|
15988
|
+
blockedUrl,
|
|
15989
|
+
external: !!tab?.external,
|
|
15990
|
+
reason
|
|
15991
|
+
}
|
|
15992
|
+
}
|
|
15993
|
+
};
|
|
15994
|
+
}
|
|
15485
15995
|
function normalizeStatusStage(input) {
|
|
15486
15996
|
if (input === "analyze" || input === "route" || input === "execute" || input === "verify" || input === "complete") {
|
|
15487
15997
|
return input;
|
|
@@ -15557,14 +16067,26 @@ function shouldIgnoreRunScopedWorkerMessage(msg) {
|
|
|
15557
16067
|
return false;
|
|
15558
16068
|
}
|
|
15559
16069
|
if (type === "run_completed") {
|
|
15560
|
-
if (!pendingRunId)
|
|
16070
|
+
if (!pendingRunId) {
|
|
16071
|
+
const sharedRunId = sessionCoordinator?.getState()?.activeRun?.runId;
|
|
16072
|
+
if (sharedRunId && sharedRunId === messageRunId)
|
|
16073
|
+
return false;
|
|
15561
16074
|
return true;
|
|
16075
|
+
}
|
|
15562
16076
|
return pendingRunId !== messageRunId;
|
|
15563
16077
|
}
|
|
15564
16078
|
if (!pendingRunId)
|
|
15565
16079
|
return true;
|
|
15566
16080
|
return pendingRunId !== messageRunId;
|
|
15567
16081
|
}
|
|
16082
|
+
function normalizeRunCompletionState(msg) {
|
|
16083
|
+
if (!msg || typeof msg !== "object") {
|
|
16084
|
+
return { taskComplete: false, needsUserInput: false };
|
|
16085
|
+
}
|
|
16086
|
+
const needsUserInput = msg.needsUserInput === true;
|
|
16087
|
+
const taskComplete = msg.taskComplete === true && !needsUserInput;
|
|
16088
|
+
return { taskComplete, needsUserInput };
|
|
16089
|
+
}
|
|
15568
16090
|
function getLatestAssistantText(runId) {
|
|
15569
16091
|
if (runId && latestAssistantByRunId.has(runId)) {
|
|
15570
16092
|
return latestAssistantByRunId.get(runId);
|
|
@@ -15786,6 +16308,8 @@ function ensureUnloadHandler() {
|
|
|
15786
16308
|
sessionCoordinator?.releaseWorkflowLock(runtimeState.pendingRun.id);
|
|
15787
16309
|
}
|
|
15788
16310
|
persistRuntimeState();
|
|
16311
|
+
void flushTelemetry(true);
|
|
16312
|
+
stopTelemetry();
|
|
15789
16313
|
cloudCheckpointClient?.markDirty();
|
|
15790
16314
|
cloudCheckpointClient?.syncNow();
|
|
15791
16315
|
cloudCheckpointClient?.stop();
|
|
@@ -15801,6 +16325,9 @@ function ensureUnloadHandler() {
|
|
|
15801
16325
|
sessionCoordinator.claimLease(false);
|
|
15802
16326
|
}
|
|
15803
16327
|
autoResumeAttempted = false;
|
|
16328
|
+
if (currentConfig) {
|
|
16329
|
+
setupTelemetry(currentConfig);
|
|
16330
|
+
}
|
|
15804
16331
|
};
|
|
15805
16332
|
window.addEventListener("pageshow", onPageShow);
|
|
15806
16333
|
}
|
|
@@ -15828,6 +16355,33 @@ function markTaskActivity(role, timestamp = Date.now()) {
|
|
|
15828
16355
|
task.endedAt = void 0;
|
|
15829
16356
|
}
|
|
15830
16357
|
}
|
|
16358
|
+
function markTaskRunning(reason = "worker_task_active", timestamp = Date.now()) {
|
|
16359
|
+
if (!runtimeState)
|
|
16360
|
+
return;
|
|
16361
|
+
const task = ensureActiveTask(reason);
|
|
16362
|
+
if (!task)
|
|
16363
|
+
return;
|
|
16364
|
+
task.status = "running";
|
|
16365
|
+
task.endedAt = void 0;
|
|
16366
|
+
task.boundaryReason = reason;
|
|
16367
|
+
if (!task.lastUserAt && !task.lastAssistantAt) {
|
|
16368
|
+
task.lastAssistantAt = timestamp;
|
|
16369
|
+
}
|
|
16370
|
+
sessionCoordinator?.syncTask({ ...task }, runtimeState.taskEpoch);
|
|
16371
|
+
persistRuntimeState();
|
|
16372
|
+
}
|
|
16373
|
+
function markTaskCompleted(reason = "worker_task_complete", timestamp = Date.now()) {
|
|
16374
|
+
if (!runtimeState)
|
|
16375
|
+
return;
|
|
16376
|
+
const task = ensureActiveTask(reason);
|
|
16377
|
+
if (!task)
|
|
16378
|
+
return;
|
|
16379
|
+
task.status = "completed";
|
|
16380
|
+
task.endedAt = timestamp;
|
|
16381
|
+
task.boundaryReason = reason;
|
|
16382
|
+
sessionCoordinator?.syncTask({ ...task }, runtimeState.taskEpoch);
|
|
16383
|
+
persistRuntimeState();
|
|
16384
|
+
}
|
|
15831
16385
|
function hideTaskSuggestion() {
|
|
15832
16386
|
pendingTaskSuggestion = null;
|
|
15833
16387
|
ui?.setTaskSuggestion({ visible: false });
|
|
@@ -15837,53 +16391,37 @@ function clearTaskUiState() {
|
|
|
15837
16391
|
ui?.clearTimeline();
|
|
15838
16392
|
hideTaskSuggestion();
|
|
15839
16393
|
}
|
|
15840
|
-
function
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
if (!ta.size || !tb.size)
|
|
15853
|
-
return 0;
|
|
15854
|
-
let intersection = 0;
|
|
15855
|
-
for (const token of ta) {
|
|
15856
|
-
if (tb.has(token))
|
|
15857
|
-
intersection += 1;
|
|
16394
|
+
function isPendingRunLikelyActive() {
|
|
16395
|
+
const pending = runtimeState?.pendingRun;
|
|
16396
|
+
if (!pending)
|
|
16397
|
+
return false;
|
|
16398
|
+
if (!sessionCoordinator)
|
|
16399
|
+
return true;
|
|
16400
|
+
const sharedActiveRun = sessionCoordinator?.getState()?.activeRun;
|
|
16401
|
+
if (sharedActiveRun?.runId && sharedActiveRun.runId === pending.id) {
|
|
16402
|
+
return true;
|
|
16403
|
+
}
|
|
16404
|
+
if (sharedActiveRun?.runtimeId && sharedActiveRun.runtimeId !== runtimeId) {
|
|
16405
|
+
return true;
|
|
15858
16406
|
}
|
|
15859
|
-
|
|
16407
|
+
const ageMs = Date.now() - Number(pending.startedAt || 0);
|
|
16408
|
+
return ageMs >= 0 && ageMs <= ACTIVE_PENDING_RUN_GRACE_MS;
|
|
15860
16409
|
}
|
|
15861
|
-
function
|
|
15862
|
-
if (!runtimeState
|
|
15863
|
-
return
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
const
|
|
15870
|
-
if (!
|
|
15871
|
-
return
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
return { suggest: false };
|
|
15877
|
-
const recent = collectRecentUserTextsForHeuristic();
|
|
15878
|
-
if (!recent.length)
|
|
15879
|
-
return { suggest: true, reason: "inactivity_only" };
|
|
15880
|
-
const baseline = recent.join(" ");
|
|
15881
|
-
const overlap = computeSemanticOverlap(baseline, text);
|
|
15882
|
-
const threshold = Math.max(0.05, Math.min(0.9, Number(currentConfig.taskContext?.semanticSimilarityThreshold) || DEFAULT_SEMANTIC_SIMILARITY_THRESHOLD));
|
|
15883
|
-
if (overlap < threshold) {
|
|
15884
|
-
return { suggest: true, reason: `low_similarity_${overlap.toFixed(2)}` };
|
|
15885
|
-
}
|
|
15886
|
-
return { suggest: false };
|
|
16410
|
+
function maybeClearStalePendingRun() {
|
|
16411
|
+
if (!runtimeState?.pendingRun)
|
|
16412
|
+
return;
|
|
16413
|
+
if (isPendingRunLikelyActive())
|
|
16414
|
+
return;
|
|
16415
|
+
if (!sessionCoordinator)
|
|
16416
|
+
return;
|
|
16417
|
+
const pending = runtimeState.pendingRun;
|
|
16418
|
+
const ageMs = Date.now() - Number(pending.startedAt || 0);
|
|
16419
|
+
if (!Number.isFinite(ageMs) || ageMs < STALE_PENDING_RUN_MS)
|
|
16420
|
+
return;
|
|
16421
|
+
setPendingRun(void 0);
|
|
16422
|
+
sessionCoordinator?.clearActiveRunRuntimeId(pending.id);
|
|
16423
|
+
sessionCoordinator?.releaseWorkflowLock(pending.id);
|
|
16424
|
+
sessionCoordinator?.setActiveRun(void 0);
|
|
15887
16425
|
}
|
|
15888
16426
|
function appendUiMessage(role, text, persist = true, options) {
|
|
15889
16427
|
const clean = truncateText2(String(text || ""));
|
|
@@ -16068,28 +16606,16 @@ function dispatchUserPrompt(text, options) {
|
|
|
16068
16606
|
const trimmed = String(text || "").trim();
|
|
16069
16607
|
if (!trimmed)
|
|
16070
16608
|
return;
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
ui?.setTaskSuggestion({
|
|
16082
|
-
visible: true,
|
|
16083
|
-
text: "This looks like a different request. Start a new task or continue this one?",
|
|
16084
|
-
primaryLabel: "Start new",
|
|
16085
|
-
secondaryLabel: "Continue"
|
|
16086
|
-
});
|
|
16087
|
-
emit("task_suggested_reset", {
|
|
16088
|
-
text: trimmed,
|
|
16089
|
-
reason: pendingTaskSuggestion.reason
|
|
16090
|
-
});
|
|
16091
|
-
return;
|
|
16092
|
-
}
|
|
16609
|
+
maybeClearStalePendingRun();
|
|
16610
|
+
const activeTaskStatus = runtimeState?.activeTask?.status;
|
|
16611
|
+
const shouldStartFreshTask = !!options?.startNewTask || activeTaskStatus === "completed" || activeTaskStatus === "ended";
|
|
16612
|
+
sessionCoordinator?.pruneTabs({
|
|
16613
|
+
dropRuntimeDetached: true,
|
|
16614
|
+
dropAllDetachedExternal: shouldStartFreshTask
|
|
16615
|
+
});
|
|
16616
|
+
if (shouldStartFreshTask) {
|
|
16617
|
+
const autoReason = options?.reason || (activeTaskStatus === "completed" ? "auto_after_task_complete" : "auto_after_task_end");
|
|
16618
|
+
newTask({ reason: autoReason, clearUi: true });
|
|
16093
16619
|
}
|
|
16094
16620
|
hideTaskSuggestion();
|
|
16095
16621
|
postRun(trimmed, { appendUserMessage: true, resume: false, autoResume: true });
|
|
@@ -16418,6 +16944,10 @@ function setupCloudCheckpointing(cfg) {
|
|
|
16418
16944
|
if (!resolvedVisitorId)
|
|
16419
16945
|
return;
|
|
16420
16946
|
try {
|
|
16947
|
+
const emitCheckpointState = (payload) => {
|
|
16948
|
+
emit("checkpoint_state", payload);
|
|
16949
|
+
cfg.checkpointing?.onStateChange?.(payload);
|
|
16950
|
+
};
|
|
16421
16951
|
cloudCheckpointClient = new RoverCloudCheckpointClient({
|
|
16422
16952
|
apiBase: cfg.apiBase,
|
|
16423
16953
|
apiKey: cfg.apiKey,
|
|
@@ -16433,7 +16963,36 @@ function setupCloudCheckpointing(cfg) {
|
|
|
16433
16963
|
onCheckpoint: (payload) => {
|
|
16434
16964
|
applyCloudCheckpointPayload(payload);
|
|
16435
16965
|
},
|
|
16436
|
-
|
|
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
|
+
}
|
|
16437
16996
|
}
|
|
16438
16997
|
});
|
|
16439
16998
|
cloudCheckpointClient.start();
|
|
@@ -16727,6 +17286,7 @@ function handleWorkerMessage(msg) {
|
|
|
16727
17286
|
}
|
|
16728
17287
|
sessionCoordinator?.setActiveRun(void 0);
|
|
16729
17288
|
if (!msg.ok && msg.error) {
|
|
17289
|
+
markTaskRunning("worker_run_failed");
|
|
16730
17290
|
setUiStatus(`Task failed: ${String(msg.error)}`);
|
|
16731
17291
|
latestAssistantByRunId.delete(String(msg.runId || ""));
|
|
16732
17292
|
appendTimelineEvent({
|
|
@@ -16736,8 +17296,27 @@ function handleWorkerMessage(msg) {
|
|
|
16736
17296
|
status: "error"
|
|
16737
17297
|
});
|
|
16738
17298
|
} else if (msg.ok) {
|
|
16739
|
-
|
|
16740
|
-
|
|
17299
|
+
const completionState = normalizeRunCompletionState(msg);
|
|
17300
|
+
const taskComplete = completionState.taskComplete;
|
|
17301
|
+
const needsUserInput = completionState.needsUserInput;
|
|
17302
|
+
if (taskComplete) {
|
|
17303
|
+
markTaskCompleted("worker_task_complete");
|
|
17304
|
+
sessionCoordinator?.pruneTabs({
|
|
17305
|
+
dropRuntimeDetached: true,
|
|
17306
|
+
dropAllDetachedExternal: true
|
|
17307
|
+
});
|
|
17308
|
+
setUiStatus("Task completed");
|
|
17309
|
+
finalizeSuccessfulRunTimeline(typeof msg.runId === "string" ? msg.runId : void 0);
|
|
17310
|
+
} else {
|
|
17311
|
+
markTaskRunning(needsUserInput ? "worker_waiting_for_input" : "worker_continuation");
|
|
17312
|
+
setUiStatus(needsUserInput ? "Need more input to continue" : "Execution finished. Continue when ready.");
|
|
17313
|
+
appendTimelineEvent({
|
|
17314
|
+
kind: "status",
|
|
17315
|
+
title: needsUserInput ? "Waiting for your input" : "Continuation available",
|
|
17316
|
+
detail: needsUserInput ? "Planner requested more information before marking the task complete." : "Task is still active and will continue with your next message.",
|
|
17317
|
+
status: "info"
|
|
17318
|
+
});
|
|
17319
|
+
}
|
|
16741
17320
|
if (typeof msg.runId === "string" && msg.runId) {
|
|
16742
17321
|
latestAssistantByRunId.delete(msg.runId);
|
|
16743
17322
|
}
|
|
@@ -16783,6 +17362,7 @@ function createRuntime(cfg) {
|
|
|
16783
17362
|
}
|
|
16784
17363
|
}
|
|
16785
17364
|
setupCloudCheckpointing(cfg);
|
|
17365
|
+
setupTelemetry(cfg);
|
|
16786
17366
|
const initialAllowActions = (cfg.allowActions ?? true) && (sessionCoordinator ? sessionCoordinator.isController() : true);
|
|
16787
17367
|
bridge = new Bridge({
|
|
16788
17368
|
allowActions: initialAllowActions,
|
|
@@ -16790,7 +17370,6 @@ function createRuntime(cfg) {
|
|
|
16790
17370
|
allowedDomains: cfg.allowedDomains,
|
|
16791
17371
|
domainScopeMode: cfg.domainScopeMode,
|
|
16792
17372
|
externalNavigationPolicy: cfg.externalNavigationPolicy,
|
|
16793
|
-
crossDomainPolicy: cfg.crossDomainPolicy,
|
|
16794
17373
|
registerOpenedTab: (payload) => sessionCoordinator?.registerOpenedTab(payload),
|
|
16795
17374
|
switchToLogicalTab: (logicalTabId) => sessionCoordinator?.switchToLogicalTab(logicalTabId) || { ok: false, reason: "No session coordinator" },
|
|
16796
17375
|
listKnownTabs: () => (sessionCoordinator?.listTabs() || []).map((tab) => ({
|
|
@@ -16815,26 +17394,34 @@ function createRuntime(cfg) {
|
|
|
16815
17394
|
bindRpc(channel.port1, {
|
|
16816
17395
|
getSnapshot: () => bridge.getSnapshot(),
|
|
16817
17396
|
getPageData: async (params) => {
|
|
16818
|
-
const
|
|
17397
|
+
const runtimeCfg = currentConfig || cfg;
|
|
17398
|
+
const tabId = Number(params?.tabId);
|
|
16819
17399
|
const localTabId = sessionCoordinator?.getLocalLogicalTabId();
|
|
16820
|
-
if (!tabId || tabId === localTabId || !sessionCoordinator) {
|
|
17400
|
+
if (!Number.isFinite(tabId) || tabId <= 0 || tabId === localTabId || !sessionCoordinator) {
|
|
16821
17401
|
return bridge.getPageData(params);
|
|
16822
17402
|
}
|
|
16823
17403
|
const tabs = sessionCoordinator.listTabs();
|
|
16824
17404
|
const targetTab = tabs.find((t) => t.logicalTabId === tabId);
|
|
16825
|
-
if (!targetTab
|
|
17405
|
+
if (!targetTab) {
|
|
17406
|
+
return buildInaccessibleTabPageData(runtimeCfg, { logicalTabId: tabId, external: true }, "target_tab_missing");
|
|
17407
|
+
}
|
|
17408
|
+
if (targetTab.runtimeId === runtimeId) {
|
|
16826
17409
|
return bridge.getPageData(params);
|
|
16827
17410
|
}
|
|
17411
|
+
if (targetTab.external && runtimeCfg.externalNavigationPolicy !== "allow") {
|
|
17412
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "external_domain_inaccessible");
|
|
17413
|
+
}
|
|
16828
17414
|
if (!targetTab.runtimeId || !sessionCoordinator.isTabAlive(tabId)) {
|
|
16829
|
-
return
|
|
17415
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "target_tab_inactive");
|
|
16830
17416
|
}
|
|
16831
17417
|
try {
|
|
16832
17418
|
return await sessionCoordinator.sendCrossTabRpc(targetTab.runtimeId, "getPageData", params, 15e3);
|
|
16833
17419
|
} catch {
|
|
16834
|
-
return
|
|
17420
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "cross_tab_rpc_failed");
|
|
16835
17421
|
}
|
|
16836
17422
|
},
|
|
16837
17423
|
executeTool: async (params) => {
|
|
17424
|
+
const runtimeCfg = currentConfig || cfg;
|
|
16838
17425
|
const activeTabId = sessionCoordinator?.getActiveLogicalTabId();
|
|
16839
17426
|
const localTabId = sessionCoordinator?.getLocalLogicalTabId();
|
|
16840
17427
|
if (!activeTabId || activeTabId === localTabId || !sessionCoordinator) {
|
|
@@ -16842,16 +17429,22 @@ function createRuntime(cfg) {
|
|
|
16842
17429
|
}
|
|
16843
17430
|
const tabs = sessionCoordinator.listTabs();
|
|
16844
17431
|
const targetTab = tabs.find((t) => t.logicalTabId === activeTabId);
|
|
16845
|
-
if (!targetTab
|
|
16846
|
-
return
|
|
17432
|
+
if (!targetTab) {
|
|
17433
|
+
return buildTabAccessToolError(runtimeCfg, { logicalTabId: activeTabId, external: true }, "target_tab_missing");
|
|
16847
17434
|
}
|
|
16848
|
-
if (
|
|
17435
|
+
if (targetTab.external && runtimeCfg.externalNavigationPolicy !== "allow") {
|
|
17436
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "external_tab_action_blocked");
|
|
17437
|
+
}
|
|
17438
|
+
if (targetTab.runtimeId === runtimeId) {
|
|
16849
17439
|
return bridge.executeTool(params.call, params.payload);
|
|
16850
17440
|
}
|
|
17441
|
+
if (!targetTab.runtimeId || !sessionCoordinator.isTabAlive(activeTabId)) {
|
|
17442
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "target_tab_inactive");
|
|
17443
|
+
}
|
|
16851
17444
|
try {
|
|
16852
17445
|
return await sessionCoordinator.sendCrossTabRpc(targetTab.runtimeId, "executeTool", params, 2e4);
|
|
16853
17446
|
} catch {
|
|
16854
|
-
return
|
|
17447
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "cross_tab_execute_failed");
|
|
16855
17448
|
}
|
|
16856
17449
|
},
|
|
16857
17450
|
executeClientTool: (params) => bridge.executeClientTool(params.name, params.args),
|
|
@@ -16938,6 +17531,9 @@ function createRuntime(cfg) {
|
|
|
16938
17531
|
},
|
|
16939
17532
|
showTaskControls: cfg.ui?.showTaskControls !== false,
|
|
16940
17533
|
muted: cfg.ui?.muted,
|
|
17534
|
+
agent: {
|
|
17535
|
+
name: cfg.ui?.agent?.name
|
|
17536
|
+
},
|
|
16941
17537
|
mascot: {
|
|
16942
17538
|
disabled: cfg.ui?.mascot?.disabled,
|
|
16943
17539
|
mp4Url: cfg.ui?.mascot?.mp4Url,
|
|
@@ -17052,9 +17648,7 @@ function boot(cfg) {
|
|
|
17052
17648
|
plannerOnActError: cfg.taskRouting?.plannerOnActError
|
|
17053
17649
|
},
|
|
17054
17650
|
taskContext: {
|
|
17055
|
-
|
|
17056
|
-
suggestReset: cfg.taskContext?.suggestReset ?? true,
|
|
17057
|
-
semanticSimilarityThreshold: cfg.taskContext?.semanticSimilarityThreshold ?? DEFAULT_SEMANTIC_SIMILARITY_THRESHOLD
|
|
17651
|
+
...cfg.taskContext
|
|
17058
17652
|
}
|
|
17059
17653
|
};
|
|
17060
17654
|
runtimeState.sessionId = currentConfig.sessionId;
|
|
@@ -17115,17 +17709,29 @@ function update(cfg) {
|
|
|
17115
17709
|
...cfg.taskRouting
|
|
17116
17710
|
},
|
|
17117
17711
|
taskContext: {
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
semanticSimilarityThreshold: cfg.taskContext?.semanticSimilarityThreshold ?? currentConfig.taskContext?.semanticSimilarityThreshold ?? DEFAULT_SEMANTIC_SIMILARITY_THRESHOLD
|
|
17712
|
+
...currentConfig.taskContext,
|
|
17713
|
+
...cfg.taskContext
|
|
17121
17714
|
},
|
|
17122
17715
|
ui: {
|
|
17123
17716
|
...currentConfig.ui,
|
|
17124
17717
|
...cfg.ui,
|
|
17718
|
+
agent: {
|
|
17719
|
+
...currentConfig.ui?.agent,
|
|
17720
|
+
...cfg.ui?.agent
|
|
17721
|
+
},
|
|
17125
17722
|
panel: {
|
|
17126
17723
|
...currentConfig.ui?.panel,
|
|
17127
17724
|
...cfg.ui?.panel
|
|
17128
17725
|
}
|
|
17726
|
+
},
|
|
17727
|
+
tools: {
|
|
17728
|
+
...currentConfig.tools,
|
|
17729
|
+
...cfg.tools,
|
|
17730
|
+
client: cfg.tools?.client ?? currentConfig.tools?.client,
|
|
17731
|
+
web: {
|
|
17732
|
+
...currentConfig.tools?.web,
|
|
17733
|
+
...cfg.tools?.web
|
|
17734
|
+
}
|
|
17129
17735
|
}
|
|
17130
17736
|
};
|
|
17131
17737
|
resolvedVisitorId = resolveVisitorId(currentConfig);
|
|
@@ -17140,16 +17746,16 @@ function update(cfg) {
|
|
|
17140
17746
|
setupSessionCoordinator(currentConfig);
|
|
17141
17747
|
}
|
|
17142
17748
|
setupCloudCheckpointing(currentConfig);
|
|
17749
|
+
setupTelemetry(currentConfig);
|
|
17143
17750
|
if (bridge) {
|
|
17144
17751
|
if (typeof cfg.allowActions === "boolean") {
|
|
17145
17752
|
bridge.setAllowActions(cfg.allowActions && currentMode === "controller");
|
|
17146
17753
|
}
|
|
17147
|
-
if (cfg.allowedDomains || cfg.
|
|
17754
|
+
if (cfg.allowedDomains || cfg.domainScopeMode || cfg.externalNavigationPolicy) {
|
|
17148
17755
|
bridge.setNavigationPolicy({
|
|
17149
17756
|
allowedDomains: cfg.allowedDomains,
|
|
17150
17757
|
domainScopeMode: cfg.domainScopeMode,
|
|
17151
|
-
externalNavigationPolicy: cfg.externalNavigationPolicy
|
|
17152
|
-
crossDomainPolicy: cfg.crossDomainPolicy
|
|
17758
|
+
externalNavigationPolicy: cfg.externalNavigationPolicy
|
|
17153
17759
|
});
|
|
17154
17760
|
}
|
|
17155
17761
|
}
|
|
@@ -17164,6 +17770,8 @@ function update(cfg) {
|
|
|
17164
17770
|
function shutdown() {
|
|
17165
17771
|
hideTaskSuggestion();
|
|
17166
17772
|
persistRuntimeState();
|
|
17773
|
+
void flushTelemetry(true);
|
|
17774
|
+
stopTelemetry();
|
|
17167
17775
|
cloudCheckpointClient?.markDirty();
|
|
17168
17776
|
cloudCheckpointClient?.syncNow();
|
|
17169
17777
|
cloudCheckpointClient?.stop();
|
|
@@ -17181,6 +17789,10 @@ function shutdown() {
|
|
|
17181
17789
|
runtimeId = "";
|
|
17182
17790
|
resolvedVisitorId = void 0;
|
|
17183
17791
|
suppressCheckpointSync = false;
|
|
17792
|
+
telemetryInFlight = false;
|
|
17793
|
+
telemetryPausedAuth = false;
|
|
17794
|
+
telemetryBuffer = [];
|
|
17795
|
+
telemetrySeq = 0;
|
|
17184
17796
|
currentMode = "controller";
|
|
17185
17797
|
pendingTaskSuggestion = null;
|
|
17186
17798
|
runtimeStateStore = null;
|