@rtrvr-ai/rover 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/embed.js +16 -16
- package/dist/index.d.ts +11 -0
- package/dist/rover.js +371 -105
- package/dist/worker/rover-worker.js +836 -75
- package/package.json +1 -1
package/dist/rover.js
CHANGED
|
@@ -11465,9 +11465,9 @@ var Bridge = class {
|
|
|
11465
11465
|
}
|
|
11466
11466
|
async openUrlInNewTab(targetUrl, options) {
|
|
11467
11467
|
const external = !isUrlAllowedByDomains(targetUrl, this.allowedDomains);
|
|
11468
|
-
const
|
|
11468
|
+
const popupAttempt = this.openVerifiedPopup(targetUrl);
|
|
11469
11469
|
let logicalTabId;
|
|
11470
|
-
if (this.registerOpenedTab) {
|
|
11470
|
+
if (popupAttempt.opened && this.registerOpenedTab) {
|
|
11471
11471
|
try {
|
|
11472
11472
|
const registered = await this.registerOpenedTab({
|
|
11473
11473
|
url: targetUrl,
|
|
@@ -11479,8 +11479,8 @@ var Bridge = class {
|
|
|
11479
11479
|
} catch {
|
|
11480
11480
|
}
|
|
11481
11481
|
}
|
|
11482
|
-
const registrationFailed = !logicalTabId && !!this.registerOpenedTab;
|
|
11483
|
-
if (!
|
|
11482
|
+
const registrationFailed = popupAttempt.opened && !logicalTabId && !!this.registerOpenedTab;
|
|
11483
|
+
if (!popupAttempt.opened) {
|
|
11484
11484
|
const reason = "Browser popup settings blocked opening a new tab.";
|
|
11485
11485
|
if (options?.policyBlocked) {
|
|
11486
11486
|
this.emitNavigationGuardrail({
|
|
@@ -11516,6 +11516,13 @@ var Bridge = class {
|
|
|
11516
11516
|
openedInNewTab: true
|
|
11517
11517
|
});
|
|
11518
11518
|
}
|
|
11519
|
+
const warningMessages = [];
|
|
11520
|
+
if (registrationFailed) {
|
|
11521
|
+
warningMessages.push("Tab opened but registration failed; tab may not be targetable.");
|
|
11522
|
+
}
|
|
11523
|
+
if (!popupAttempt.verified) {
|
|
11524
|
+
warningMessages.push("Tab open was triggered, but browser did not return a popup handle.");
|
|
11525
|
+
}
|
|
11519
11526
|
return {
|
|
11520
11527
|
success: true,
|
|
11521
11528
|
output: {
|
|
@@ -11523,13 +11530,64 @@ var Bridge = class {
|
|
|
11523
11530
|
external,
|
|
11524
11531
|
logicalTabId,
|
|
11525
11532
|
openedInNewTab: true,
|
|
11533
|
+
openVerification: popupAttempt.verified ? "verified" : "unverified",
|
|
11526
11534
|
policyBlocked: !!options?.policyBlocked,
|
|
11527
11535
|
message,
|
|
11528
|
-
...
|
|
11536
|
+
...warningMessages.length ? { warning: warningMessages.join(" ") } : {}
|
|
11529
11537
|
},
|
|
11530
11538
|
allowFallback: true
|
|
11531
11539
|
};
|
|
11532
11540
|
}
|
|
11541
|
+
openVerifiedPopup(targetUrl) {
|
|
11542
|
+
const popup = window.open("about:blank", "_blank");
|
|
11543
|
+
if (popup) {
|
|
11544
|
+
try {
|
|
11545
|
+
popup.opener = null;
|
|
11546
|
+
} catch {
|
|
11547
|
+
}
|
|
11548
|
+
try {
|
|
11549
|
+
popup.location.href = targetUrl;
|
|
11550
|
+
} catch {
|
|
11551
|
+
}
|
|
11552
|
+
return { opened: true, verified: true };
|
|
11553
|
+
}
|
|
11554
|
+
try {
|
|
11555
|
+
const directPopup = window.open(targetUrl, "_blank");
|
|
11556
|
+
if (directPopup) {
|
|
11557
|
+
try {
|
|
11558
|
+
directPopup.opener = null;
|
|
11559
|
+
} catch {
|
|
11560
|
+
}
|
|
11561
|
+
return { opened: true, verified: true };
|
|
11562
|
+
}
|
|
11563
|
+
} catch {
|
|
11564
|
+
}
|
|
11565
|
+
try {
|
|
11566
|
+
const noOpenerPopup = window.open(targetUrl, "_blank", "noopener,noreferrer");
|
|
11567
|
+
if (noOpenerPopup) {
|
|
11568
|
+
try {
|
|
11569
|
+
noOpenerPopup.opener = null;
|
|
11570
|
+
} catch {
|
|
11571
|
+
}
|
|
11572
|
+
return { opened: true, verified: true };
|
|
11573
|
+
}
|
|
11574
|
+
return { opened: true, verified: false };
|
|
11575
|
+
} catch {
|
|
11576
|
+
}
|
|
11577
|
+
try {
|
|
11578
|
+
const anchor = document.createElement("a");
|
|
11579
|
+
anchor.href = targetUrl;
|
|
11580
|
+
anchor.target = "_blank";
|
|
11581
|
+
anchor.rel = "noopener noreferrer";
|
|
11582
|
+
anchor.style.display = "none";
|
|
11583
|
+
document.body.appendChild(anchor);
|
|
11584
|
+
anchor.click();
|
|
11585
|
+
anchor.remove();
|
|
11586
|
+
return { opened: true, verified: false };
|
|
11587
|
+
} catch {
|
|
11588
|
+
return { opened: false, verified: false };
|
|
11589
|
+
}
|
|
11590
|
+
}
|
|
11533
11591
|
async openElementInNewTab(args) {
|
|
11534
11592
|
const elementId = args.element_id ?? args.source_element_id ?? args.target_element_id ?? args.center_element_id ?? null;
|
|
11535
11593
|
if (!elementId) {
|
|
@@ -11950,6 +12008,7 @@ function bindRpc(port, handlers) {
|
|
|
11950
12008
|
}
|
|
11951
12009
|
|
|
11952
12010
|
// ../ui/dist/mount.js
|
|
12011
|
+
var DEFAULT_AGENT_NAME = "Rover";
|
|
11953
12012
|
var DEFAULT_MASCOT_MP4 = "https://www.rtrvr.ai/rover/mascot.mp4";
|
|
11954
12013
|
var DEFAULT_MASCOT_WEBM = "https://www.rtrvr.ai/rover/mascot.webm";
|
|
11955
12014
|
var EXPAND_THRESHOLD_OUTPUT = 280;
|
|
@@ -11999,6 +12058,22 @@ function deriveTraceKey(event) {
|
|
|
11999
12058
|
function sanitizeText(text) {
|
|
12000
12059
|
return String(text || "").trim();
|
|
12001
12060
|
}
|
|
12061
|
+
function resolveAgentName(input) {
|
|
12062
|
+
const normalized = String(input || "").trim();
|
|
12063
|
+
if (!normalized)
|
|
12064
|
+
return DEFAULT_AGENT_NAME;
|
|
12065
|
+
return normalized.slice(0, 64);
|
|
12066
|
+
}
|
|
12067
|
+
function deriveAgentInitial(name) {
|
|
12068
|
+
const normalized = String(name || "").trim();
|
|
12069
|
+
if (!normalized)
|
|
12070
|
+
return "R";
|
|
12071
|
+
return normalized[0].toUpperCase();
|
|
12072
|
+
}
|
|
12073
|
+
function deriveLauncherToken(name) {
|
|
12074
|
+
const compact = String(name || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 3).toUpperCase();
|
|
12075
|
+
return compact || "RVR";
|
|
12076
|
+
}
|
|
12002
12077
|
function parseStageFromTitle(title) {
|
|
12003
12078
|
const clean = sanitizeText(title);
|
|
12004
12079
|
const match = /^(Analyze|Route|Execute|Verify|Complete):\s*(.*)$/i.exec(clean);
|
|
@@ -12174,6 +12249,9 @@ function createExpandableRichContent(text, threshold) {
|
|
|
12174
12249
|
return wrapper;
|
|
12175
12250
|
}
|
|
12176
12251
|
function mountWidget(opts) {
|
|
12252
|
+
const agentName = resolveAgentName(opts.agent?.name);
|
|
12253
|
+
const agentInitial = deriveAgentInitial(agentName);
|
|
12254
|
+
const launcherToken = deriveLauncherToken(agentName);
|
|
12177
12255
|
const host = document.createElement("div");
|
|
12178
12256
|
host.id = "rover-widget-root";
|
|
12179
12257
|
document.documentElement.appendChild(host);
|
|
@@ -13220,7 +13298,7 @@ function mountWidget(opts) {
|
|
|
13220
13298
|
wrapper.dataset.mood = "idle";
|
|
13221
13299
|
const launcher = document.createElement("button");
|
|
13222
13300
|
launcher.className = "launcher";
|
|
13223
|
-
launcher.setAttribute("aria-label",
|
|
13301
|
+
launcher.setAttribute("aria-label", `Open ${agentName} assistant`);
|
|
13224
13302
|
const mascotDisabled = opts.mascot?.disabled === true;
|
|
13225
13303
|
let launcherVideo = null;
|
|
13226
13304
|
if (!mascotDisabled) {
|
|
@@ -13242,7 +13320,7 @@ function mountWidget(opts) {
|
|
|
13242
13320
|
}
|
|
13243
13321
|
const launcherFallback = document.createElement("span");
|
|
13244
13322
|
launcherFallback.className = "launcherFallback";
|
|
13245
|
-
launcherFallback.textContent =
|
|
13323
|
+
launcherFallback.textContent = launcherToken;
|
|
13246
13324
|
const launcherShine = document.createElement("div");
|
|
13247
13325
|
launcherShine.className = "launcherShine";
|
|
13248
13326
|
launcher.appendChild(launcherFallback);
|
|
@@ -13260,13 +13338,13 @@ function mountWidget(opts) {
|
|
|
13260
13338
|
}
|
|
13261
13339
|
const avatarFallback = document.createElement("span");
|
|
13262
13340
|
avatarFallback.className = "avatarFallback";
|
|
13263
|
-
avatarFallback.textContent =
|
|
13341
|
+
avatarFallback.textContent = agentInitial;
|
|
13264
13342
|
avatar.appendChild(avatarFallback);
|
|
13265
13343
|
const meta = document.createElement("div");
|
|
13266
13344
|
meta.className = "meta";
|
|
13267
13345
|
const titleEl = document.createElement("div");
|
|
13268
13346
|
titleEl.className = "title";
|
|
13269
|
-
titleEl.textContent =
|
|
13347
|
+
titleEl.textContent = agentName;
|
|
13270
13348
|
const statusEl = document.createElement("div");
|
|
13271
13349
|
statusEl.className = "status";
|
|
13272
13350
|
const statusDot = document.createElement("span");
|
|
@@ -13399,7 +13477,7 @@ function mountWidget(opts) {
|
|
|
13399
13477
|
composer.className = "composer";
|
|
13400
13478
|
const composerTextarea = document.createElement("textarea");
|
|
13401
13479
|
composerTextarea.rows = 1;
|
|
13402
|
-
composerTextarea.placeholder =
|
|
13480
|
+
composerTextarea.placeholder = `Ask ${agentName} to act on this website...`;
|
|
13403
13481
|
composer.appendChild(composerTextarea);
|
|
13404
13482
|
const sendButton = document.createElement("button");
|
|
13405
13483
|
sendButton.type = "submit";
|
|
@@ -13707,7 +13785,7 @@ function mountWidget(opts) {
|
|
|
13707
13785
|
if (mode === "controller") {
|
|
13708
13786
|
modeBadge.textContent = "active";
|
|
13709
13787
|
menuTakeControl.style.display = "none";
|
|
13710
|
-
inputEl.placeholder =
|
|
13788
|
+
inputEl.placeholder = `Ask ${agentName} to act on this website...`;
|
|
13711
13789
|
} else {
|
|
13712
13790
|
modeBadge.textContent = "observer";
|
|
13713
13791
|
if (executionMeta?.canTakeControl !== false) {
|
|
@@ -13720,7 +13798,7 @@ function mountWidget(opts) {
|
|
|
13720
13798
|
} else if (canComposeInObserver) {
|
|
13721
13799
|
inputEl.placeholder = "Send to take control and run here.";
|
|
13722
13800
|
} else if (executionMeta?.activeLogicalTabId && executionMeta?.localLogicalTabId && executionMeta.activeLogicalTabId !== executionMeta.localLogicalTabId) {
|
|
13723
|
-
inputEl.placeholder = `Observing:
|
|
13801
|
+
inputEl.placeholder = `Observing: ${agentName} is acting in tab #${executionMeta.activeLogicalTabId}`;
|
|
13724
13802
|
} else {
|
|
13725
13803
|
inputEl.placeholder = "Observer mode. Take control to run actions here.";
|
|
13726
13804
|
}
|
|
@@ -13920,6 +13998,9 @@ function mountWidget(opts) {
|
|
|
13920
13998
|
var SHARED_VERSION = 2;
|
|
13921
13999
|
var SHARED_KEY_PREFIX = "rover:shared:";
|
|
13922
14000
|
var SHARED_CHANNEL_PREFIX = "rover:channel:";
|
|
14001
|
+
var STALE_DETACHED_EXTERNAL_TAB_MS = 2 * 6e4;
|
|
14002
|
+
var STALE_DETACHED_TAB_MS = 10 * 6e4;
|
|
14003
|
+
var STALE_RUNTIME_TAB_MS = 45e3;
|
|
13923
14004
|
function now() {
|
|
13924
14005
|
return Date.now();
|
|
13925
14006
|
}
|
|
@@ -14125,6 +14206,36 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14125
14206
|
lastNotifiedRole;
|
|
14126
14207
|
roleChangeTimer = null;
|
|
14127
14208
|
static ROLE_CHANGE_DEBOUNCE_MS = 200;
|
|
14209
|
+
pruneDetachedTabs(draft, options) {
|
|
14210
|
+
const dropRuntimeDetached = !!options?.dropRuntimeDetached;
|
|
14211
|
+
const dropAllDetachedExternal = !!options?.dropAllDetachedExternal;
|
|
14212
|
+
const nowMs2 = now();
|
|
14213
|
+
const before = draft.tabs.length;
|
|
14214
|
+
draft.tabs = draft.tabs.filter((tab) => {
|
|
14215
|
+
if (tab.runtimeId) {
|
|
14216
|
+
if (tab.runtimeId === this.runtimeId)
|
|
14217
|
+
return true;
|
|
14218
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
|
|
14219
|
+
}
|
|
14220
|
+
if (dropRuntimeDetached && tab.openerRuntimeId === this.runtimeId) {
|
|
14221
|
+
return false;
|
|
14222
|
+
}
|
|
14223
|
+
if (tab.external) {
|
|
14224
|
+
if (dropAllDetachedExternal)
|
|
14225
|
+
return false;
|
|
14226
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
|
|
14227
|
+
}
|
|
14228
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
|
|
14229
|
+
});
|
|
14230
|
+
if (before !== draft.tabs.length) {
|
|
14231
|
+
if (draft.activeLogicalTabId && !draft.tabs.some((tab) => tab.logicalTabId === draft.activeLogicalTabId)) {
|
|
14232
|
+
draft.activeLogicalTabId = this.localLogicalTabId || draft.tabs[0]?.logicalTabId;
|
|
14233
|
+
}
|
|
14234
|
+
if (draft.nextLogicalTabId <= (draft.tabs.at(-1)?.logicalTabId ?? 0)) {
|
|
14235
|
+
draft.nextLogicalTabId = draft.tabs.reduce((max, tab) => Math.max(max, tab.logicalTabId), 0) + 1;
|
|
14236
|
+
}
|
|
14237
|
+
}
|
|
14238
|
+
}
|
|
14128
14239
|
constructor(options) {
|
|
14129
14240
|
this.siteId = options.siteId;
|
|
14130
14241
|
this.sessionId = options.sessionId;
|
|
@@ -14144,6 +14255,9 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14144
14255
|
if (this.started)
|
|
14145
14256
|
return;
|
|
14146
14257
|
this.started = true;
|
|
14258
|
+
this.mutate("local", (draft) => {
|
|
14259
|
+
this.pruneDetachedTabs(draft, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14260
|
+
});
|
|
14147
14261
|
this.registerCurrentTab(window.location.href, document.title || void 0);
|
|
14148
14262
|
this.claimLease(false);
|
|
14149
14263
|
if (!this.state.task) {
|
|
@@ -14278,10 +14392,27 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14278
14392
|
}
|
|
14279
14393
|
hydrateExternalState(raw) {
|
|
14280
14394
|
const incoming = sanitizeSharedState(raw, this.siteId, this.sessionId);
|
|
14395
|
+
this.pruneDetachedTabs(incoming, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14281
14396
|
const beforeSeq = this.state.seq;
|
|
14282
14397
|
const beforeUpdatedAt = this.state.updatedAt;
|
|
14283
14398
|
this.applyIncomingState(incoming);
|
|
14284
|
-
|
|
14399
|
+
let changed = this.state.seq !== beforeSeq || this.state.updatedAt !== beforeUpdatedAt;
|
|
14400
|
+
if (!changed) {
|
|
14401
|
+
const hasDetachedCandidates = this.state.tabs.some((tab) => {
|
|
14402
|
+
if (tab.runtimeId)
|
|
14403
|
+
return false;
|
|
14404
|
+
if (tab.external)
|
|
14405
|
+
return true;
|
|
14406
|
+
return tab.openerRuntimeId === this.runtimeId;
|
|
14407
|
+
});
|
|
14408
|
+
if (hasDetachedCandidates) {
|
|
14409
|
+
const beforeTabCount = this.state.tabs.length;
|
|
14410
|
+
this.mutate("local", (draft) => {
|
|
14411
|
+
this.pruneDetachedTabs(draft, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
|
|
14412
|
+
});
|
|
14413
|
+
changed = this.state.tabs.length !== beforeTabCount;
|
|
14414
|
+
}
|
|
14415
|
+
}
|
|
14285
14416
|
if (!changed)
|
|
14286
14417
|
return false;
|
|
14287
14418
|
this.persistState();
|
|
@@ -14291,7 +14422,23 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14291
14422
|
return true;
|
|
14292
14423
|
}
|
|
14293
14424
|
listTabs() {
|
|
14294
|
-
|
|
14425
|
+
const nowMs2 = now();
|
|
14426
|
+
return this.state.tabs.filter((tab) => {
|
|
14427
|
+
if (tab.runtimeId) {
|
|
14428
|
+
if (tab.runtimeId === this.runtimeId)
|
|
14429
|
+
return true;
|
|
14430
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
|
|
14431
|
+
}
|
|
14432
|
+
if (tab.external) {
|
|
14433
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
|
|
14434
|
+
}
|
|
14435
|
+
return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
|
|
14436
|
+
});
|
|
14437
|
+
}
|
|
14438
|
+
pruneTabs(options) {
|
|
14439
|
+
this.mutate("local", (draft) => {
|
|
14440
|
+
this.pruneDetachedTabs(draft, options);
|
|
14441
|
+
});
|
|
14295
14442
|
}
|
|
14296
14443
|
startNewTask(task) {
|
|
14297
14444
|
const startedAt = Number(task.startedAt) || now();
|
|
@@ -14312,6 +14459,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14312
14459
|
draft.uiStatus = void 0;
|
|
14313
14460
|
draft.activeRun = void 0;
|
|
14314
14461
|
draft.workerContext = void 0;
|
|
14462
|
+
this.pruneDetachedTabs(draft, { dropAllDetachedExternal: true });
|
|
14315
14463
|
});
|
|
14316
14464
|
return nextTask;
|
|
14317
14465
|
}
|
|
@@ -14426,9 +14574,6 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14426
14574
|
this.mutate("local", (draft) => {
|
|
14427
14575
|
if (!activeRun) {
|
|
14428
14576
|
draft.activeRun = void 0;
|
|
14429
|
-
if (draft.task && draft.task.status === "running") {
|
|
14430
|
-
draft.task.status = "completed";
|
|
14431
|
-
}
|
|
14432
14577
|
return;
|
|
14433
14578
|
}
|
|
14434
14579
|
draft.activeRun = {
|
|
@@ -14781,6 +14926,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14781
14926
|
if (draft.workflowLock?.runtimeId === this.runtimeId) {
|
|
14782
14927
|
draft.workflowLock.expiresAt = now() + this.leaseMs * 5;
|
|
14783
14928
|
}
|
|
14929
|
+
this.pruneDetachedTabs(draft);
|
|
14784
14930
|
});
|
|
14785
14931
|
this.notifyRoleChange();
|
|
14786
14932
|
}
|
|
@@ -14872,6 +15018,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
14872
15018
|
return;
|
|
14873
15019
|
if (incoming.seq === this.state.seq && incoming.updatedAt <= this.state.updatedAt)
|
|
14874
15020
|
return;
|
|
15021
|
+
this.pruneDetachedTabs(incoming);
|
|
14875
15022
|
this.state = incoming;
|
|
14876
15023
|
this.syncLocalLogicalTabId();
|
|
14877
15024
|
this.onStateChange?.(this.state, "remote");
|
|
@@ -15238,9 +15385,8 @@ var MAX_AUTO_RESUME_AGE_MS = 15 * 6e4;
|
|
|
15238
15385
|
var MAX_AUTO_RESUME_ATTEMPTS = 12;
|
|
15239
15386
|
var VISITOR_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
15240
15387
|
var CHECKPOINT_PAYLOAD_VERSION = 1;
|
|
15241
|
-
var
|
|
15242
|
-
var
|
|
15243
|
-
var MAX_RECENT_USER_MESSAGES_FOR_TASK_HEURISTIC = 3;
|
|
15388
|
+
var ACTIVE_PENDING_RUN_GRACE_MS = 3e3;
|
|
15389
|
+
var STALE_PENDING_RUN_MS = 9e4;
|
|
15244
15390
|
var instance = null;
|
|
15245
15391
|
var bridge = null;
|
|
15246
15392
|
var worker = null;
|
|
@@ -15482,6 +15628,72 @@ function safeSerialize(value) {
|
|
|
15482
15628
|
}
|
|
15483
15629
|
}
|
|
15484
15630
|
}
|
|
15631
|
+
function resolveEmbeddedDomainLabel(allowedDomains) {
|
|
15632
|
+
const raw = String((allowedDomains || [])[0] || "").trim();
|
|
15633
|
+
if (!raw)
|
|
15634
|
+
return "the configured domain";
|
|
15635
|
+
return raw.replace(/^\*?\./, "").replace(/^=/, "");
|
|
15636
|
+
}
|
|
15637
|
+
function buildInaccessibleTabPageData(cfg, tab, reason = "tab_not_accessible") {
|
|
15638
|
+
const logicalTabId = Number(tab?.logicalTabId) || void 0;
|
|
15639
|
+
const url = tab?.url || "";
|
|
15640
|
+
const title = tab?.title || (tab?.external ? "External Tab (Inaccessible)" : "Inactive Tab");
|
|
15641
|
+
const embeddedDomain = resolveEmbeddedDomainLabel(cfg.allowedDomains);
|
|
15642
|
+
const normalizedReason = String(reason || "").trim();
|
|
15643
|
+
const reasonLine = normalizedReason ? ` Reason: ${normalizedReason}.` : "";
|
|
15644
|
+
const content = tab?.external ? `Rover is embedded inside ${embeddedDomain}. This tab is outside direct DOM access and only virtual tab context is available.${reasonLine}` : `This tab is currently not attached to an active Rover runtime. Switch to a live tab or reopen it.${reasonLine}`;
|
|
15645
|
+
return {
|
|
15646
|
+
url,
|
|
15647
|
+
title,
|
|
15648
|
+
contentType: "text/html",
|
|
15649
|
+
content,
|
|
15650
|
+
metadata: {
|
|
15651
|
+
inaccessible: true,
|
|
15652
|
+
external: !!tab?.external,
|
|
15653
|
+
accessMode: tab?.external ? "external_placeholder" : "inactive_tab",
|
|
15654
|
+
reason,
|
|
15655
|
+
logicalTabId
|
|
15656
|
+
}
|
|
15657
|
+
};
|
|
15658
|
+
}
|
|
15659
|
+
function buildTabAccessToolError(cfg, tab, reason = "tab_not_accessible") {
|
|
15660
|
+
const logicalTabId = Number(tab?.logicalTabId) || 0;
|
|
15661
|
+
const blockedUrl = tab?.url || "";
|
|
15662
|
+
const embeddedDomain = resolveEmbeddedDomainLabel(cfg.allowedDomains);
|
|
15663
|
+
const message = tab?.external ? `Tab ${logicalTabId} is outside Rover's embedded domain scope (${embeddedDomain}) and cannot be controlled directly.` : `Tab ${logicalTabId} is not attached to an active Rover runtime.`;
|
|
15664
|
+
const code = tab?.external ? "DOMAIN_SCOPE_BLOCKED" : "TAB_NOT_ACCESSIBLE";
|
|
15665
|
+
return {
|
|
15666
|
+
success: false,
|
|
15667
|
+
error: message,
|
|
15668
|
+
allowFallback: true,
|
|
15669
|
+
output: {
|
|
15670
|
+
success: false,
|
|
15671
|
+
error: {
|
|
15672
|
+
code,
|
|
15673
|
+
message,
|
|
15674
|
+
missing: [],
|
|
15675
|
+
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.",
|
|
15676
|
+
retryable: false
|
|
15677
|
+
},
|
|
15678
|
+
blocked_url: blockedUrl || void 0,
|
|
15679
|
+
logical_tab_id: logicalTabId || void 0,
|
|
15680
|
+
external: !!tab?.external,
|
|
15681
|
+
policy_action: tab?.external ? cfg.externalNavigationPolicy || "open_new_tab_notice" : void 0,
|
|
15682
|
+
reason
|
|
15683
|
+
},
|
|
15684
|
+
errorDetails: {
|
|
15685
|
+
code,
|
|
15686
|
+
message,
|
|
15687
|
+
retryable: false,
|
|
15688
|
+
details: {
|
|
15689
|
+
logicalTabId,
|
|
15690
|
+
blockedUrl,
|
|
15691
|
+
external: !!tab?.external,
|
|
15692
|
+
reason
|
|
15693
|
+
}
|
|
15694
|
+
}
|
|
15695
|
+
};
|
|
15696
|
+
}
|
|
15485
15697
|
function normalizeStatusStage(input) {
|
|
15486
15698
|
if (input === "analyze" || input === "route" || input === "execute" || input === "verify" || input === "complete") {
|
|
15487
15699
|
return input;
|
|
@@ -15557,14 +15769,26 @@ function shouldIgnoreRunScopedWorkerMessage(msg) {
|
|
|
15557
15769
|
return false;
|
|
15558
15770
|
}
|
|
15559
15771
|
if (type === "run_completed") {
|
|
15560
|
-
if (!pendingRunId)
|
|
15772
|
+
if (!pendingRunId) {
|
|
15773
|
+
const sharedRunId = sessionCoordinator?.getState()?.activeRun?.runId;
|
|
15774
|
+
if (sharedRunId && sharedRunId === messageRunId)
|
|
15775
|
+
return false;
|
|
15561
15776
|
return true;
|
|
15777
|
+
}
|
|
15562
15778
|
return pendingRunId !== messageRunId;
|
|
15563
15779
|
}
|
|
15564
15780
|
if (!pendingRunId)
|
|
15565
15781
|
return true;
|
|
15566
15782
|
return pendingRunId !== messageRunId;
|
|
15567
15783
|
}
|
|
15784
|
+
function normalizeRunCompletionState(msg) {
|
|
15785
|
+
if (!msg || typeof msg !== "object") {
|
|
15786
|
+
return { taskComplete: false, needsUserInput: false };
|
|
15787
|
+
}
|
|
15788
|
+
const needsUserInput = msg.needsUserInput === true;
|
|
15789
|
+
const taskComplete = msg.taskComplete === true && !needsUserInput;
|
|
15790
|
+
return { taskComplete, needsUserInput };
|
|
15791
|
+
}
|
|
15568
15792
|
function getLatestAssistantText(runId) {
|
|
15569
15793
|
if (runId && latestAssistantByRunId.has(runId)) {
|
|
15570
15794
|
return latestAssistantByRunId.get(runId);
|
|
@@ -15828,6 +16052,33 @@ function markTaskActivity(role, timestamp = Date.now()) {
|
|
|
15828
16052
|
task.endedAt = void 0;
|
|
15829
16053
|
}
|
|
15830
16054
|
}
|
|
16055
|
+
function markTaskRunning(reason = "worker_task_active", timestamp = Date.now()) {
|
|
16056
|
+
if (!runtimeState)
|
|
16057
|
+
return;
|
|
16058
|
+
const task = ensureActiveTask(reason);
|
|
16059
|
+
if (!task)
|
|
16060
|
+
return;
|
|
16061
|
+
task.status = "running";
|
|
16062
|
+
task.endedAt = void 0;
|
|
16063
|
+
task.boundaryReason = reason;
|
|
16064
|
+
if (!task.lastUserAt && !task.lastAssistantAt) {
|
|
16065
|
+
task.lastAssistantAt = timestamp;
|
|
16066
|
+
}
|
|
16067
|
+
sessionCoordinator?.syncTask({ ...task }, runtimeState.taskEpoch);
|
|
16068
|
+
persistRuntimeState();
|
|
16069
|
+
}
|
|
16070
|
+
function markTaskCompleted(reason = "worker_task_complete", timestamp = Date.now()) {
|
|
16071
|
+
if (!runtimeState)
|
|
16072
|
+
return;
|
|
16073
|
+
const task = ensureActiveTask(reason);
|
|
16074
|
+
if (!task)
|
|
16075
|
+
return;
|
|
16076
|
+
task.status = "completed";
|
|
16077
|
+
task.endedAt = timestamp;
|
|
16078
|
+
task.boundaryReason = reason;
|
|
16079
|
+
sessionCoordinator?.syncTask({ ...task }, runtimeState.taskEpoch);
|
|
16080
|
+
persistRuntimeState();
|
|
16081
|
+
}
|
|
15831
16082
|
function hideTaskSuggestion() {
|
|
15832
16083
|
pendingTaskSuggestion = null;
|
|
15833
16084
|
ui?.setTaskSuggestion({ visible: false });
|
|
@@ -15837,53 +16088,37 @@ function clearTaskUiState() {
|
|
|
15837
16088
|
ui?.clearTimeline();
|
|
15838
16089
|
hideTaskSuggestion();
|
|
15839
16090
|
}
|
|
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;
|
|
16091
|
+
function isPendingRunLikelyActive() {
|
|
16092
|
+
const pending = runtimeState?.pendingRun;
|
|
16093
|
+
if (!pending)
|
|
16094
|
+
return false;
|
|
16095
|
+
if (!sessionCoordinator)
|
|
16096
|
+
return true;
|
|
16097
|
+
const sharedActiveRun = sessionCoordinator?.getState()?.activeRun;
|
|
16098
|
+
if (sharedActiveRun?.runId && sharedActiveRun.runId === pending.id) {
|
|
16099
|
+
return true;
|
|
16100
|
+
}
|
|
16101
|
+
if (sharedActiveRun?.runtimeId && sharedActiveRun.runtimeId !== runtimeId) {
|
|
16102
|
+
return true;
|
|
15858
16103
|
}
|
|
15859
|
-
|
|
16104
|
+
const ageMs = Date.now() - Number(pending.startedAt || 0);
|
|
16105
|
+
return ageMs >= 0 && ageMs <= ACTIVE_PENDING_RUN_GRACE_MS;
|
|
15860
16106
|
}
|
|
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 };
|
|
16107
|
+
function maybeClearStalePendingRun() {
|
|
16108
|
+
if (!runtimeState?.pendingRun)
|
|
16109
|
+
return;
|
|
16110
|
+
if (isPendingRunLikelyActive())
|
|
16111
|
+
return;
|
|
16112
|
+
if (!sessionCoordinator)
|
|
16113
|
+
return;
|
|
16114
|
+
const pending = runtimeState.pendingRun;
|
|
16115
|
+
const ageMs = Date.now() - Number(pending.startedAt || 0);
|
|
16116
|
+
if (!Number.isFinite(ageMs) || ageMs < STALE_PENDING_RUN_MS)
|
|
16117
|
+
return;
|
|
16118
|
+
setPendingRun(void 0);
|
|
16119
|
+
sessionCoordinator?.clearActiveRunRuntimeId(pending.id);
|
|
16120
|
+
sessionCoordinator?.releaseWorkflowLock(pending.id);
|
|
16121
|
+
sessionCoordinator?.setActiveRun(void 0);
|
|
15887
16122
|
}
|
|
15888
16123
|
function appendUiMessage(role, text, persist = true, options) {
|
|
15889
16124
|
const clean = truncateText2(String(text || ""));
|
|
@@ -16068,28 +16303,16 @@ function dispatchUserPrompt(text, options) {
|
|
|
16068
16303
|
const trimmed = String(text || "").trim();
|
|
16069
16304
|
if (!trimmed)
|
|
16070
16305
|
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
|
-
}
|
|
16306
|
+
maybeClearStalePendingRun();
|
|
16307
|
+
const activeTaskStatus = runtimeState?.activeTask?.status;
|
|
16308
|
+
const shouldStartFreshTask = !!options?.startNewTask || activeTaskStatus === "completed" || activeTaskStatus === "ended";
|
|
16309
|
+
sessionCoordinator?.pruneTabs({
|
|
16310
|
+
dropRuntimeDetached: true,
|
|
16311
|
+
dropAllDetachedExternal: shouldStartFreshTask
|
|
16312
|
+
});
|
|
16313
|
+
if (shouldStartFreshTask) {
|
|
16314
|
+
const autoReason = options?.reason || (activeTaskStatus === "completed" ? "auto_after_task_complete" : "auto_after_task_end");
|
|
16315
|
+
newTask({ reason: autoReason, clearUi: true });
|
|
16093
16316
|
}
|
|
16094
16317
|
hideTaskSuggestion();
|
|
16095
16318
|
postRun(trimmed, { appendUserMessage: true, resume: false, autoResume: true });
|
|
@@ -16727,6 +16950,7 @@ function handleWorkerMessage(msg) {
|
|
|
16727
16950
|
}
|
|
16728
16951
|
sessionCoordinator?.setActiveRun(void 0);
|
|
16729
16952
|
if (!msg.ok && msg.error) {
|
|
16953
|
+
markTaskRunning("worker_run_failed");
|
|
16730
16954
|
setUiStatus(`Task failed: ${String(msg.error)}`);
|
|
16731
16955
|
latestAssistantByRunId.delete(String(msg.runId || ""));
|
|
16732
16956
|
appendTimelineEvent({
|
|
@@ -16736,8 +16960,23 @@ function handleWorkerMessage(msg) {
|
|
|
16736
16960
|
status: "error"
|
|
16737
16961
|
});
|
|
16738
16962
|
} else if (msg.ok) {
|
|
16739
|
-
|
|
16740
|
-
|
|
16963
|
+
const completionState = normalizeRunCompletionState(msg);
|
|
16964
|
+
const taskComplete = completionState.taskComplete;
|
|
16965
|
+
const needsUserInput = completionState.needsUserInput;
|
|
16966
|
+
if (taskComplete) {
|
|
16967
|
+
markTaskCompleted("worker_task_complete");
|
|
16968
|
+
setUiStatus("Task completed");
|
|
16969
|
+
finalizeSuccessfulRunTimeline(typeof msg.runId === "string" ? msg.runId : void 0);
|
|
16970
|
+
} else {
|
|
16971
|
+
markTaskRunning(needsUserInput ? "worker_waiting_for_input" : "worker_continuation");
|
|
16972
|
+
setUiStatus(needsUserInput ? "Need more input to continue" : "Execution finished. Continue when ready.");
|
|
16973
|
+
appendTimelineEvent({
|
|
16974
|
+
kind: "status",
|
|
16975
|
+
title: needsUserInput ? "Waiting for your input" : "Continuation available",
|
|
16976
|
+
detail: needsUserInput ? "Planner requested more information before marking the task complete." : "Task is still active and will continue with your next message.",
|
|
16977
|
+
status: "info"
|
|
16978
|
+
});
|
|
16979
|
+
}
|
|
16741
16980
|
if (typeof msg.runId === "string" && msg.runId) {
|
|
16742
16981
|
latestAssistantByRunId.delete(msg.runId);
|
|
16743
16982
|
}
|
|
@@ -16815,26 +17054,34 @@ function createRuntime(cfg) {
|
|
|
16815
17054
|
bindRpc(channel.port1, {
|
|
16816
17055
|
getSnapshot: () => bridge.getSnapshot(),
|
|
16817
17056
|
getPageData: async (params) => {
|
|
16818
|
-
const
|
|
17057
|
+
const runtimeCfg = currentConfig || cfg;
|
|
17058
|
+
const tabId = Number(params?.tabId);
|
|
16819
17059
|
const localTabId = sessionCoordinator?.getLocalLogicalTabId();
|
|
16820
|
-
if (!tabId || tabId === localTabId || !sessionCoordinator) {
|
|
17060
|
+
if (!Number.isFinite(tabId) || tabId <= 0 || tabId === localTabId || !sessionCoordinator) {
|
|
16821
17061
|
return bridge.getPageData(params);
|
|
16822
17062
|
}
|
|
16823
17063
|
const tabs = sessionCoordinator.listTabs();
|
|
16824
17064
|
const targetTab = tabs.find((t) => t.logicalTabId === tabId);
|
|
16825
|
-
if (!targetTab
|
|
17065
|
+
if (!targetTab) {
|
|
17066
|
+
return buildInaccessibleTabPageData(runtimeCfg, { logicalTabId: tabId, external: true }, "target_tab_missing");
|
|
17067
|
+
}
|
|
17068
|
+
if (targetTab.runtimeId === runtimeId) {
|
|
16826
17069
|
return bridge.getPageData(params);
|
|
16827
17070
|
}
|
|
17071
|
+
if (targetTab.external && runtimeCfg.externalNavigationPolicy !== "allow") {
|
|
17072
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "external_domain_inaccessible");
|
|
17073
|
+
}
|
|
16828
17074
|
if (!targetTab.runtimeId || !sessionCoordinator.isTabAlive(tabId)) {
|
|
16829
|
-
return
|
|
17075
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "target_tab_inactive");
|
|
16830
17076
|
}
|
|
16831
17077
|
try {
|
|
16832
17078
|
return await sessionCoordinator.sendCrossTabRpc(targetTab.runtimeId, "getPageData", params, 15e3);
|
|
16833
17079
|
} catch {
|
|
16834
|
-
return
|
|
17080
|
+
return buildInaccessibleTabPageData(runtimeCfg, targetTab, "cross_tab_rpc_failed");
|
|
16835
17081
|
}
|
|
16836
17082
|
},
|
|
16837
17083
|
executeTool: async (params) => {
|
|
17084
|
+
const runtimeCfg = currentConfig || cfg;
|
|
16838
17085
|
const activeTabId = sessionCoordinator?.getActiveLogicalTabId();
|
|
16839
17086
|
const localTabId = sessionCoordinator?.getLocalLogicalTabId();
|
|
16840
17087
|
if (!activeTabId || activeTabId === localTabId || !sessionCoordinator) {
|
|
@@ -16842,16 +17089,22 @@ function createRuntime(cfg) {
|
|
|
16842
17089
|
}
|
|
16843
17090
|
const tabs = sessionCoordinator.listTabs();
|
|
16844
17091
|
const targetTab = tabs.find((t) => t.logicalTabId === activeTabId);
|
|
16845
|
-
if (!targetTab
|
|
16846
|
-
return
|
|
17092
|
+
if (!targetTab) {
|
|
17093
|
+
return buildTabAccessToolError(runtimeCfg, { logicalTabId: activeTabId, external: true }, "target_tab_missing");
|
|
17094
|
+
}
|
|
17095
|
+
if (targetTab.external && runtimeCfg.externalNavigationPolicy !== "allow") {
|
|
17096
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "external_tab_action_blocked");
|
|
16847
17097
|
}
|
|
16848
|
-
if (
|
|
17098
|
+
if (targetTab.runtimeId === runtimeId) {
|
|
16849
17099
|
return bridge.executeTool(params.call, params.payload);
|
|
16850
17100
|
}
|
|
17101
|
+
if (!targetTab.runtimeId || !sessionCoordinator.isTabAlive(activeTabId)) {
|
|
17102
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "target_tab_inactive");
|
|
17103
|
+
}
|
|
16851
17104
|
try {
|
|
16852
17105
|
return await sessionCoordinator.sendCrossTabRpc(targetTab.runtimeId, "executeTool", params, 2e4);
|
|
16853
17106
|
} catch {
|
|
16854
|
-
return
|
|
17107
|
+
return buildTabAccessToolError(runtimeCfg, targetTab, "cross_tab_execute_failed");
|
|
16855
17108
|
}
|
|
16856
17109
|
},
|
|
16857
17110
|
executeClientTool: (params) => bridge.executeClientTool(params.name, params.args),
|
|
@@ -16938,6 +17191,9 @@ function createRuntime(cfg) {
|
|
|
16938
17191
|
},
|
|
16939
17192
|
showTaskControls: cfg.ui?.showTaskControls !== false,
|
|
16940
17193
|
muted: cfg.ui?.muted,
|
|
17194
|
+
agent: {
|
|
17195
|
+
name: cfg.ui?.agent?.name
|
|
17196
|
+
},
|
|
16941
17197
|
mascot: {
|
|
16942
17198
|
disabled: cfg.ui?.mascot?.disabled,
|
|
16943
17199
|
mp4Url: cfg.ui?.mascot?.mp4Url,
|
|
@@ -17052,9 +17308,7 @@ function boot(cfg) {
|
|
|
17052
17308
|
plannerOnActError: cfg.taskRouting?.plannerOnActError
|
|
17053
17309
|
},
|
|
17054
17310
|
taskContext: {
|
|
17055
|
-
|
|
17056
|
-
suggestReset: cfg.taskContext?.suggestReset ?? true,
|
|
17057
|
-
semanticSimilarityThreshold: cfg.taskContext?.semanticSimilarityThreshold ?? DEFAULT_SEMANTIC_SIMILARITY_THRESHOLD
|
|
17311
|
+
...cfg.taskContext
|
|
17058
17312
|
}
|
|
17059
17313
|
};
|
|
17060
17314
|
runtimeState.sessionId = currentConfig.sessionId;
|
|
@@ -17115,17 +17369,29 @@ function update(cfg) {
|
|
|
17115
17369
|
...cfg.taskRouting
|
|
17116
17370
|
},
|
|
17117
17371
|
taskContext: {
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
semanticSimilarityThreshold: cfg.taskContext?.semanticSimilarityThreshold ?? currentConfig.taskContext?.semanticSimilarityThreshold ?? DEFAULT_SEMANTIC_SIMILARITY_THRESHOLD
|
|
17372
|
+
...currentConfig.taskContext,
|
|
17373
|
+
...cfg.taskContext
|
|
17121
17374
|
},
|
|
17122
17375
|
ui: {
|
|
17123
17376
|
...currentConfig.ui,
|
|
17124
17377
|
...cfg.ui,
|
|
17378
|
+
agent: {
|
|
17379
|
+
...currentConfig.ui?.agent,
|
|
17380
|
+
...cfg.ui?.agent
|
|
17381
|
+
},
|
|
17125
17382
|
panel: {
|
|
17126
17383
|
...currentConfig.ui?.panel,
|
|
17127
17384
|
...cfg.ui?.panel
|
|
17128
17385
|
}
|
|
17386
|
+
},
|
|
17387
|
+
tools: {
|
|
17388
|
+
...currentConfig.tools,
|
|
17389
|
+
...cfg.tools,
|
|
17390
|
+
client: cfg.tools?.client ?? currentConfig.tools?.client,
|
|
17391
|
+
web: {
|
|
17392
|
+
...currentConfig.tools?.web,
|
|
17393
|
+
...cfg.tools?.web
|
|
17394
|
+
}
|
|
17129
17395
|
}
|
|
17130
17396
|
};
|
|
17131
17397
|
resolvedVisitorId = resolveVisitorId(currentConfig);
|