@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.
@@ -161,6 +161,27 @@ var DEFAULT_GEMINI_MODEL = GEMINI_MODEL.FLASH;
161
161
  var PLANNER = FUNCTION_CALLS.PLANNER;
162
162
  var maxFileSizeInBytes = 20 * 1024 * 1024;
163
163
 
164
+ // ../shared/dist/lib/types/agent-types.js
165
+ var SUB_AGENTS;
166
+ (function(SUB_AGENTS2) {
167
+ SUB_AGENTS2["enhance"] = "enhance";
168
+ SUB_AGENTS2["plan"] = "plan";
169
+ SUB_AGENTS2["actOnTabs"] = "actOnTabs";
170
+ SUB_AGENTS2["extract"] = "extract";
171
+ SUB_AGENTS2["infer"] = "infer";
172
+ SUB_AGENTS2["processTabWorkflows"] = "processTabWorkflows";
173
+ SUB_AGENTS2["processText"] = "processText";
174
+ SUB_AGENTS2["createSheetFromData"] = "createSheetFromData";
175
+ SUB_AGENTS2["queryRtrvrDocs"] = "queryRtrvrDocs";
176
+ SUB_AGENTS2["googleDocGenerator"] = "googleDocGenerator";
177
+ SUB_AGENTS2["googleSlidesGenerator"] = "googleSlidesGenerator";
178
+ SUB_AGENTS2["webpageGenerator"] = "webpageGenerator";
179
+ SUB_AGENTS2["pdfFiller"] = "pdfFiller";
180
+ SUB_AGENTS2["customToolGenerator"] = "customToolGenerator";
181
+ SUB_AGENTS2["getPageData"] = "getPageData";
182
+ SUB_AGENTS2["roverExternalPageData"] = "roverExternalPageData";
183
+ })(SUB_AGENTS || (SUB_AGENTS = {}));
184
+
164
185
  // dist/tabular-memory/tabular-store.js
165
186
  var makeId = () => {
166
187
  if (typeof crypto !== "undefined" && "randomUUID" in crypto)
@@ -620,7 +641,8 @@ function defaultNextActionForCode(code) {
620
641
  return void 0;
621
642
  }
622
643
  function toRoverErrorEnvelope(err, fallbackMessage = "Operation failed") {
623
- const candidate = err?.roverError || err?.errorDetails || (typeof err?.error === "object" ? err.error : void 0);
644
+ const directCandidate = err && typeof err === "object" && typeof err.code === "string" && typeof err.message === "string" ? err : void 0;
645
+ const candidate = err?.roverError || err?.errorDetails || (typeof err?.error === "object" ? err.error : void 0) || directCandidate;
624
646
  if (candidate && typeof candidate === "object" && candidate.code && candidate.message) {
625
647
  return {
626
648
  code: candidate.code,
@@ -664,6 +686,10 @@ var LEGACY_ADDITIONAL_TOOL_ALIASES = {
664
686
  pdf_filling: "generate_docs",
665
687
  generate_web_pages: "generate_websites"
666
688
  };
689
+ var DEFAULT_EXTERNAL_WEB_CONFIG = {
690
+ enableExternalWebContext: false,
691
+ scrapeMode: "off"
692
+ };
667
693
  function normalizeApiToolsConfig(input) {
668
694
  if (!input)
669
695
  return void 0;
@@ -680,6 +706,123 @@ function normalizeApiToolsConfig(input) {
680
706
  enableAdditionalTools: normalizedAdditional
681
707
  };
682
708
  }
709
+ function normalizeDomainRules(input) {
710
+ if (!Array.isArray(input))
711
+ return [];
712
+ const seen = /* @__PURE__ */ new Set();
713
+ const out = [];
714
+ for (const raw of input) {
715
+ const next = String(raw || "").trim().toLowerCase().replace(/^\./, "");
716
+ if (!next || seen.has(next))
717
+ continue;
718
+ seen.add(next);
719
+ out.push(next);
720
+ }
721
+ return out;
722
+ }
723
+ function normalizeAgentName(input) {
724
+ const normalized = String(input || "").trim();
725
+ if (!normalized)
726
+ return void 0;
727
+ return normalized.slice(0, 64);
728
+ }
729
+ function hostFromUrl(url) {
730
+ try {
731
+ return new URL(String(url || "")).hostname.toLowerCase();
732
+ } catch {
733
+ return void 0;
734
+ }
735
+ }
736
+ function matchesDomainRule(host, rule) {
737
+ const clean = String(rule || "").trim().toLowerCase();
738
+ if (!clean)
739
+ return false;
740
+ if (clean === "*")
741
+ return true;
742
+ if (clean.startsWith("*.")) {
743
+ const base = clean.slice(2);
744
+ return !!base && (host === base || host.endsWith(`.${base}`));
745
+ }
746
+ return host === clean || host.endsWith(`.${clean}`);
747
+ }
748
+ function normalizeExternalWebConfig(input) {
749
+ return {
750
+ enableExternalWebContext: input?.enableExternalWebContext ?? DEFAULT_EXTERNAL_WEB_CONFIG.enableExternalWebContext,
751
+ scrapeMode: input?.scrapeMode === "on_demand" ? "on_demand" : DEFAULT_EXTERNAL_WEB_CONFIG.scrapeMode,
752
+ allowDomains: normalizeDomainRules(input?.allowDomains),
753
+ denyDomains: normalizeDomainRules(input?.denyDomains)
754
+ };
755
+ }
756
+ function isAllowedByRules(url, rules) {
757
+ const host = hostFromUrl(url);
758
+ if (!host)
759
+ return { allowed: false, reason: "invalid_url" };
760
+ if (rules.denyDomains.some((rule) => matchesDomainRule(host, rule))) {
761
+ return { allowed: false, reason: `deny_rule:${host}` };
762
+ }
763
+ if (!rules.allowDomains.length)
764
+ return { allowed: true };
765
+ if (rules.allowDomains.some((rule) => matchesDomainRule(host, rule)))
766
+ return { allowed: true };
767
+ return { allowed: false, reason: `not_in_allowlist:${host}` };
768
+ }
769
+ function normalizeRuntimeExternalTabs(input) {
770
+ if (!Array.isArray(input))
771
+ return [];
772
+ const deduped = /* @__PURE__ */ new Map();
773
+ for (const tab of input) {
774
+ const tabId = Number(tab?.tabId);
775
+ if (!Number.isFinite(tabId))
776
+ continue;
777
+ const accessMode = tab?.accessMode === "external_scraped" ? "external_scraped" : "external_placeholder";
778
+ const host = String(tab?.host || "").trim() || void 0;
779
+ const title = String(tab?.title || "").trim() || void 0;
780
+ const reason = String(tab?.reason || "").trim() || void 0;
781
+ deduped.set(tabId, {
782
+ tabId,
783
+ accessMode,
784
+ host,
785
+ title,
786
+ reason
787
+ });
788
+ }
789
+ return Array.from(deduped.values());
790
+ }
791
+ function buildRoverRuntimeContext(config2) {
792
+ const runtimeContext = config2.runtimeContext;
793
+ if (!runtimeContext || runtimeContext.mode !== "rover_embed")
794
+ return void 0;
795
+ const compactExternalTabs = normalizeRuntimeExternalTabs(runtimeContext.externalTabs).slice(0, 8);
796
+ return {
797
+ mode: "rover_embed",
798
+ embeddedDomain: String(runtimeContext.embeddedDomain || "").trim() || void 0,
799
+ agentName: normalizeAgentName(runtimeContext.agentName) || "Rover",
800
+ externalNavigationPolicy: runtimeContext.externalNavigationPolicy || config2.externalNavigationPolicy,
801
+ tabIdContract: runtimeContext.tabIdContract || "tree_index_mapped_by_tab_order",
802
+ ...compactExternalTabs.length ? { externalTabs: compactExternalTabs } : {}
803
+ };
804
+ }
805
+ function buildExternalPlaceholderPageData(params) {
806
+ const embedded = params.embeddedDomain || "the configured domain";
807
+ const agentName = normalizeAgentName(params.agentName) || "Rover";
808
+ const reason = params.reason || "external_domain_inaccessible";
809
+ const title = params.title || "External Tab (Inaccessible)";
810
+ const url = params.url || "";
811
+ const reasonLine = reason ? ` Reason: ${reason}.` : "";
812
+ return {
813
+ url,
814
+ title,
815
+ contentType: "text/html",
816
+ content: `${agentName} is embedded inside ${embedded}. This external tab is tracked, but live DOM control and accessibility-tree access are unavailable here.${reasonLine}`,
817
+ metadata: {
818
+ inaccessible: true,
819
+ external: true,
820
+ accessMode: "external_placeholder",
821
+ reason,
822
+ logicalTabId: params.tabId
823
+ }
824
+ };
825
+ }
683
826
  function getUserFriendlyTimestamp() {
684
827
  const now = /* @__PURE__ */ new Date();
685
828
  const year = now.getFullYear();
@@ -712,8 +855,16 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
712
855
  }
713
856
  const base = (config2.apiBase || DEFAULT_CLOUD_FUNCTIONS_BASE).replace(/\/$/, "");
714
857
  const endpoint = base.endsWith("/extensionRouter") ? base : `${base}/extensionRouter`;
858
+ const runtimeContext = buildRoverRuntimeContext(config2);
859
+ const externalWebConfig = normalizeExternalWebConfig(config2.tools?.web);
860
+ const externalPageDataCache = /* @__PURE__ */ new Map();
861
+ const externalPageDataErrorCache = /* @__PURE__ */ new Map();
862
+ let externalPageDataDisabledReason;
863
+ let cachedActiveTabId = 0;
864
+ let cachedActiveTabTs = 0;
715
865
  const RETRY_DELAYS = [1e3, 3e3];
716
866
  const MAX_RETRIES3 = RETRY_DELAYS.length;
867
+ const ACTIVE_TAB_CACHE_TTL_MS = 250;
717
868
  const callExtensionRouter = async (action, data) => {
718
869
  const token = config2.apiKey || config2.authToken;
719
870
  if (!token) {
@@ -736,7 +887,13 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
736
887
  Authorization: `Bearer ${token}`,
737
888
  "Content-Type": "application/json"
738
889
  },
739
- body: JSON.stringify({ action, data }),
890
+ body: JSON.stringify({
891
+ action,
892
+ data: data && typeof data === "object" ? {
893
+ ...data,
894
+ ...!data.runtimeContext && runtimeContext ? { runtimeContext } : {}
895
+ } : data
896
+ }),
740
897
  signal: config2.signal
741
898
  });
742
899
  if (!response.ok) {
@@ -789,11 +946,135 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
789
946
  }
790
947
  throw lastError || new Error("callExtensionRouter: unexpected retry exhaustion");
791
948
  };
949
+ const getExternalPageData = async (url, options) => {
950
+ const normalizedUrl = String(url || "").trim();
951
+ if (!normalizedUrl)
952
+ throw new Error("external page data requires url");
953
+ const cacheKey = normalizedUrl;
954
+ if (externalPageDataCache.has(cacheKey)) {
955
+ return externalPageDataCache.get(cacheKey);
956
+ }
957
+ if (externalPageDataErrorCache.has(cacheKey)) {
958
+ throw new Error(externalPageDataErrorCache.get(cacheKey) || "external page data fetch failed");
959
+ }
960
+ if (externalPageDataDisabledReason) {
961
+ throw new Error(externalPageDataDisabledReason);
962
+ }
963
+ const source = options?.source || "direct_url";
964
+ const payload = {
965
+ url: normalizedUrl,
966
+ source,
967
+ siteId: config2.siteId,
968
+ roverPolicy: {
969
+ allowDomains: externalWebConfig.allowDomains,
970
+ denyDomains: externalWebConfig.denyDomains
971
+ }
972
+ };
973
+ try {
974
+ const response = await callExtensionRouter(SUB_AGENTS.roverExternalPageData, payload);
975
+ const pageData = response?.data || response;
976
+ externalPageDataCache.set(cacheKey, pageData);
977
+ return pageData;
978
+ } catch (error) {
979
+ const envelope = toRoverErrorEnvelope(error, "external page data fetch failed");
980
+ const normalizedMessage = envelope?.message || "external page data fetch failed";
981
+ externalPageDataErrorCache.set(cacheKey, normalizedMessage);
982
+ if (envelope.code === "PERMISSION_DENIED" || envelope.code === "MISSING_API_KEY" || envelope.code === "INVALID_API_KEY") {
983
+ externalPageDataDisabledReason = `${envelope.code}: ${normalizedMessage}`;
984
+ }
985
+ throw error;
986
+ }
987
+ };
988
+ const getActiveLogicalTabId = async () => {
989
+ const nowMs = Date.now();
990
+ if (cachedActiveTabId > 0 && nowMs - cachedActiveTabTs <= ACTIVE_TAB_CACHE_TTL_MS) {
991
+ return cachedActiveTabId;
992
+ }
993
+ try {
994
+ const context = await bridgeRpc2("getTabContext");
995
+ const active = Number(context?.activeLogicalTabId || context?.logicalTabId || context?.id);
996
+ if (Number.isFinite(active) && active > 0) {
997
+ cachedActiveTabId = active;
998
+ cachedActiveTabTs = nowMs;
999
+ return active;
1000
+ }
1001
+ } catch {
1002
+ }
1003
+ return void 0;
1004
+ };
792
1005
  const getPageData = async (tabId, options) => {
793
- if (options) {
794
- return bridgeRpc2("getPageData", { pageConfig: options, tabId });
1006
+ const numericTabId = Number(tabId);
1007
+ const rawOptions = options && typeof options === "object" ? options : void 0;
1008
+ const allowExternalFetch = rawOptions?.__roverAllowExternalFetch === true;
1009
+ const pageConfig = rawOptions && typeof rawOptions === "object" ? Object.fromEntries(Object.entries(rawOptions).filter(([key]) => key !== "__roverAllowExternalFetch")) : void 0;
1010
+ const hasPageConfig = !!pageConfig && Object.keys(pageConfig).length > 0;
1011
+ const localPageData = rawOptions ? await bridgeRpc2("getPageData", hasPageConfig ? { pageConfig, tabId: numericTabId } : { tabId: numericTabId }) : await bridgeRpc2("getPageData", { tabId: numericTabId });
1012
+ const metadata = localPageData?.metadata || {};
1013
+ const isExternal = !!metadata?.external || metadata?.accessMode === "external_placeholder" || metadata?.accessMode === "external_scraped";
1014
+ if (!isExternal) {
1015
+ return localPageData;
1016
+ }
1017
+ const pageUrl = typeof localPageData?.url === "string" ? localPageData.url.trim() : "";
1018
+ if (!externalWebConfig.enableExternalWebContext || externalWebConfig.scrapeMode === "off" || !pageUrl) {
1019
+ return localPageData;
1020
+ }
1021
+ if (externalPageDataDisabledReason) {
1022
+ return buildExternalPlaceholderPageData({
1023
+ tabId: numericTabId,
1024
+ url: pageUrl,
1025
+ title: localPageData?.title,
1026
+ embeddedDomain: runtimeContext?.embeddedDomain,
1027
+ agentName: runtimeContext?.agentName,
1028
+ reason: `cloud_fetch_disabled:${externalPageDataDisabledReason}`
1029
+ });
1030
+ }
1031
+ const ruleCheck = isAllowedByRules(pageUrl, {
1032
+ allowDomains: externalWebConfig.allowDomains,
1033
+ denyDomains: externalWebConfig.denyDomains
1034
+ });
1035
+ if (!ruleCheck.allowed) {
1036
+ return buildExternalPlaceholderPageData({
1037
+ tabId: numericTabId,
1038
+ url: pageUrl,
1039
+ title: localPageData?.title,
1040
+ embeddedDomain: runtimeContext?.embeddedDomain,
1041
+ agentName: runtimeContext?.agentName,
1042
+ reason: `policy_blocked:${ruleCheck.reason || "blocked"}`
1043
+ });
1044
+ }
1045
+ const activeTabId = await getActiveLogicalTabId();
1046
+ const shouldFetchExternalContext = allowExternalFetch || (activeTabId ? activeTabId === numericTabId : false);
1047
+ if (!shouldFetchExternalContext) {
1048
+ return localPageData;
1049
+ }
1050
+ try {
1051
+ const cloudData = await getExternalPageData(pageUrl, {
1052
+ tabId: numericTabId,
1053
+ source: pageUrl.includes("google.com/search") ? "google_search" : "direct_url"
1054
+ });
1055
+ if (cloudData && typeof cloudData === "object") {
1056
+ return {
1057
+ ...cloudData,
1058
+ metadata: {
1059
+ ...cloudData.metadata || {},
1060
+ external: true,
1061
+ accessMode: "external_scraped",
1062
+ logicalTabId: numericTabId
1063
+ }
1064
+ };
1065
+ }
1066
+ return localPageData;
1067
+ } catch (error) {
1068
+ const envelope = toRoverErrorEnvelope(error, "external page data fetch failed");
1069
+ return buildExternalPlaceholderPageData({
1070
+ tabId: numericTabId,
1071
+ url: pageUrl,
1072
+ title: localPageData?.title,
1073
+ embeddedDomain: runtimeContext?.embeddedDomain,
1074
+ agentName: runtimeContext?.agentName,
1075
+ reason: `cloud_fetch_failed:${envelope?.code || "unknown"}`
1076
+ });
795
1077
  }
796
- return bridgeRpc2("getPageData", { tabId });
797
1078
  };
798
1079
  const userProfile = config2.userProfile || (config2.userContext ? { userContext: config2.userContext } : void 0);
799
1080
  const defaultApiToolsConfig = apiMode ? {
@@ -806,6 +1087,7 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
806
1087
  llmIntegration,
807
1088
  userProfile,
808
1089
  getPageData,
1090
+ getExternalPageData,
809
1091
  callExtensionRouter,
810
1092
  apiMode,
811
1093
  apiToolsConfig: normalizeApiToolsConfig(config2.apiToolsConfig ?? defaultApiToolsConfig),
@@ -1014,26 +1296,6 @@ var GeminiModel;
1014
1296
  GeminiModel2["PRO"] = "Gemini Pro";
1015
1297
  })(GeminiModel || (GeminiModel = {}));
1016
1298
 
1017
- // ../shared/dist/lib/types/agent-types.js
1018
- var SUB_AGENTS;
1019
- (function(SUB_AGENTS2) {
1020
- SUB_AGENTS2["enhance"] = "enhance";
1021
- SUB_AGENTS2["plan"] = "plan";
1022
- SUB_AGENTS2["actOnTabs"] = "actOnTabs";
1023
- SUB_AGENTS2["extract"] = "extract";
1024
- SUB_AGENTS2["infer"] = "infer";
1025
- SUB_AGENTS2["processTabWorkflows"] = "processTabWorkflows";
1026
- SUB_AGENTS2["processText"] = "processText";
1027
- SUB_AGENTS2["createSheetFromData"] = "createSheetFromData";
1028
- SUB_AGENTS2["queryRtrvrDocs"] = "queryRtrvrDocs";
1029
- SUB_AGENTS2["googleDocGenerator"] = "googleDocGenerator";
1030
- SUB_AGENTS2["googleSlidesGenerator"] = "googleSlidesGenerator";
1031
- SUB_AGENTS2["webpageGenerator"] = "webpageGenerator";
1032
- SUB_AGENTS2["pdfFiller"] = "pdfFiller";
1033
- SUB_AGENTS2["customToolGenerator"] = "customToolGenerator";
1034
- SUB_AGENTS2["getPageData"] = "getPageData";
1035
- })(SUB_AGENTS || (SUB_AGENTS = {}));
1036
-
1037
1299
  // ../shared/dist/lib/system-tools/tools.js
1038
1300
  var SystemToolNames;
1039
1301
  (function(SystemToolNames2) {
@@ -1315,6 +1577,127 @@ function formatFunctionResultsIntoPrevSteps(results, functionCalls) {
1315
1577
  return steps;
1316
1578
  }
1317
1579
 
1580
+ // dist/agent/runtimeTabs.js
1581
+ var DEFAULT_MAX_CONTEXT_TABS = 8;
1582
+ var DEFAULT_DETACHED_EXTERNAL_TAB_MAX_AGE_MS = 9e4;
1583
+ var DEFAULT_STALE_RUNTIME_TAB_MAX_AGE_MS = 45e3;
1584
+ function dedupePositiveTabIds(input) {
1585
+ const seen = /* @__PURE__ */ new Set();
1586
+ const out = [];
1587
+ for (const value of input) {
1588
+ const next = Number(value);
1589
+ if (!Number.isFinite(next) || next <= 0 || seen.has(next))
1590
+ continue;
1591
+ seen.add(next);
1592
+ out.push(next);
1593
+ }
1594
+ return out;
1595
+ }
1596
+ function normalizeAccessMode(value, external) {
1597
+ if (value === "external_scraped" || value === "external_placeholder" || value === "live_dom")
1598
+ return value;
1599
+ return external ? "external_placeholder" : "live_dom";
1600
+ }
1601
+ function normalizeFallbackTabs(input) {
1602
+ return input.map((tab) => ({
1603
+ id: Number(tab?.id),
1604
+ url: typeof tab?.url === "string" ? tab.url : void 0,
1605
+ title: typeof tab?.title === "string" ? tab.title : void 0,
1606
+ external: !!tab?.external,
1607
+ accessMode: normalizeAccessMode(tab?.accessMode, !!tab?.external),
1608
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0
1609
+ })).filter((tab) => Number.isFinite(tab.id) && tab.id > 0);
1610
+ }
1611
+ function normalizeListedTabs(input) {
1612
+ if (!Array.isArray(input))
1613
+ return [];
1614
+ return input.map((tab) => {
1615
+ const id = Number(tab?.logicalTabId || tab?.id);
1616
+ if (!Number.isFinite(id) || id <= 0)
1617
+ return void 0;
1618
+ const external = !!tab?.external;
1619
+ return {
1620
+ id,
1621
+ runtimeId: typeof tab?.runtimeId === "string" ? tab.runtimeId : void 0,
1622
+ url: typeof tab?.url === "string" ? tab.url : void 0,
1623
+ title: typeof tab?.title === "string" ? tab.title : void 0,
1624
+ external,
1625
+ accessMode: normalizeAccessMode(tab?.accessMode, external),
1626
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0,
1627
+ updatedAt: Number(tab?.updatedAt) || 0
1628
+ };
1629
+ }).filter((tab) => !!tab);
1630
+ }
1631
+ async function resolveRuntimeTabs(bridgeRpc2, fallbackTabs, options) {
1632
+ const maxContextTabs = Math.max(1, Number(options?.maxContextTabs) || DEFAULT_MAX_CONTEXT_TABS);
1633
+ const detachedExternalTabMaxAgeMs = Math.max(5e3, Number(options?.detachedExternalTabMaxAgeMs) || DEFAULT_DETACHED_EXTERNAL_TAB_MAX_AGE_MS);
1634
+ const staleRuntimeTabMaxAgeMs = Math.max(1e4, Number(options?.staleRuntimeTabMaxAgeMs) || DEFAULT_STALE_RUNTIME_TAB_MAX_AGE_MS);
1635
+ const fallbackSnapshots = normalizeFallbackTabs(fallbackTabs);
1636
+ const fallbackTabIds = dedupePositiveTabIds(fallbackSnapshots.map((tab) => tab.id));
1637
+ let tabIds = [...fallbackTabIds];
1638
+ let listedTabs = [];
1639
+ if (bridgeRpc2) {
1640
+ try {
1641
+ listedTabs = normalizeListedTabs(await bridgeRpc2("listSessionTabs"));
1642
+ const listedIds = dedupePositiveTabIds(listedTabs.map((tab) => tab.id));
1643
+ if (listedIds.length > 0) {
1644
+ tabIds = listedIds;
1645
+ }
1646
+ } catch {
1647
+ }
1648
+ }
1649
+ let activeTabId = tabIds[0] || fallbackTabIds[0] || 1;
1650
+ if (bridgeRpc2) {
1651
+ try {
1652
+ const context = await bridgeRpc2("getTabContext");
1653
+ const candidate = Number(context?.activeLogicalTabId || context?.logicalTabId || context?.id);
1654
+ if (Number.isFinite(candidate) && candidate > 0) {
1655
+ activeTabId = candidate;
1656
+ }
1657
+ } catch {
1658
+ }
1659
+ }
1660
+ if (!tabIds.length) {
1661
+ tabIds = [activeTabId];
1662
+ } else if (!tabIds.includes(activeTabId)) {
1663
+ tabIds = [activeTabId, ...tabIds];
1664
+ }
1665
+ const nowMs = Date.now();
1666
+ const listedById = /* @__PURE__ */ new Map();
1667
+ for (const tab of listedTabs)
1668
+ listedById.set(tab.id, tab);
1669
+ const prioritized = tabIds.filter((tabId) => {
1670
+ if (tabId === activeTabId)
1671
+ return true;
1672
+ const listed = listedById.get(tabId);
1673
+ if (!listed)
1674
+ return true;
1675
+ if (listed.runtimeId) {
1676
+ return nowMs - (listed.updatedAt || 0) <= staleRuntimeTabMaxAgeMs;
1677
+ }
1678
+ if (!listed.external)
1679
+ return true;
1680
+ return nowMs - (listed.updatedAt || 0) <= detachedExternalTabMaxAgeMs;
1681
+ });
1682
+ let tabOrder = (prioritized.length ? prioritized : tabIds).slice(0, maxContextTabs);
1683
+ if (!tabOrder.includes(activeTabId)) {
1684
+ tabOrder = [activeTabId, ...tabOrder].slice(0, maxContextTabs);
1685
+ }
1686
+ if (!tabOrder.length) {
1687
+ tabOrder = [activeTabId || 1];
1688
+ }
1689
+ const tabMetaById = {};
1690
+ for (const tab of fallbackSnapshots)
1691
+ tabMetaById[tab.id] = tab;
1692
+ for (const tab of listedTabs)
1693
+ tabMetaById[tab.id] = { ...tabMetaById[tab.id] || {}, ...tab };
1694
+ for (const tabId of tabOrder) {
1695
+ if (!tabMetaById[tabId])
1696
+ tabMetaById[tabId] = { id: tabId };
1697
+ }
1698
+ return { tabOrder, activeTabId, tabMetaById };
1699
+ }
1700
+
1318
1701
  // dist/agent/actAgent.js
1319
1702
  var MAX_RETRIES = 3;
1320
1703
  var MAX_ITERATIONS = 25;
@@ -1326,10 +1709,10 @@ async function executeAgenticSeek(options) {
1326
1709
  let totalCreditsUsed = 0;
1327
1710
  const allWarnings = [];
1328
1711
  const accumulatedPrevSteps = Array.isArray(previousSteps) ? previousSteps : [];
1712
+ const fallbackTabs = tabOrder.map((id) => ({ id }));
1329
1713
  let retry = 0;
1330
1714
  let iterations = 0;
1331
1715
  let pageDataOptions;
1332
- const tabId = tabOrder[0];
1333
1716
  while (retry < MAX_RETRIES) {
1334
1717
  if (iterations++ >= MAX_ITERATIONS) {
1335
1718
  return { error: "Max action iterations reached", prevSteps: accumulatedPrevSteps, creditsUsed: totalCreditsUsed };
@@ -1340,13 +1723,44 @@ async function executeAgenticSeek(options) {
1340
1723
  try {
1341
1724
  await waitWhilePaused(void 0);
1342
1725
  onStatusUpdate?.("Analyzing page content...", "Calling seek workflow", "analyze");
1343
- const pageData = await ctx.getPageData(tabId, pageDataOptions);
1726
+ const { tabOrder: runtimeTabOrder, activeTabId } = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
1727
+ const scopedTabOrder = runtimeTabOrder.length ? runtimeTabOrder : tabOrder;
1728
+ const webPageMap = {};
1729
+ try {
1730
+ webPageMap[activeTabId] = await ctx.getPageData(activeTabId, {
1731
+ ...pageDataOptions || {},
1732
+ __roverAllowExternalFetch: true
1733
+ });
1734
+ } catch {
1735
+ retry++;
1736
+ continue;
1737
+ }
1738
+ const backgroundTabIds = scopedTabOrder.filter((currentTabId) => currentTabId !== activeTabId);
1739
+ const backgroundResults = await Promise.all(backgroundTabIds.map(async (currentTabId) => {
1740
+ try {
1741
+ const pageData = await ctx.getPageData(currentTabId);
1742
+ return { tabId: currentTabId, pageData };
1743
+ } catch {
1744
+ return { tabId: currentTabId, pageData: void 0 };
1745
+ }
1746
+ }));
1747
+ for (const result of backgroundResults) {
1748
+ if (result.pageData) {
1749
+ webPageMap[result.tabId] = result.pageData;
1750
+ } else {
1751
+ allWarnings.push(`Could not load page data for tab ${result.tabId}; continuing with remaining tabs.`);
1752
+ }
1753
+ }
1754
+ if (!webPageMap[activeTabId]) {
1755
+ retry++;
1756
+ continue;
1757
+ }
1344
1758
  const request = {
1345
1759
  siteId: ctx.siteId,
1346
1760
  customTabWorkflow: "Seek",
1347
- webPageMap: { [tabId]: pageData },
1348
- tabOrder: [tabId],
1349
- activeTabId: tabId,
1761
+ webPageMap,
1762
+ tabOrder: scopedTabOrder,
1763
+ activeTabId,
1350
1764
  userInput,
1351
1765
  dataJsonSchema: schema,
1352
1766
  files,
@@ -1368,7 +1782,7 @@ async function executeAgenticSeek(options) {
1368
1782
  }
1369
1783
  const data = response.data;
1370
1784
  totalCreditsUsed += data?.creditsUsed || 0;
1371
- const tabResponse = data?.tabResponses?.[tabId];
1785
+ const tabResponse = data?.tabResponses?.[activeTabId];
1372
1786
  if (!tabResponse) {
1373
1787
  retry++;
1374
1788
  continue;
@@ -1382,7 +1796,7 @@ async function executeAgenticSeek(options) {
1382
1796
  const processResult = await processActionResponse({
1383
1797
  request,
1384
1798
  response: tabResponse,
1385
- tabId,
1799
+ tabId: activeTabId,
1386
1800
  prevSteps: accumulatedPrevSteps,
1387
1801
  thought: tabResponse.thought,
1388
1802
  bridgeRpc: bridgeRpc2,
@@ -1447,20 +1861,28 @@ async function executeExtract(options) {
1447
1861
  }
1448
1862
  let totalCreditsUsed = 0;
1449
1863
  const warnings = [];
1450
- const tabId = tabOrder[0];
1864
+ const fallbackTabs = tabOrder.map((id) => ({ id }));
1451
1865
  const prevSteps = Array.isArray(previousSteps) ? previousSteps : [];
1452
1866
  let pageDataOptions;
1453
1867
  for (let retry = 0; retry < MAX_RETRIES2; retry++) {
1454
1868
  await waitWhilePaused(void 0);
1455
- const pageData = await ctx.getPageData(tabId, pageDataOptions);
1869
+ const { activeTabId } = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
1870
+ const tabId = activeTabId;
1871
+ let pageData;
1872
+ try {
1873
+ pageData = await ctx.getPageData(tabId, pageDataOptions);
1874
+ } catch {
1875
+ warnings.push(`Could not load page data for tab ${tabId}; retrying.`);
1876
+ continue;
1877
+ }
1456
1878
  const request = {
1457
1879
  siteId: ctx.siteId,
1458
1880
  userInput,
1459
1881
  schema,
1460
1882
  outputDestination,
1461
1883
  schemaHeaderSheetInfo,
1462
- webPageMap: { [tabId]: pageData },
1463
- tabOrder: [tabId],
1884
+ webPageMap: { [activeTabId]: pageData },
1885
+ tabOrder: [activeTabId],
1464
1886
  plannerPrevSteps,
1465
1887
  llmIntegration: ctx.llmIntegration,
1466
1888
  apiMode: ctx.apiMode,
@@ -1491,12 +1913,12 @@ async function executeExtract(options) {
1491
1913
  warnings
1492
1914
  };
1493
1915
  }
1494
- const tabResponse = data?.tabResponses?.[tabId];
1916
+ const tabResponse = data?.tabResponses?.[activeTabId];
1495
1917
  if (tabResponse) {
1496
1918
  const processResult = await processActionResponse({
1497
1919
  request,
1498
1920
  response: tabResponse,
1499
- tabId,
1921
+ tabId: activeTabId,
1500
1922
  prevSteps,
1501
1923
  thought: tabResponse.thought,
1502
1924
  bridgeRpc: bridgeRpc2,
@@ -2151,6 +2573,8 @@ function resolvePath(obj, path) {
2151
2573
  }
2152
2574
 
2153
2575
  // dist/agent/toolExecutor.js
2576
+ var MAX_AGENT_CHATLOG_ENTRIES = 24;
2577
+ var MAX_AGENT_CHATLOG_MESSAGE_CHARS = 1e3;
2154
2578
  function unsupportedToolResult(toolName, message) {
2155
2579
  return {
2156
2580
  error: message,
@@ -2180,10 +2604,18 @@ function buildStructuredErrorOutput(envelope) {
2180
2604
  }
2181
2605
  function normalizeAgentLog(agentLog) {
2182
2606
  const prevSteps = Array.isArray(agentLog?.prevSteps) ? agentLog.prevSteps : [];
2607
+ const sanitizeMessage = (value) => {
2608
+ const normalized = String(value || "").replace(/\s+/g, " ").trim();
2609
+ if (!normalized)
2610
+ return "";
2611
+ if (normalized.length <= MAX_AGENT_CHATLOG_MESSAGE_CHARS)
2612
+ return normalized;
2613
+ return `${normalized.slice(0, MAX_AGENT_CHATLOG_MESSAGE_CHARS - 1)}\u2026`;
2614
+ };
2183
2615
  const chatLog = Array.isArray(agentLog?.chatLog) ? agentLog.chatLog.map((entry) => ({
2184
2616
  role: entry?.role === "user" ? "user" : "model",
2185
- message: typeof entry?.message === "string" ? entry.message : ""
2186
- })).filter((entry) => !!entry.message) : [];
2617
+ message: typeof entry?.message === "string" ? sanitizeMessage(entry.message) : ""
2618
+ })).filter((entry) => !!entry.message).slice(-MAX_AGENT_CHATLOG_ENTRIES) : [];
2187
2619
  return { prevSteps, chatLog };
2188
2620
  }
2189
2621
  async function executeToolFromPlan(context) {
@@ -2192,7 +2624,9 @@ async function executeToolFromPlan(context) {
2192
2624
  if (!effectiveCtx) {
2193
2625
  return { error: "Agent context unavailable" };
2194
2626
  }
2195
- const tabOrder = tabs?.length ? tabs.map((t) => t.id) : [1];
2627
+ const fallbackTabs = Array.isArray(tabs) && tabs.length ? tabs : [{ id: 1 }];
2628
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
2629
+ const tabOrder = resolvedTabs.tabOrder.length ? resolvedTabs.tabOrder : fallbackTabs.map((tab) => tab.id);
2196
2630
  const effectiveAgentLog = normalizeAgentLog(agentLog);
2197
2631
  try {
2198
2632
  switch (toolName) {
@@ -2999,18 +3433,50 @@ async function executeCustomToolGenerator({ userInput, plannerPrevSteps, files,
2999
3433
 
3000
3434
  // dist/agent/plannerAgent.js
3001
3435
  var MAX_PLANNER_DEPTH = 15;
3436
+ var MAX_CHATLOG_ENTRIES = 24;
3437
+ var MAX_CHATLOG_MESSAGE_CHARS = 1e3;
3438
+ function normalizeChatLog(entries) {
3439
+ if (!Array.isArray(entries) || !entries.length)
3440
+ return [];
3441
+ return entries.map((entry) => ({
3442
+ role: entry?.role === "user" ? "user" : "model",
3443
+ message: String(entry?.message || "").replace(/\s+/g, " ").trim()
3444
+ })).filter((entry) => !!entry.message).map((entry) => ({
3445
+ role: entry.role,
3446
+ message: entry.message.length > MAX_CHATLOG_MESSAGE_CHARS ? `${entry.message.slice(0, MAX_CHATLOG_MESSAGE_CHARS - 1)}\u2026` : entry.message
3447
+ })).slice(-MAX_CHATLOG_ENTRIES);
3448
+ }
3002
3449
  async function executePlanner(options) {
3003
- const { userInput, tabs, previousMessages = [], trajectoryId: trajectoryId2, previousSteps = [], files, continuePlanning = false, recordingContext, driveAuthToken, agentLog, lastToolPreviousSteps, ctx, functionDeclarations } = options;
3004
- const tabOrder = tabs.map((t) => t.id);
3450
+ const { userInput, tabs, previousMessages = [], trajectoryId: trajectoryId2, previousSteps = [], files, continuePlanning = false, recordingContext, driveAuthToken, agentLog, lastToolPreviousSteps, ctx, bridgeRpc: bridgeRpc2, functionDeclarations } = options;
3451
+ const fallbackTabs = Array.isArray(tabs) && tabs.length ? tabs : [{ id: 1 }];
3452
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
3453
+ const tabOrder = resolvedTabs.tabOrder.length ? resolvedTabs.tabOrder : fallbackTabs.map((tab) => tab.id);
3454
+ const activeTabId = resolvedTabs.activeTabId;
3455
+ const tabMetaById = resolvedTabs.tabMetaById;
3005
3456
  const webPageMap = {};
3006
- for (const tab of tabs) {
3457
+ const loadPageData = async (tabId, options2) => {
3007
3458
  try {
3008
- webPageMap[tab.id] = await ctx.getPageData(tab.id, { onlyTextContent: false });
3459
+ return await ctx.getPageData(tabId, {
3460
+ onlyTextContent: false,
3461
+ ...options2?.allowExternalFetch ? { __roverAllowExternalFetch: true } : {}
3462
+ });
3009
3463
  } catch {
3010
- webPageMap[tab.id] = { url: tab.url || "", title: tab.title || "", content: "", contentType: "text/html" };
3464
+ const tab = tabMetaById[tabId];
3465
+ return {
3466
+ url: tab?.url || "",
3467
+ title: tab?.title || (tab?.external ? "External Tab (Inaccessible)" : ""),
3468
+ content: "",
3469
+ contentType: "text/html"
3470
+ };
3011
3471
  }
3472
+ };
3473
+ webPageMap[activeTabId] = await loadPageData(activeTabId, { allowExternalFetch: true });
3474
+ const backgroundTabIds = tabOrder.filter((tabId) => tabId !== activeTabId);
3475
+ const backgroundResults = await Promise.all(backgroundTabIds.map(async (tabId) => ({ tabId, pageData: await loadPageData(tabId) })));
3476
+ for (const { tabId, pageData } of backgroundResults) {
3477
+ webPageMap[tabId] = pageData;
3012
3478
  }
3013
- const chatLog = agentLog?.chatLog?.length ? agentLog.chatLog : previousMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role === "user" ? "user" : "model", message: m.content }));
3479
+ const chatLog = agentLog?.chatLog?.length ? normalizeChatLog(agentLog.chatLog) : normalizeChatLog(previousMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role === "user" ? "user" : "model", message: m.content })));
3014
3480
  const request = {
3015
3481
  siteId: ctx.siteId,
3016
3482
  userInput,
@@ -3687,7 +4153,98 @@ var activeAbortController = null;
3687
4153
  var lastStatusKey = "";
3688
4154
  var seenStatusKeys = /* @__PURE__ */ new Set();
3689
4155
  var completedRunIds = /* @__PURE__ */ new Set();
4156
+ var completedRunOutcomes = /* @__PURE__ */ new Map();
3690
4157
  var RPC_TIMEOUT_MS = 3e4;
4158
+ var DETACHED_EXTERNAL_TAB_MAX_AGE_MS = 9e4;
4159
+ var MAX_CHATLOG_ENTRIES2 = 24;
4160
+ var MAX_CHATLOG_MESSAGE_CHARS2 = 1e3;
4161
+ function resolveEmbeddedDomain(config2) {
4162
+ const domains = Array.isArray(config2?.allowedDomains) ? config2.allowedDomains : [];
4163
+ const first = String(domains[0] || "").trim();
4164
+ if (!first)
4165
+ return void 0;
4166
+ return first.replace(/^\*?\./, "").replace(/^=/, "");
4167
+ }
4168
+ function resolveAgentName(config2) {
4169
+ const raw = String(config2?.ui?.agent?.name || "").trim();
4170
+ if (!raw)
4171
+ return "Rover";
4172
+ return raw.slice(0, 64);
4173
+ }
4174
+ function hostFromUrl2(url) {
4175
+ const raw = String(url || "").trim();
4176
+ if (!raw)
4177
+ return void 0;
4178
+ try {
4179
+ return new URL(raw).hostname.toLowerCase();
4180
+ } catch {
4181
+ return void 0;
4182
+ }
4183
+ }
4184
+ function extractWebToolsConfig(config2) {
4185
+ if (!config2?.tools || Array.isArray(config2.tools))
4186
+ return void 0;
4187
+ return config2.tools.web;
4188
+ }
4189
+ function buildRoverRuntimeContext2(params) {
4190
+ const externalTabs = params.tabs.map((tab, index) => {
4191
+ if (!tab.external && tab.accessMode !== "external_placeholder" && tab.accessMode !== "external_scraped") {
4192
+ return void 0;
4193
+ }
4194
+ const accessMode = tab.accessMode === "external_scraped" ? "external_scraped" : "external_placeholder";
4195
+ return {
4196
+ tabId: index,
4197
+ host: hostFromUrl2(tab.url),
4198
+ title: String(tab.title || "").trim() || void 0,
4199
+ accessMode,
4200
+ reason: String(tab.inaccessibleReason || "").trim() || void 0
4201
+ };
4202
+ }).filter((tab) => !!tab).slice(0, 8);
4203
+ return {
4204
+ mode: "rover_embed",
4205
+ embeddedDomain: params.embeddedDomain,
4206
+ agentName: params.agentName,
4207
+ externalNavigationPolicy: params.externalNavigationPolicy,
4208
+ tabIdContract: "tree_index_mapped_by_tab_order",
4209
+ ...externalTabs.length ? { externalTabs } : {}
4210
+ };
4211
+ }
4212
+ function mergeWorkerTools(current, incoming) {
4213
+ if (incoming === void 0)
4214
+ return current;
4215
+ if (Array.isArray(incoming))
4216
+ return incoming;
4217
+ if (Array.isArray(current)) {
4218
+ return {
4219
+ ...incoming,
4220
+ client: incoming.client ?? current,
4221
+ web: {
4222
+ ...incoming.web || {}
4223
+ }
4224
+ };
4225
+ }
4226
+ return {
4227
+ ...current,
4228
+ ...incoming,
4229
+ client: incoming.client ?? current?.client,
4230
+ web: {
4231
+ ...current?.web || {},
4232
+ ...incoming.web || {}
4233
+ }
4234
+ };
4235
+ }
4236
+ function mergeWorkerUi(current, incoming) {
4237
+ if (!incoming)
4238
+ return current;
4239
+ return {
4240
+ ...current,
4241
+ ...incoming,
4242
+ agent: {
4243
+ ...current?.agent || {},
4244
+ ...incoming.agent || {}
4245
+ }
4246
+ };
4247
+ }
3691
4248
  function createRpcClient(port) {
3692
4249
  const pending = /* @__PURE__ */ new Map();
3693
4250
  port.onmessage = (ev) => {
@@ -3725,16 +4282,18 @@ async function getCurrentTab() {
3725
4282
  return {
3726
4283
  id: Number(tabContext.logicalTabId || tabContext.id || 1),
3727
4284
  url: tabContext.url,
3728
- title: tabContext.title
4285
+ title: tabContext.title,
4286
+ external: false,
4287
+ accessMode: "live_dom"
3729
4288
  };
3730
4289
  }
3731
4290
  } catch {
3732
4291
  }
3733
4292
  try {
3734
4293
  const pageData = await bridgeRpc("getPageData");
3735
- return { id: 1, url: pageData?.url, title: pageData?.title };
4294
+ return { id: 1, url: pageData?.url, title: pageData?.title, external: false, accessMode: "live_dom" };
3736
4295
  } catch {
3737
- return { id: 1 };
4296
+ return { id: 1, external: false, accessMode: "live_dom" };
3738
4297
  }
3739
4298
  }
3740
4299
  async function getKnownTabs() {
@@ -3743,11 +4302,32 @@ async function getKnownTabs() {
3743
4302
  try {
3744
4303
  const listed = await bridgeRpc("listSessionTabs");
3745
4304
  if (Array.isArray(listed) && listed.length > 0) {
3746
- const mapped = listed.map((tab) => ({
3747
- id: Number(tab?.logicalTabId || tab?.id || 0),
3748
- url: typeof tab?.url === "string" ? tab.url : void 0,
3749
- title: typeof tab?.title === "string" ? tab.title : void 0
3750
- })).filter((tab) => Number.isFinite(tab.id) && tab.id > 0);
4305
+ const nowMs = Date.now();
4306
+ const mapped = listed.map((tab) => {
4307
+ const id = Number(tab?.logicalTabId || tab?.id || 0);
4308
+ const external = !!tab?.external;
4309
+ return {
4310
+ id,
4311
+ runtimeId: typeof tab?.runtimeId === "string" ? tab.runtimeId : void 0,
4312
+ updatedAt: Number(tab?.updatedAt) || 0,
4313
+ url: typeof tab?.url === "string" ? tab.url : void 0,
4314
+ title: typeof tab?.title === "string" ? tab.title : void 0,
4315
+ external,
4316
+ accessMode: tab?.accessMode === "external_scraped" || tab?.accessMode === "external_placeholder" ? tab.accessMode : external ? "external_placeholder" : "live_dom",
4317
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0
4318
+ };
4319
+ }).filter((tab) => Number.isFinite(tab.id) && tab.id > 0).filter((tab) => {
4320
+ if (!tab.external || tab.runtimeId)
4321
+ return true;
4322
+ return nowMs - (tab.updatedAt || 0) <= DETACHED_EXTERNAL_TAB_MAX_AGE_MS;
4323
+ }).map((tab) => ({
4324
+ id: tab.id,
4325
+ url: tab.url,
4326
+ title: tab.title,
4327
+ external: tab.external,
4328
+ accessMode: tab.accessMode,
4329
+ inaccessibleReason: tab.inaccessibleReason
4330
+ }));
3751
4331
  if (mapped.length)
3752
4332
  return mapped;
3753
4333
  }
@@ -4112,19 +4692,46 @@ function maybePostNavigationGuardrailFromToolResult(toolResult) {
4112
4692
  });
4113
4693
  }
4114
4694
  function buildChatLogFromHistory(input, currentUserInput) {
4695
+ const sanitizeChatText = (raw, role) => {
4696
+ let text = String(raw || "").replace(/\s+/g, " ").trim();
4697
+ if (!text)
4698
+ return "";
4699
+ if (role === "assistant") {
4700
+ if (/^\w[\w_]*:\s*\{/.test(text) || /^\[error\]/i.test(text) || /"success":\s*false/.test(text)) {
4701
+ const firstSentence = text.split(/(?<=\.)\s+/)[0] || text;
4702
+ text = firstSentence.trim();
4703
+ }
4704
+ }
4705
+ if (text.length > MAX_CHATLOG_MESSAGE_CHARS2) {
4706
+ text = `${text.slice(0, MAX_CHATLOG_MESSAGE_CHARS2 - 1)}\u2026`;
4707
+ }
4708
+ return text;
4709
+ };
4710
+ const normalizedCurrentUserInput = currentUserInput ? sanitizeChatText(currentUserInput, "user") : "";
4115
4711
  const entries = input.filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
4116
4712
  role: message.role,
4117
- content: String(message.content || "")
4118
- }));
4119
- if (currentUserInput) {
4713
+ content: sanitizeChatText(String(message.content || ""), message.role)
4714
+ })).filter((message) => !!message.content);
4715
+ if (normalizedCurrentUserInput) {
4120
4716
  for (let i = entries.length - 1; i >= 0; i -= 1) {
4121
- if (entries[i].role === "user" && entries[i].content === currentUserInput) {
4717
+ if (entries[i].role === "user" && entries[i].content === normalizedCurrentUserInput) {
4122
4718
  entries.splice(i, 1);
4123
4719
  break;
4124
4720
  }
4125
4721
  }
4126
4722
  }
4127
- return entries.slice(-40).map((message) => ({
4723
+ const deduped = [];
4724
+ for (const entry of entries) {
4725
+ const previous = deduped[deduped.length - 1];
4726
+ if (previous && previous.role === entry.role && previous.content === entry.content)
4727
+ continue;
4728
+ deduped.push(entry);
4729
+ }
4730
+ const tail = deduped.slice(-MAX_CHATLOG_ENTRIES2);
4731
+ while (tail.length > 1 && tail[0]?.role !== "user") {
4732
+ tail.shift();
4733
+ }
4734
+ return tail.map((message) => ({
4128
4735
  role: message.role === "user" ? "user" : "model",
4129
4736
  message: message.content
4130
4737
  }));
@@ -4222,6 +4829,9 @@ async function waitForNewTabReady(logicalTabId, timeoutMs = 1e4) {
4222
4829
  const tabs = await bridgeRpc("listSessionTabs");
4223
4830
  if (Array.isArray(tabs)) {
4224
4831
  const target = tabs.find((t) => Number(t?.logicalTabId) === logicalTabId);
4832
+ if (target?.external) {
4833
+ return true;
4834
+ }
4225
4835
  if (target?.runtimeId) {
4226
4836
  await new Promise((resolve) => setTimeout(resolve, 1e3));
4227
4837
  return true;
@@ -4242,6 +4852,65 @@ function detectOpenedTabFromToolResult(result) {
4242
4852
  }
4243
4853
  return void 0;
4244
4854
  }
4855
+ function deriveDirectToolRunOutcome(result) {
4856
+ if (!result || typeof result !== "object") {
4857
+ return { taskComplete: false };
4858
+ }
4859
+ if (result.error) {
4860
+ return { taskComplete: false };
4861
+ }
4862
+ const output = result.output;
4863
+ if (output && typeof output === "object") {
4864
+ if (Array.isArray(output)) {
4865
+ return { taskComplete: true };
4866
+ }
4867
+ if (output.needsUserInput === true || output.waitingForUserInput === true) {
4868
+ return { taskComplete: false, needsUserInput: true };
4869
+ }
4870
+ if (Array.isArray(output.questions) && output.questions.length > 0) {
4871
+ return { taskComplete: false, needsUserInput: true };
4872
+ }
4873
+ if (typeof output.taskComplete === "boolean") {
4874
+ return { taskComplete: !!output.taskComplete };
4875
+ }
4876
+ const taskStatus = String(output.taskStatus || output.status || "").toLowerCase();
4877
+ if (taskStatus) {
4878
+ if (taskStatus === "waiting_input" || taskStatus === "needs_input" || taskStatus === "pending_user_input") {
4879
+ return { taskComplete: false, needsUserInput: true };
4880
+ }
4881
+ if (taskStatus === "running" || taskStatus === "in_progress" || taskStatus === "pending") {
4882
+ return { taskComplete: false };
4883
+ }
4884
+ if (taskStatus === "completed" || taskStatus === "complete" || taskStatus === "done" || taskStatus === "success") {
4885
+ return { taskComplete: true };
4886
+ }
4887
+ }
4888
+ if (output.success === false) {
4889
+ return { taskComplete: false };
4890
+ }
4891
+ }
4892
+ if (output != null) {
4893
+ return { taskComplete: true };
4894
+ }
4895
+ return { taskComplete: false };
4896
+ }
4897
+ function normalizeRunOutcome(outcome) {
4898
+ if (!outcome || typeof outcome !== "object") {
4899
+ return { taskComplete: false, needsUserInput: false };
4900
+ }
4901
+ const needsUserInput = outcome.needsUserInput === true;
4902
+ const taskComplete = outcome.taskComplete === true && !needsUserInput;
4903
+ return {
4904
+ route: outcome.route,
4905
+ taskComplete,
4906
+ needsUserInput
4907
+ };
4908
+ }
4909
+ function clearTaskScopedContextAfterCompletion() {
4910
+ history.length = 0;
4911
+ plannerHistory = [];
4912
+ agentPrevSteps = [];
4913
+ }
4245
4914
  async function maybeWaitForNewTab(result) {
4246
4915
  const logicalTabId = detectOpenedTabFromToolResult(result);
4247
4916
  if (logicalTabId) {
@@ -4261,10 +4930,52 @@ async function handleUserMessage(text, options) {
4261
4930
  postStateSnapshot();
4262
4931
  }
4263
4932
  const tabs = await getKnownTabs();
4933
+ const fallbackTabs = tabs.length > 0 ? tabs : [
4934
+ {
4935
+ id: 1,
4936
+ external: false,
4937
+ accessMode: "live_dom"
4938
+ }
4939
+ ];
4940
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc, fallbackTabs);
4941
+ const tabsById = new Map(tabs.map((tab) => [tab.id, tab]));
4942
+ const orderedTabs = resolvedTabs.tabOrder.map((tabId) => {
4943
+ const knownTab = tabsById.get(tabId);
4944
+ if (knownTab)
4945
+ return knownTab;
4946
+ const tabMeta = resolvedTabs.tabMetaById[tabId];
4947
+ if (!tabMeta)
4948
+ return { id: tabId };
4949
+ const external = !!tabMeta.external;
4950
+ return {
4951
+ id: tabId,
4952
+ url: tabMeta.url,
4953
+ title: tabMeta.title,
4954
+ external,
4955
+ accessMode: tabMeta.accessMode || (external ? "external_placeholder" : "live_dom"),
4956
+ inaccessibleReason: tabMeta.inaccessibleReason
4957
+ };
4958
+ }).filter((tab) => Number.isFinite(tab.id) && tab.id > 0);
4959
+ const tabsForRun = orderedTabs.length > 0 ? orderedTabs : fallbackTabs;
4264
4960
  if (!tabularStore) {
4265
4961
  tabularStore = new TabularStore(`rover-${trajectoryId}`);
4266
4962
  }
4267
- const ctx = createAgentContext({ ...config, signal: activeAbortController?.signal }, bridgeRpc, tabularStore);
4963
+ const embeddedDomain = resolveEmbeddedDomain(config);
4964
+ const agentName = resolveAgentName(config);
4965
+ const runtimeContext = buildRoverRuntimeContext2({
4966
+ tabs: tabsForRun,
4967
+ embeddedDomain,
4968
+ agentName,
4969
+ externalNavigationPolicy: config.externalNavigationPolicy
4970
+ });
4971
+ const ctx = createAgentContext({
4972
+ ...config,
4973
+ signal: activeAbortController?.signal,
4974
+ runtimeContext,
4975
+ tools: {
4976
+ web: extractWebToolsConfig(config)
4977
+ }
4978
+ }, bridgeRpc, tabularStore);
4268
4979
  const currentRunId = activeRun?.runId;
4269
4980
  ctx.isCancelled = () => cancelledRunId === currentRunId;
4270
4981
  const functionDeclarations = dedupeFunctionDeclarations(removePlannerNameCollisions(toolRegistry.getFunctionDeclarations()));
@@ -4278,7 +4989,7 @@ async function handleUserMessage(text, options) {
4278
4989
  postStateSnapshot();
4279
4990
  };
4280
4991
  const result = await handleSendMessageWithFunctions(text, {
4281
- tabs,
4992
+ tabs: tabsForRun,
4282
4993
  previousMessages: history,
4283
4994
  trajectoryId,
4284
4995
  files: [],
@@ -4308,7 +5019,7 @@ async function handleUserMessage(text, options) {
4308
5019
  history.push({ role: "assistant", content: errorMsg });
4309
5020
  postStatus("Execution failed", errorPayload.error.message, "complete");
4310
5021
  postStateSnapshot();
4311
- return result.route;
5022
+ return { route: result.route, taskComplete: false };
4312
5023
  }
4313
5024
  if (result.executedFunctions?.length) {
4314
5025
  for (const fn of result.executedFunctions) {
@@ -4323,7 +5034,7 @@ async function handleUserMessage(text, options) {
4323
5034
  history.push({ role: "assistant", content: msg });
4324
5035
  postStatus("Execution completed", "Function calls finished", "complete");
4325
5036
  postStateSnapshot();
4326
- return result.route;
5037
+ return { route: result.route, taskComplete: true };
4327
5038
  }
4328
5039
  if (result.directToolResult) {
4329
5040
  await maybeWaitForNewTab(result.directToolResult);
@@ -4341,7 +5052,12 @@ async function handleUserMessage(text, options) {
4341
5052
  history.push({ role: "assistant", content: msg });
4342
5053
  postStatus("Execution completed", structuredError?.error.message, "complete");
4343
5054
  postStateSnapshot();
4344
- return result.route;
5055
+ const outcome = deriveDirectToolRunOutcome(result.directToolResult);
5056
+ return {
5057
+ route: result.route,
5058
+ taskComplete: outcome.taskComplete,
5059
+ needsUserInput: outcome.needsUserInput
5060
+ };
4345
5061
  }
4346
5062
  if (result.plannerResponse) {
4347
5063
  postStatus("Verifying planner output", void 0, "verify");
@@ -4360,7 +5076,7 @@ ${qText}`;
4360
5076
  history.push({ role: "assistant", content: msg2 });
4361
5077
  postStatus("Planner needs user input", void 0, "verify");
4362
5078
  postStateSnapshot();
4363
- return result.route;
5079
+ return { route: result.route, taskComplete: false, needsUserInput: true };
4364
5080
  }
4365
5081
  const toolResults = result.plannerResponse.toolResults || [];
4366
5082
  for (const toolResult of toolResults) {
@@ -4377,18 +5093,30 @@ ${qText}`;
4377
5093
  history.push({ role: "assistant", content: msg });
4378
5094
  postStatus("Planner execution completed", response.overallThought, "complete");
4379
5095
  postStateSnapshot();
4380
- return result.route;
5096
+ return {
5097
+ route: result.route,
5098
+ taskComplete: !!response.taskComplete && !responseError,
5099
+ needsUserInput: false
5100
+ };
4381
5101
  }
4382
5102
  postAssistantMessage("Done.");
4383
5103
  history.push({ role: "assistant", content: "Done." });
4384
5104
  postStatus("Completed", void 0, "complete");
4385
5105
  postStateSnapshot();
4386
- return result.route;
5106
+ return { route: result.route, taskComplete: true };
4387
5107
  }
4388
5108
  async function runUserMessage(text, meta) {
4389
5109
  const runId = meta?.runId || crypto.randomUUID();
4390
5110
  if (completedRunIds.has(runId)) {
4391
- self.postMessage({ type: "run_completed", runId, ok: true, route: void 0 });
5111
+ const cachedOutcome = normalizeRunOutcome(completedRunOutcomes.get(runId));
5112
+ self.postMessage({
5113
+ type: "run_completed",
5114
+ runId,
5115
+ ok: true,
5116
+ route: cachedOutcome.route,
5117
+ taskComplete: cachedOutcome.taskComplete,
5118
+ needsUserInput: cachedOutcome.needsUserInput
5119
+ });
4392
5120
  return;
4393
5121
  }
4394
5122
  if (activeRun && activeRun.runId === runId) {
@@ -4402,13 +5130,39 @@ async function runUserMessage(text, meta) {
4402
5130
  self.postMessage({ type: "run_started", runId, text, resume });
4403
5131
  postStateSnapshot();
4404
5132
  try {
4405
- const route = await handleUserMessage(text, { resume });
4406
- self.postMessage({ type: "run_completed", runId, ok: true, route });
5133
+ const outcome = normalizeRunOutcome(await handleUserMessage(text, { resume }));
5134
+ completedRunOutcomes.set(runId, outcome);
5135
+ self.postMessage({
5136
+ type: "run_completed",
5137
+ runId,
5138
+ ok: true,
5139
+ route: outcome.route,
5140
+ taskComplete: outcome.taskComplete,
5141
+ needsUserInput: outcome.needsUserInput
5142
+ });
5143
+ if (outcome.taskComplete && !outcome.needsUserInput) {
5144
+ clearTaskScopedContextAfterCompletion();
5145
+ postStateSnapshot();
5146
+ }
4407
5147
  } catch (error) {
4408
5148
  if (error?.name === "AbortError") {
4409
- self.postMessage({ type: "run_completed", runId, ok: false, error: "Run cancelled" });
5149
+ self.postMessage({
5150
+ type: "run_completed",
5151
+ runId,
5152
+ ok: false,
5153
+ error: "Run cancelled",
5154
+ taskComplete: false,
5155
+ needsUserInput: false
5156
+ });
4410
5157
  } else {
4411
- self.postMessage({ type: "run_completed", runId, ok: false, error: error?.message || String(error) });
5158
+ self.postMessage({
5159
+ type: "run_completed",
5160
+ runId,
5161
+ ok: false,
5162
+ error: error?.message || String(error),
5163
+ taskComplete: false,
5164
+ needsUserInput: false
5165
+ });
4412
5166
  throw error;
4413
5167
  }
4414
5168
  } finally {
@@ -4417,8 +5171,10 @@ async function runUserMessage(text, meta) {
4417
5171
  completedRunIds.add(runId);
4418
5172
  if (completedRunIds.size > 50) {
4419
5173
  const oldest = completedRunIds.values().next().value;
4420
- if (oldest)
5174
+ if (oldest) {
4421
5175
  completedRunIds.delete(oldest);
5176
+ completedRunOutcomes.delete(oldest);
5177
+ }
4422
5178
  }
4423
5179
  postStateSnapshot();
4424
5180
  }
@@ -4457,7 +5213,12 @@ self.onmessage = async (ev) => {
4457
5213
  if (!config)
4458
5214
  throw new Error("Worker not initialized");
4459
5215
  const partial = data.config || {};
4460
- config = { ...config, ...partial };
5216
+ config = {
5217
+ ...config,
5218
+ ...partial,
5219
+ ui: mergeWorkerUi(config.ui, partial.ui),
5220
+ tools: mergeWorkerTools(config.tools, partial.tools)
5221
+ };
4461
5222
  if (typeof partial.sessionId === "string" && partial.sessionId.trim() && partial.sessionId.trim() !== trajectoryId) {
4462
5223
  trajectoryId = partial.sessionId.trim();
4463
5224
  plannerHistory = [];