@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.
@@ -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,121 @@ 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
+ agentName: normalizeAgentName(runtimeContext.agentName) || "Rover",
799
+ externalNavigationPolicy: runtimeContext.externalNavigationPolicy || config2.externalNavigationPolicy,
800
+ tabIdContract: runtimeContext.tabIdContract || "tree_index_mapped_by_tab_order",
801
+ ...compactExternalTabs.length ? { externalTabs: compactExternalTabs } : {}
802
+ };
803
+ }
804
+ function buildExternalPlaceholderPageData(params) {
805
+ const agentName = normalizeAgentName(params.agentName) || "Rover";
806
+ const reason = params.reason || "external_domain_inaccessible";
807
+ const title = params.title || "External Tab (Inaccessible)";
808
+ const url = params.url || "";
809
+ const reasonLine = reason ? ` Reason: ${reason}.` : "";
810
+ return {
811
+ url,
812
+ title,
813
+ contentType: "text/html",
814
+ content: `${agentName} is running in virtual external-tab mode. Live DOM control and accessibility-tree access are unavailable here.${reasonLine}`,
815
+ metadata: {
816
+ inaccessible: true,
817
+ external: true,
818
+ accessMode: "external_placeholder",
819
+ reason,
820
+ logicalTabId: params.tabId
821
+ }
822
+ };
823
+ }
683
824
  function getUserFriendlyTimestamp() {
684
825
  const now = /* @__PURE__ */ new Date();
685
826
  const year = now.getFullYear();
@@ -712,10 +853,19 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
712
853
  }
713
854
  const base = (config2.apiBase || DEFAULT_CLOUD_FUNCTIONS_BASE).replace(/\/$/, "");
714
855
  const endpoint = base.endsWith("/extensionRouter") ? base : `${base}/extensionRouter`;
856
+ const runtimeContext = buildRoverRuntimeContext(config2);
857
+ const externalWebConfig = normalizeExternalWebConfig(config2.tools?.web);
858
+ const externalPageDataCache = /* @__PURE__ */ new Map();
859
+ const externalPageDataErrorCache = /* @__PURE__ */ new Map();
860
+ let externalPageDataDisabledReason;
861
+ let cachedActiveTabId = 0;
862
+ let cachedActiveTabTs = 0;
715
863
  const RETRY_DELAYS = [1e3, 3e3];
716
864
  const MAX_RETRIES3 = RETRY_DELAYS.length;
865
+ const ACTIVE_TAB_CACHE_TTL_MS = 250;
866
+ const EXTERNAL_PAGE_CACHE_TTL_MS = 45e3;
717
867
  const callExtensionRouter = async (action, data) => {
718
- const token = config2.apiKey || config2.authToken;
868
+ const token = config2.authToken || config2.apiKey;
719
869
  if (!token) {
720
870
  throw createRoverError({
721
871
  code: "MISSING_API_KEY",
@@ -736,7 +886,13 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
736
886
  Authorization: `Bearer ${token}`,
737
887
  "Content-Type": "application/json"
738
888
  },
739
- body: JSON.stringify({ action, data }),
889
+ body: JSON.stringify({
890
+ action,
891
+ data: data && typeof data === "object" ? {
892
+ ...data,
893
+ ...!data.runtimeContext && runtimeContext ? { runtimeContext } : {}
894
+ } : data
895
+ }),
740
896
  signal: config2.signal
741
897
  });
742
898
  if (!response.ok) {
@@ -789,11 +945,138 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
789
945
  }
790
946
  throw lastError || new Error("callExtensionRouter: unexpected retry exhaustion");
791
947
  };
948
+ const getExternalPageData = async (url, options) => {
949
+ const normalizedUrl = String(url || "").trim();
950
+ if (!normalizedUrl)
951
+ throw new Error("external page data requires url");
952
+ const cacheTabId = Number(options?.tabId) || 0;
953
+ const cacheKey = `${cacheTabId}:${normalizedUrl}`;
954
+ const nowMs = Date.now();
955
+ const cachedData = externalPageDataCache.get(cacheKey);
956
+ if (cachedData && nowMs - cachedData.ts <= EXTERNAL_PAGE_CACHE_TTL_MS) {
957
+ return cachedData.data;
958
+ }
959
+ externalPageDataCache.delete(cacheKey);
960
+ const cachedError = externalPageDataErrorCache.get(cacheKey);
961
+ if (cachedError && nowMs - cachedError.ts <= EXTERNAL_PAGE_CACHE_TTL_MS) {
962
+ throw new Error(cachedError.message || "external page data fetch failed");
963
+ }
964
+ externalPageDataErrorCache.delete(cacheKey);
965
+ if (externalPageDataDisabledReason) {
966
+ throw new Error(externalPageDataDisabledReason);
967
+ }
968
+ const source = options?.source || "direct_url";
969
+ const payload = {
970
+ url: normalizedUrl,
971
+ source,
972
+ siteId: config2.siteId,
973
+ roverPolicy: {
974
+ allowDomains: externalWebConfig.allowDomains,
975
+ denyDomains: externalWebConfig.denyDomains
976
+ }
977
+ };
978
+ try {
979
+ const response = await callExtensionRouter(SUB_AGENTS.roverExternalPageData, payload);
980
+ const pageData = response?.data || response;
981
+ externalPageDataCache.set(cacheKey, { data: pageData, ts: nowMs });
982
+ return pageData;
983
+ } catch (error) {
984
+ const envelope = toRoverErrorEnvelope(error, "external page data fetch failed");
985
+ const normalizedMessage = envelope?.message || "external page data fetch failed";
986
+ externalPageDataErrorCache.set(cacheKey, { message: normalizedMessage, ts: nowMs });
987
+ if (envelope.code === "PERMISSION_DENIED" || envelope.code === "MISSING_API_KEY" || envelope.code === "INVALID_API_KEY") {
988
+ externalPageDataDisabledReason = `${envelope.code}: ${normalizedMessage}`;
989
+ }
990
+ throw error;
991
+ }
992
+ };
993
+ const getActiveLogicalTabId = async () => {
994
+ const nowMs = Date.now();
995
+ if (cachedActiveTabId > 0 && nowMs - cachedActiveTabTs <= ACTIVE_TAB_CACHE_TTL_MS) {
996
+ return cachedActiveTabId;
997
+ }
998
+ try {
999
+ const context = await bridgeRpc2("getTabContext");
1000
+ const active = Number(context?.activeLogicalTabId || context?.logicalTabId || context?.id);
1001
+ if (Number.isFinite(active) && active > 0) {
1002
+ cachedActiveTabId = active;
1003
+ cachedActiveTabTs = nowMs;
1004
+ return active;
1005
+ }
1006
+ } catch {
1007
+ }
1008
+ return void 0;
1009
+ };
792
1010
  const getPageData = async (tabId, options) => {
793
- if (options) {
794
- return bridgeRpc2("getPageData", { pageConfig: options, tabId });
1011
+ const numericTabId = Number(tabId);
1012
+ const rawOptions = options && typeof options === "object" ? options : void 0;
1013
+ const allowExternalFetch = rawOptions?.__roverAllowExternalFetch === true;
1014
+ const pageConfig = rawOptions && typeof rawOptions === "object" ? Object.fromEntries(Object.entries(rawOptions).filter(([key]) => key !== "__roverAllowExternalFetch")) : void 0;
1015
+ const hasPageConfig = !!pageConfig && Object.keys(pageConfig).length > 0;
1016
+ const localPageData = rawOptions ? await bridgeRpc2("getPageData", hasPageConfig ? { pageConfig, tabId: numericTabId } : { tabId: numericTabId }) : await bridgeRpc2("getPageData", { tabId: numericTabId });
1017
+ const metadata = localPageData?.metadata || {};
1018
+ const isExternal = !!metadata?.external || metadata?.accessMode === "external_placeholder" || metadata?.accessMode === "external_scraped";
1019
+ if (!isExternal) {
1020
+ return localPageData;
1021
+ }
1022
+ const pageUrl = typeof localPageData?.url === "string" ? localPageData.url.trim() : "";
1023
+ if (!externalWebConfig.enableExternalWebContext || externalWebConfig.scrapeMode === "off" || !pageUrl) {
1024
+ return localPageData;
1025
+ }
1026
+ if (externalPageDataDisabledReason) {
1027
+ return buildExternalPlaceholderPageData({
1028
+ tabId: numericTabId,
1029
+ url: pageUrl,
1030
+ title: localPageData?.title,
1031
+ agentName: runtimeContext?.agentName,
1032
+ reason: `cloud_fetch_disabled:${externalPageDataDisabledReason}`
1033
+ });
1034
+ }
1035
+ const ruleCheck = isAllowedByRules(pageUrl, {
1036
+ allowDomains: externalWebConfig.allowDomains,
1037
+ denyDomains: externalWebConfig.denyDomains
1038
+ });
1039
+ if (!ruleCheck.allowed) {
1040
+ return buildExternalPlaceholderPageData({
1041
+ tabId: numericTabId,
1042
+ url: pageUrl,
1043
+ title: localPageData?.title,
1044
+ agentName: runtimeContext?.agentName,
1045
+ reason: `policy_blocked:${ruleCheck.reason || "blocked"}`
1046
+ });
1047
+ }
1048
+ const activeTabId = await getActiveLogicalTabId();
1049
+ const shouldFetchExternalContext = allowExternalFetch || (activeTabId ? activeTabId === numericTabId : false);
1050
+ if (!shouldFetchExternalContext) {
1051
+ return localPageData;
1052
+ }
1053
+ try {
1054
+ const cloudData = await getExternalPageData(pageUrl, {
1055
+ tabId: numericTabId,
1056
+ source: pageUrl.includes("google.com/search") ? "google_search" : "direct_url"
1057
+ });
1058
+ if (cloudData && typeof cloudData === "object") {
1059
+ return {
1060
+ ...cloudData,
1061
+ metadata: {
1062
+ ...cloudData.metadata || {},
1063
+ external: true,
1064
+ accessMode: "external_scraped",
1065
+ logicalTabId: numericTabId
1066
+ }
1067
+ };
1068
+ }
1069
+ return localPageData;
1070
+ } catch (error) {
1071
+ const envelope = toRoverErrorEnvelope(error, "external page data fetch failed");
1072
+ return buildExternalPlaceholderPageData({
1073
+ tabId: numericTabId,
1074
+ url: pageUrl,
1075
+ title: localPageData?.title,
1076
+ agentName: runtimeContext?.agentName,
1077
+ reason: `cloud_fetch_failed:${envelope?.code || "unknown"}`
1078
+ });
795
1079
  }
796
- return bridgeRpc2("getPageData", { tabId });
797
1080
  };
798
1081
  const userProfile = config2.userProfile || (config2.userContext ? { userContext: config2.userContext } : void 0);
799
1082
  const defaultApiToolsConfig = apiMode ? {
@@ -806,6 +1089,7 @@ function createAgentContext(config2, bridgeRpc2, tabularStore2) {
806
1089
  llmIntegration,
807
1090
  userProfile,
808
1091
  getPageData,
1092
+ getExternalPageData,
809
1093
  callExtensionRouter,
810
1094
  apiMode,
811
1095
  apiToolsConfig: normalizeApiToolsConfig(config2.apiToolsConfig ?? defaultApiToolsConfig),
@@ -1014,26 +1298,6 @@ var GeminiModel;
1014
1298
  GeminiModel2["PRO"] = "Gemini Pro";
1015
1299
  })(GeminiModel || (GeminiModel = {}));
1016
1300
 
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
1301
  // ../shared/dist/lib/system-tools/tools.js
1038
1302
  var SystemToolNames;
1039
1303
  (function(SystemToolNames2) {
@@ -1315,6 +1579,127 @@ function formatFunctionResultsIntoPrevSteps(results, functionCalls) {
1315
1579
  return steps;
1316
1580
  }
1317
1581
 
1582
+ // dist/agent/runtimeTabs.js
1583
+ var DEFAULT_MAX_CONTEXT_TABS = 8;
1584
+ var DEFAULT_DETACHED_EXTERNAL_TAB_MAX_AGE_MS = 9e4;
1585
+ var DEFAULT_STALE_RUNTIME_TAB_MAX_AGE_MS = 45e3;
1586
+ function dedupePositiveTabIds(input) {
1587
+ const seen = /* @__PURE__ */ new Set();
1588
+ const out = [];
1589
+ for (const value of input) {
1590
+ const next = Number(value);
1591
+ if (!Number.isFinite(next) || next <= 0 || seen.has(next))
1592
+ continue;
1593
+ seen.add(next);
1594
+ out.push(next);
1595
+ }
1596
+ return out;
1597
+ }
1598
+ function normalizeAccessMode(value, external) {
1599
+ if (value === "external_scraped" || value === "external_placeholder" || value === "live_dom")
1600
+ return value;
1601
+ return external ? "external_placeholder" : "live_dom";
1602
+ }
1603
+ function normalizeFallbackTabs(input) {
1604
+ return input.map((tab) => ({
1605
+ id: Number(tab?.id),
1606
+ url: typeof tab?.url === "string" ? tab.url : void 0,
1607
+ title: typeof tab?.title === "string" ? tab.title : void 0,
1608
+ external: !!tab?.external,
1609
+ accessMode: normalizeAccessMode(tab?.accessMode, !!tab?.external),
1610
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0
1611
+ })).filter((tab) => Number.isFinite(tab.id) && tab.id > 0);
1612
+ }
1613
+ function normalizeListedTabs(input) {
1614
+ if (!Array.isArray(input))
1615
+ return [];
1616
+ return input.map((tab) => {
1617
+ const id = Number(tab?.logicalTabId || tab?.id);
1618
+ if (!Number.isFinite(id) || id <= 0)
1619
+ return void 0;
1620
+ const external = !!tab?.external;
1621
+ return {
1622
+ id,
1623
+ runtimeId: typeof tab?.runtimeId === "string" ? tab.runtimeId : void 0,
1624
+ url: typeof tab?.url === "string" ? tab.url : void 0,
1625
+ title: typeof tab?.title === "string" ? tab.title : void 0,
1626
+ external,
1627
+ accessMode: normalizeAccessMode(tab?.accessMode, external),
1628
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0,
1629
+ updatedAt: Number(tab?.updatedAt) || 0
1630
+ };
1631
+ }).filter((tab) => !!tab);
1632
+ }
1633
+ async function resolveRuntimeTabs(bridgeRpc2, fallbackTabs, options) {
1634
+ const maxContextTabs = Math.max(1, Number(options?.maxContextTabs) || DEFAULT_MAX_CONTEXT_TABS);
1635
+ const detachedExternalTabMaxAgeMs = Math.max(5e3, Number(options?.detachedExternalTabMaxAgeMs) || DEFAULT_DETACHED_EXTERNAL_TAB_MAX_AGE_MS);
1636
+ const staleRuntimeTabMaxAgeMs = Math.max(1e4, Number(options?.staleRuntimeTabMaxAgeMs) || DEFAULT_STALE_RUNTIME_TAB_MAX_AGE_MS);
1637
+ const fallbackSnapshots = normalizeFallbackTabs(fallbackTabs);
1638
+ const fallbackTabIds = dedupePositiveTabIds(fallbackSnapshots.map((tab) => tab.id));
1639
+ let tabIds = [...fallbackTabIds];
1640
+ let listedTabs = [];
1641
+ if (bridgeRpc2) {
1642
+ try {
1643
+ listedTabs = normalizeListedTabs(await bridgeRpc2("listSessionTabs"));
1644
+ const listedIds = dedupePositiveTabIds(listedTabs.map((tab) => tab.id));
1645
+ if (listedIds.length > 0) {
1646
+ tabIds = listedIds;
1647
+ }
1648
+ } catch {
1649
+ }
1650
+ }
1651
+ let activeTabId = tabIds[0] || fallbackTabIds[0] || 1;
1652
+ if (bridgeRpc2) {
1653
+ try {
1654
+ const context = await bridgeRpc2("getTabContext");
1655
+ const candidate = Number(context?.activeLogicalTabId || context?.logicalTabId || context?.id);
1656
+ if (Number.isFinite(candidate) && candidate > 0) {
1657
+ activeTabId = candidate;
1658
+ }
1659
+ } catch {
1660
+ }
1661
+ }
1662
+ if (!tabIds.length) {
1663
+ tabIds = [activeTabId];
1664
+ } else if (!tabIds.includes(activeTabId)) {
1665
+ tabIds = [activeTabId, ...tabIds];
1666
+ }
1667
+ const nowMs = Date.now();
1668
+ const listedById = /* @__PURE__ */ new Map();
1669
+ for (const tab of listedTabs)
1670
+ listedById.set(tab.id, tab);
1671
+ const prioritized = tabIds.filter((tabId) => {
1672
+ if (tabId === activeTabId)
1673
+ return true;
1674
+ const listed = listedById.get(tabId);
1675
+ if (!listed)
1676
+ return true;
1677
+ if (listed.runtimeId) {
1678
+ return nowMs - (listed.updatedAt || 0) <= staleRuntimeTabMaxAgeMs;
1679
+ }
1680
+ if (!listed.external)
1681
+ return true;
1682
+ return nowMs - (listed.updatedAt || 0) <= detachedExternalTabMaxAgeMs;
1683
+ });
1684
+ let tabOrder = (prioritized.length ? prioritized : tabIds).slice(0, maxContextTabs);
1685
+ if (!tabOrder.includes(activeTabId)) {
1686
+ tabOrder = [activeTabId, ...tabOrder].slice(0, maxContextTabs);
1687
+ }
1688
+ if (!tabOrder.length) {
1689
+ tabOrder = [activeTabId || 1];
1690
+ }
1691
+ const tabMetaById = {};
1692
+ for (const tab of fallbackSnapshots)
1693
+ tabMetaById[tab.id] = tab;
1694
+ for (const tab of listedTabs)
1695
+ tabMetaById[tab.id] = { ...tabMetaById[tab.id] || {}, ...tab };
1696
+ for (const tabId of tabOrder) {
1697
+ if (!tabMetaById[tabId])
1698
+ tabMetaById[tabId] = { id: tabId };
1699
+ }
1700
+ return { tabOrder, activeTabId, tabMetaById };
1701
+ }
1702
+
1318
1703
  // dist/agent/actAgent.js
1319
1704
  var MAX_RETRIES = 3;
1320
1705
  var MAX_ITERATIONS = 25;
@@ -1326,10 +1711,10 @@ async function executeAgenticSeek(options) {
1326
1711
  let totalCreditsUsed = 0;
1327
1712
  const allWarnings = [];
1328
1713
  const accumulatedPrevSteps = Array.isArray(previousSteps) ? previousSteps : [];
1714
+ const fallbackTabs = tabOrder.map((id) => ({ id }));
1329
1715
  let retry = 0;
1330
1716
  let iterations = 0;
1331
1717
  let pageDataOptions;
1332
- const tabId = tabOrder[0];
1333
1718
  while (retry < MAX_RETRIES) {
1334
1719
  if (iterations++ >= MAX_ITERATIONS) {
1335
1720
  return { error: "Max action iterations reached", prevSteps: accumulatedPrevSteps, creditsUsed: totalCreditsUsed };
@@ -1340,13 +1725,44 @@ async function executeAgenticSeek(options) {
1340
1725
  try {
1341
1726
  await waitWhilePaused(void 0);
1342
1727
  onStatusUpdate?.("Analyzing page content...", "Calling seek workflow", "analyze");
1343
- const pageData = await ctx.getPageData(tabId, pageDataOptions);
1728
+ const { tabOrder: runtimeTabOrder, activeTabId } = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
1729
+ const scopedTabOrder = runtimeTabOrder.length ? runtimeTabOrder : tabOrder;
1730
+ const webPageMap = {};
1731
+ try {
1732
+ webPageMap[activeTabId] = await ctx.getPageData(activeTabId, {
1733
+ ...pageDataOptions || {},
1734
+ __roverAllowExternalFetch: true
1735
+ });
1736
+ } catch {
1737
+ retry++;
1738
+ continue;
1739
+ }
1740
+ const backgroundTabIds = scopedTabOrder.filter((currentTabId) => currentTabId !== activeTabId);
1741
+ const backgroundResults = await Promise.all(backgroundTabIds.map(async (currentTabId) => {
1742
+ try {
1743
+ const pageData = await ctx.getPageData(currentTabId);
1744
+ return { tabId: currentTabId, pageData };
1745
+ } catch {
1746
+ return { tabId: currentTabId, pageData: void 0 };
1747
+ }
1748
+ }));
1749
+ for (const result of backgroundResults) {
1750
+ if (result.pageData) {
1751
+ webPageMap[result.tabId] = result.pageData;
1752
+ } else {
1753
+ allWarnings.push(`Could not load page data for tab ${result.tabId}; continuing with remaining tabs.`);
1754
+ }
1755
+ }
1756
+ if (!webPageMap[activeTabId]) {
1757
+ retry++;
1758
+ continue;
1759
+ }
1344
1760
  const request = {
1345
1761
  siteId: ctx.siteId,
1346
1762
  customTabWorkflow: "Seek",
1347
- webPageMap: { [tabId]: pageData },
1348
- tabOrder: [tabId],
1349
- activeTabId: tabId,
1763
+ webPageMap,
1764
+ tabOrder: scopedTabOrder,
1765
+ activeTabId,
1350
1766
  userInput,
1351
1767
  dataJsonSchema: schema,
1352
1768
  files,
@@ -1368,7 +1784,7 @@ async function executeAgenticSeek(options) {
1368
1784
  }
1369
1785
  const data = response.data;
1370
1786
  totalCreditsUsed += data?.creditsUsed || 0;
1371
- const tabResponse = data?.tabResponses?.[tabId];
1787
+ const tabResponse = data?.tabResponses?.[activeTabId];
1372
1788
  if (!tabResponse) {
1373
1789
  retry++;
1374
1790
  continue;
@@ -1382,7 +1798,7 @@ async function executeAgenticSeek(options) {
1382
1798
  const processResult = await processActionResponse({
1383
1799
  request,
1384
1800
  response: tabResponse,
1385
- tabId,
1801
+ tabId: activeTabId,
1386
1802
  prevSteps: accumulatedPrevSteps,
1387
1803
  thought: tabResponse.thought,
1388
1804
  bridgeRpc: bridgeRpc2,
@@ -1447,20 +1863,28 @@ async function executeExtract(options) {
1447
1863
  }
1448
1864
  let totalCreditsUsed = 0;
1449
1865
  const warnings = [];
1450
- const tabId = tabOrder[0];
1866
+ const fallbackTabs = tabOrder.map((id) => ({ id }));
1451
1867
  const prevSteps = Array.isArray(previousSteps) ? previousSteps : [];
1452
1868
  let pageDataOptions;
1453
1869
  for (let retry = 0; retry < MAX_RETRIES2; retry++) {
1454
1870
  await waitWhilePaused(void 0);
1455
- const pageData = await ctx.getPageData(tabId, pageDataOptions);
1871
+ const { activeTabId } = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
1872
+ const tabId = activeTabId;
1873
+ let pageData;
1874
+ try {
1875
+ pageData = await ctx.getPageData(tabId, pageDataOptions);
1876
+ } catch {
1877
+ warnings.push(`Could not load page data for tab ${tabId}; retrying.`);
1878
+ continue;
1879
+ }
1456
1880
  const request = {
1457
1881
  siteId: ctx.siteId,
1458
1882
  userInput,
1459
1883
  schema,
1460
1884
  outputDestination,
1461
1885
  schemaHeaderSheetInfo,
1462
- webPageMap: { [tabId]: pageData },
1463
- tabOrder: [tabId],
1886
+ webPageMap: { [activeTabId]: pageData },
1887
+ tabOrder: [activeTabId],
1464
1888
  plannerPrevSteps,
1465
1889
  llmIntegration: ctx.llmIntegration,
1466
1890
  apiMode: ctx.apiMode,
@@ -1491,12 +1915,12 @@ async function executeExtract(options) {
1491
1915
  warnings
1492
1916
  };
1493
1917
  }
1494
- const tabResponse = data?.tabResponses?.[tabId];
1918
+ const tabResponse = data?.tabResponses?.[activeTabId];
1495
1919
  if (tabResponse) {
1496
1920
  const processResult = await processActionResponse({
1497
1921
  request,
1498
1922
  response: tabResponse,
1499
- tabId,
1923
+ tabId: activeTabId,
1500
1924
  prevSteps,
1501
1925
  thought: tabResponse.thought,
1502
1926
  bridgeRpc: bridgeRpc2,
@@ -2151,6 +2575,8 @@ function resolvePath(obj, path) {
2151
2575
  }
2152
2576
 
2153
2577
  // dist/agent/toolExecutor.js
2578
+ var MAX_AGENT_CHATLOG_ENTRIES = 24;
2579
+ var MAX_AGENT_CHATLOG_MESSAGE_CHARS = 1e3;
2154
2580
  function unsupportedToolResult(toolName, message) {
2155
2581
  return {
2156
2582
  error: message,
@@ -2180,10 +2606,18 @@ function buildStructuredErrorOutput(envelope) {
2180
2606
  }
2181
2607
  function normalizeAgentLog(agentLog) {
2182
2608
  const prevSteps = Array.isArray(agentLog?.prevSteps) ? agentLog.prevSteps : [];
2609
+ const sanitizeMessage = (value) => {
2610
+ const normalized = String(value || "").replace(/\s+/g, " ").trim();
2611
+ if (!normalized)
2612
+ return "";
2613
+ if (normalized.length <= MAX_AGENT_CHATLOG_MESSAGE_CHARS)
2614
+ return normalized;
2615
+ return `${normalized.slice(0, MAX_AGENT_CHATLOG_MESSAGE_CHARS - 1)}\u2026`;
2616
+ };
2183
2617
  const chatLog = Array.isArray(agentLog?.chatLog) ? agentLog.chatLog.map((entry) => ({
2184
2618
  role: entry?.role === "user" ? "user" : "model",
2185
- message: typeof entry?.message === "string" ? entry.message : ""
2186
- })).filter((entry) => !!entry.message) : [];
2619
+ message: typeof entry?.message === "string" ? sanitizeMessage(entry.message) : ""
2620
+ })).filter((entry) => !!entry.message).slice(-MAX_AGENT_CHATLOG_ENTRIES) : [];
2187
2621
  return { prevSteps, chatLog };
2188
2622
  }
2189
2623
  async function executeToolFromPlan(context) {
@@ -2192,7 +2626,9 @@ async function executeToolFromPlan(context) {
2192
2626
  if (!effectiveCtx) {
2193
2627
  return { error: "Agent context unavailable" };
2194
2628
  }
2195
- const tabOrder = tabs?.length ? tabs.map((t) => t.id) : [1];
2629
+ const fallbackTabs = Array.isArray(tabs) && tabs.length ? tabs : [{ id: 1 }];
2630
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
2631
+ const tabOrder = resolvedTabs.tabOrder.length ? resolvedTabs.tabOrder : fallbackTabs.map((tab) => tab.id);
2196
2632
  const effectiveAgentLog = normalizeAgentLog(agentLog);
2197
2633
  try {
2198
2634
  switch (toolName) {
@@ -2999,18 +3435,50 @@ async function executeCustomToolGenerator({ userInput, plannerPrevSteps, files,
2999
3435
 
3000
3436
  // dist/agent/plannerAgent.js
3001
3437
  var MAX_PLANNER_DEPTH = 15;
3438
+ var MAX_CHATLOG_ENTRIES = 24;
3439
+ var MAX_CHATLOG_MESSAGE_CHARS = 1e3;
3440
+ function normalizeChatLog(entries) {
3441
+ if (!Array.isArray(entries) || !entries.length)
3442
+ return [];
3443
+ return entries.map((entry) => ({
3444
+ role: entry?.role === "user" ? "user" : "model",
3445
+ message: String(entry?.message || "").replace(/\s+/g, " ").trim()
3446
+ })).filter((entry) => !!entry.message).map((entry) => ({
3447
+ role: entry.role,
3448
+ message: entry.message.length > MAX_CHATLOG_MESSAGE_CHARS ? `${entry.message.slice(0, MAX_CHATLOG_MESSAGE_CHARS - 1)}\u2026` : entry.message
3449
+ })).slice(-MAX_CHATLOG_ENTRIES);
3450
+ }
3002
3451
  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);
3452
+ const { userInput, tabs, previousMessages = [], trajectoryId: trajectoryId2, previousSteps = [], files, continuePlanning = false, recordingContext, driveAuthToken, agentLog, lastToolPreviousSteps, ctx, bridgeRpc: bridgeRpc2, functionDeclarations } = options;
3453
+ const fallbackTabs = Array.isArray(tabs) && tabs.length ? tabs : [{ id: 1 }];
3454
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc2, fallbackTabs);
3455
+ const tabOrder = resolvedTabs.tabOrder.length ? resolvedTabs.tabOrder : fallbackTabs.map((tab) => tab.id);
3456
+ const activeTabId = resolvedTabs.activeTabId;
3457
+ const tabMetaById = resolvedTabs.tabMetaById;
3005
3458
  const webPageMap = {};
3006
- for (const tab of tabs) {
3459
+ const loadPageData = async (tabId, options2) => {
3007
3460
  try {
3008
- webPageMap[tab.id] = await ctx.getPageData(tab.id, { onlyTextContent: false });
3461
+ return await ctx.getPageData(tabId, {
3462
+ onlyTextContent: false,
3463
+ ...options2?.allowExternalFetch ? { __roverAllowExternalFetch: true } : {}
3464
+ });
3009
3465
  } catch {
3010
- webPageMap[tab.id] = { url: tab.url || "", title: tab.title || "", content: "", contentType: "text/html" };
3466
+ const tab = tabMetaById[tabId];
3467
+ return {
3468
+ url: tab?.url || "",
3469
+ title: tab?.title || (tab?.external ? "External Tab (Inaccessible)" : ""),
3470
+ content: "",
3471
+ contentType: "text/html"
3472
+ };
3011
3473
  }
3474
+ };
3475
+ webPageMap[activeTabId] = await loadPageData(activeTabId, { allowExternalFetch: true });
3476
+ const backgroundTabIds = tabOrder.filter((tabId) => tabId !== activeTabId);
3477
+ const backgroundResults = await Promise.all(backgroundTabIds.map(async (tabId) => ({ tabId, pageData: await loadPageData(tabId) })));
3478
+ for (const { tabId, pageData } of backgroundResults) {
3479
+ webPageMap[tabId] = pageData;
3012
3480
  }
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 }));
3481
+ 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
3482
  const request = {
3015
3483
  siteId: ctx.siteId,
3016
3484
  userInput,
@@ -3687,7 +4155,98 @@ var activeAbortController = null;
3687
4155
  var lastStatusKey = "";
3688
4156
  var seenStatusKeys = /* @__PURE__ */ new Set();
3689
4157
  var completedRunIds = /* @__PURE__ */ new Set();
4158
+ var completedRunOutcomes = /* @__PURE__ */ new Map();
3690
4159
  var RPC_TIMEOUT_MS = 3e4;
4160
+ var DETACHED_EXTERNAL_TAB_MAX_AGE_MS = 9e4;
4161
+ var MAX_CHATLOG_ENTRIES2 = 24;
4162
+ var MAX_CHATLOG_MESSAGE_CHARS2 = 1e3;
4163
+ function resolveAgentName(config2) {
4164
+ const raw = String(config2?.ui?.agent?.name || "").trim();
4165
+ if (!raw)
4166
+ return "Rover";
4167
+ return raw.slice(0, 64);
4168
+ }
4169
+ function hostFromUrl2(url) {
4170
+ const raw = String(url || "").trim();
4171
+ if (!raw)
4172
+ return void 0;
4173
+ try {
4174
+ return new URL(raw).hostname.toLowerCase();
4175
+ } catch {
4176
+ return void 0;
4177
+ }
4178
+ }
4179
+ function extractWebToolsConfig(config2) {
4180
+ if (!config2?.tools || Array.isArray(config2.tools))
4181
+ return void 0;
4182
+ return config2.tools.web;
4183
+ }
4184
+ function resolveRuntimeExternalNavigationPolicy(config2) {
4185
+ if (!config2)
4186
+ return void 0;
4187
+ if (config2.externalNavigationPolicy === "open_new_tab_notice" || config2.externalNavigationPolicy === "block" || config2.externalNavigationPolicy === "allow") {
4188
+ return config2.externalNavigationPolicy;
4189
+ }
4190
+ return void 0;
4191
+ }
4192
+ function buildRoverRuntimeContext2(params) {
4193
+ const externalTabs = params.tabs.map((tab, index) => {
4194
+ if (!tab.external && tab.accessMode !== "external_placeholder" && tab.accessMode !== "external_scraped") {
4195
+ return void 0;
4196
+ }
4197
+ const accessMode = tab.accessMode === "external_scraped" ? "external_scraped" : "external_placeholder";
4198
+ return {
4199
+ tabId: index,
4200
+ host: hostFromUrl2(tab.url),
4201
+ title: String(tab.title || "").trim() || void 0,
4202
+ accessMode,
4203
+ reason: String(tab.inaccessibleReason || "").trim() || void 0
4204
+ };
4205
+ }).filter((tab) => !!tab).slice(0, 8);
4206
+ return {
4207
+ mode: "rover_embed",
4208
+ agentName: params.agentName,
4209
+ externalNavigationPolicy: params.externalNavigationPolicy,
4210
+ tabIdContract: "tree_index_mapped_by_tab_order",
4211
+ ...externalTabs.length ? { externalTabs } : {}
4212
+ };
4213
+ }
4214
+ function mergeWorkerTools(current, incoming) {
4215
+ if (incoming === void 0)
4216
+ return current;
4217
+ if (Array.isArray(incoming))
4218
+ return incoming;
4219
+ if (Array.isArray(current)) {
4220
+ return {
4221
+ ...incoming,
4222
+ client: incoming.client ?? current,
4223
+ web: {
4224
+ ...incoming.web || {}
4225
+ }
4226
+ };
4227
+ }
4228
+ return {
4229
+ ...current,
4230
+ ...incoming,
4231
+ client: incoming.client ?? current?.client,
4232
+ web: {
4233
+ ...current?.web || {},
4234
+ ...incoming.web || {}
4235
+ }
4236
+ };
4237
+ }
4238
+ function mergeWorkerUi(current, incoming) {
4239
+ if (!incoming)
4240
+ return current;
4241
+ return {
4242
+ ...current,
4243
+ ...incoming,
4244
+ agent: {
4245
+ ...current?.agent || {},
4246
+ ...incoming.agent || {}
4247
+ }
4248
+ };
4249
+ }
3691
4250
  function createRpcClient(port) {
3692
4251
  const pending = /* @__PURE__ */ new Map();
3693
4252
  port.onmessage = (ev) => {
@@ -3725,16 +4284,18 @@ async function getCurrentTab() {
3725
4284
  return {
3726
4285
  id: Number(tabContext.logicalTabId || tabContext.id || 1),
3727
4286
  url: tabContext.url,
3728
- title: tabContext.title
4287
+ title: tabContext.title,
4288
+ external: false,
4289
+ accessMode: "live_dom"
3729
4290
  };
3730
4291
  }
3731
4292
  } catch {
3732
4293
  }
3733
4294
  try {
3734
4295
  const pageData = await bridgeRpc("getPageData");
3735
- return { id: 1, url: pageData?.url, title: pageData?.title };
4296
+ return { id: 1, url: pageData?.url, title: pageData?.title, external: false, accessMode: "live_dom" };
3736
4297
  } catch {
3737
- return { id: 1 };
4298
+ return { id: 1, external: false, accessMode: "live_dom" };
3738
4299
  }
3739
4300
  }
3740
4301
  async function getKnownTabs() {
@@ -3743,11 +4304,32 @@ async function getKnownTabs() {
3743
4304
  try {
3744
4305
  const listed = await bridgeRpc("listSessionTabs");
3745
4306
  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);
4307
+ const nowMs = Date.now();
4308
+ const mapped = listed.map((tab) => {
4309
+ const id = Number(tab?.logicalTabId || tab?.id || 0);
4310
+ const external = !!tab?.external;
4311
+ return {
4312
+ id,
4313
+ runtimeId: typeof tab?.runtimeId === "string" ? tab.runtimeId : void 0,
4314
+ updatedAt: Number(tab?.updatedAt) || 0,
4315
+ url: typeof tab?.url === "string" ? tab.url : void 0,
4316
+ title: typeof tab?.title === "string" ? tab.title : void 0,
4317
+ external,
4318
+ accessMode: tab?.accessMode === "external_scraped" || tab?.accessMode === "external_placeholder" ? tab.accessMode : external ? "external_placeholder" : "live_dom",
4319
+ inaccessibleReason: typeof tab?.inaccessibleReason === "string" ? tab.inaccessibleReason : void 0
4320
+ };
4321
+ }).filter((tab) => Number.isFinite(tab.id) && tab.id > 0).filter((tab) => {
4322
+ if (!tab.external || tab.runtimeId)
4323
+ return true;
4324
+ return nowMs - (tab.updatedAt || 0) <= DETACHED_EXTERNAL_TAB_MAX_AGE_MS;
4325
+ }).map((tab) => ({
4326
+ id: tab.id,
4327
+ url: tab.url,
4328
+ title: tab.title,
4329
+ external: tab.external,
4330
+ accessMode: tab.accessMode,
4331
+ inaccessibleReason: tab.inaccessibleReason
4332
+ }));
3751
4333
  if (mapped.length)
3752
4334
  return mapped;
3753
4335
  }
@@ -4112,19 +4694,46 @@ function maybePostNavigationGuardrailFromToolResult(toolResult) {
4112
4694
  });
4113
4695
  }
4114
4696
  function buildChatLogFromHistory(input, currentUserInput) {
4697
+ const sanitizeChatText = (raw, role) => {
4698
+ let text = String(raw || "").replace(/\s+/g, " ").trim();
4699
+ if (!text)
4700
+ return "";
4701
+ if (role === "assistant") {
4702
+ if (/^\w[\w_]*:\s*\{/.test(text) || /^\[error\]/i.test(text) || /"success":\s*false/.test(text)) {
4703
+ const firstSentence = text.split(/(?<=\.)\s+/)[0] || text;
4704
+ text = firstSentence.trim();
4705
+ }
4706
+ }
4707
+ if (text.length > MAX_CHATLOG_MESSAGE_CHARS2) {
4708
+ text = `${text.slice(0, MAX_CHATLOG_MESSAGE_CHARS2 - 1)}\u2026`;
4709
+ }
4710
+ return text;
4711
+ };
4712
+ const normalizedCurrentUserInput = currentUserInput ? sanitizeChatText(currentUserInput, "user") : "";
4115
4713
  const entries = input.filter((message) => message.role === "user" || message.role === "assistant").map((message) => ({
4116
4714
  role: message.role,
4117
- content: String(message.content || "")
4118
- }));
4119
- if (currentUserInput) {
4715
+ content: sanitizeChatText(String(message.content || ""), message.role)
4716
+ })).filter((message) => !!message.content);
4717
+ if (normalizedCurrentUserInput) {
4120
4718
  for (let i = entries.length - 1; i >= 0; i -= 1) {
4121
- if (entries[i].role === "user" && entries[i].content === currentUserInput) {
4719
+ if (entries[i].role === "user" && entries[i].content === normalizedCurrentUserInput) {
4122
4720
  entries.splice(i, 1);
4123
4721
  break;
4124
4722
  }
4125
4723
  }
4126
4724
  }
4127
- return entries.slice(-40).map((message) => ({
4725
+ const deduped = [];
4726
+ for (const entry of entries) {
4727
+ const previous = deduped[deduped.length - 1];
4728
+ if (previous && previous.role === entry.role && previous.content === entry.content)
4729
+ continue;
4730
+ deduped.push(entry);
4731
+ }
4732
+ const tail = deduped.slice(-MAX_CHATLOG_ENTRIES2);
4733
+ while (tail.length > 1 && tail[0]?.role !== "user") {
4734
+ tail.shift();
4735
+ }
4736
+ return tail.map((message) => ({
4128
4737
  role: message.role === "user" ? "user" : "model",
4129
4738
  message: message.content
4130
4739
  }));
@@ -4222,6 +4831,9 @@ async function waitForNewTabReady(logicalTabId, timeoutMs = 1e4) {
4222
4831
  const tabs = await bridgeRpc("listSessionTabs");
4223
4832
  if (Array.isArray(tabs)) {
4224
4833
  const target = tabs.find((t) => Number(t?.logicalTabId) === logicalTabId);
4834
+ if (target?.external) {
4835
+ return true;
4836
+ }
4225
4837
  if (target?.runtimeId) {
4226
4838
  await new Promise((resolve) => setTimeout(resolve, 1e3));
4227
4839
  return true;
@@ -4242,6 +4854,81 @@ function detectOpenedTabFromToolResult(result) {
4242
4854
  }
4243
4855
  return void 0;
4244
4856
  }
4857
+ function deriveDirectToolRunOutcome(result) {
4858
+ if (!result || typeof result !== "object") {
4859
+ return { taskComplete: false };
4860
+ }
4861
+ const topLevelStatus = String(result.status || "").trim().toLowerCase();
4862
+ if (topLevelStatus === "failure" || topLevelStatus === "failed" || topLevelStatus === "error") {
4863
+ return { taskComplete: false };
4864
+ }
4865
+ if (topLevelStatus === "waiting_input" || topLevelStatus === "needs_input" || topLevelStatus === "pending_user_input") {
4866
+ return { taskComplete: false, needsUserInput: true };
4867
+ }
4868
+ if (result.error) {
4869
+ return { taskComplete: false };
4870
+ }
4871
+ const output = result.output;
4872
+ if (output && typeof output === "object") {
4873
+ if (Array.isArray(output)) {
4874
+ return { taskComplete: true };
4875
+ }
4876
+ if (output.needsUserInput === true || output.waitingForUserInput === true) {
4877
+ return { taskComplete: false, needsUserInput: true };
4878
+ }
4879
+ if (Array.isArray(output.questions) && output.questions.length > 0) {
4880
+ return { taskComplete: false, needsUserInput: true };
4881
+ }
4882
+ if (output.error) {
4883
+ return { taskComplete: false };
4884
+ }
4885
+ if (typeof output.taskComplete === "boolean") {
4886
+ return { taskComplete: !!output.taskComplete };
4887
+ }
4888
+ const taskStatus = String(output.taskStatus || output.status || "").toLowerCase();
4889
+ if (taskStatus) {
4890
+ if (taskStatus === "waiting_input" || taskStatus === "needs_input" || taskStatus === "pending_user_input") {
4891
+ return { taskComplete: false, needsUserInput: true };
4892
+ }
4893
+ if (taskStatus === "running" || taskStatus === "in_progress" || taskStatus === "pending") {
4894
+ return { taskComplete: false };
4895
+ }
4896
+ if (taskStatus === "completed" || taskStatus === "complete" || taskStatus === "done" || taskStatus === "success") {
4897
+ return { taskComplete: true };
4898
+ }
4899
+ if (taskStatus === "failure" || taskStatus === "failed" || taskStatus === "error") {
4900
+ return { taskComplete: false };
4901
+ }
4902
+ }
4903
+ if (output.success === false) {
4904
+ return { taskComplete: false };
4905
+ }
4906
+ if (String(output.status || "").trim().toLowerCase() === "failure") {
4907
+ return { taskComplete: false };
4908
+ }
4909
+ }
4910
+ if (output != null) {
4911
+ return { taskComplete: true };
4912
+ }
4913
+ return { taskComplete: false };
4914
+ }
4915
+ function normalizeRunOutcome(outcome) {
4916
+ if (!outcome || typeof outcome !== "object") {
4917
+ return { taskComplete: false, needsUserInput: false };
4918
+ }
4919
+ const needsUserInput = outcome.needsUserInput === true;
4920
+ const taskComplete = outcome.taskComplete === true && !needsUserInput;
4921
+ return {
4922
+ route: outcome.route,
4923
+ taskComplete,
4924
+ needsUserInput
4925
+ };
4926
+ }
4927
+ function clearTaskScopedContextAfterCompletion() {
4928
+ history.length = 0;
4929
+ plannerHistory = [];
4930
+ agentPrevSteps = [];
4931
+ }
4245
4932
  async function maybeWaitForNewTab(result) {
4246
4933
  const logicalTabId = detectOpenedTabFromToolResult(result);
4247
4934
  if (logicalTabId) {
@@ -4261,10 +4948,50 @@ async function handleUserMessage(text, options) {
4261
4948
  postStateSnapshot();
4262
4949
  }
4263
4950
  const tabs = await getKnownTabs();
4951
+ const fallbackTabs = tabs.length > 0 ? tabs : [
4952
+ {
4953
+ id: 1,
4954
+ external: false,
4955
+ accessMode: "live_dom"
4956
+ }
4957
+ ];
4958
+ const resolvedTabs = await resolveRuntimeTabs(bridgeRpc, fallbackTabs);
4959
+ const tabsById = new Map(tabs.map((tab) => [tab.id, tab]));
4960
+ const orderedTabs = resolvedTabs.tabOrder.map((tabId) => {
4961
+ const knownTab = tabsById.get(tabId);
4962
+ if (knownTab)
4963
+ return knownTab;
4964
+ const tabMeta = resolvedTabs.tabMetaById[tabId];
4965
+ if (!tabMeta)
4966
+ return { id: tabId };
4967
+ const external = !!tabMeta.external;
4968
+ return {
4969
+ id: tabId,
4970
+ url: tabMeta.url,
4971
+ title: tabMeta.title,
4972
+ external,
4973
+ accessMode: tabMeta.accessMode || (external ? "external_placeholder" : "live_dom"),
4974
+ inaccessibleReason: tabMeta.inaccessibleReason
4975
+ };
4976
+ }).filter((tab) => Number.isFinite(tab.id) && tab.id > 0);
4977
+ const tabsForRun = orderedTabs.length > 0 ? orderedTabs : fallbackTabs;
4264
4978
  if (!tabularStore) {
4265
4979
  tabularStore = new TabularStore(`rover-${trajectoryId}`);
4266
4980
  }
4267
- const ctx = createAgentContext({ ...config, signal: activeAbortController?.signal }, bridgeRpc, tabularStore);
4981
+ const agentName = resolveAgentName(config);
4982
+ const runtimeContext = buildRoverRuntimeContext2({
4983
+ tabs: tabsForRun,
4984
+ agentName,
4985
+ externalNavigationPolicy: resolveRuntimeExternalNavigationPolicy(config)
4986
+ });
4987
+ const ctx = createAgentContext({
4988
+ ...config,
4989
+ signal: activeAbortController?.signal,
4990
+ runtimeContext,
4991
+ tools: {
4992
+ web: extractWebToolsConfig(config)
4993
+ }
4994
+ }, bridgeRpc, tabularStore);
4268
4995
  const currentRunId = activeRun?.runId;
4269
4996
  ctx.isCancelled = () => cancelledRunId === currentRunId;
4270
4997
  const functionDeclarations = dedupeFunctionDeclarations(removePlannerNameCollisions(toolRegistry.getFunctionDeclarations()));
@@ -4278,7 +5005,7 @@ async function handleUserMessage(text, options) {
4278
5005
  postStateSnapshot();
4279
5006
  };
4280
5007
  const result = await handleSendMessageWithFunctions(text, {
4281
- tabs,
5008
+ tabs: tabsForRun,
4282
5009
  previousMessages: history,
4283
5010
  trajectoryId,
4284
5011
  files: [],
@@ -4308,7 +5035,7 @@ async function handleUserMessage(text, options) {
4308
5035
  history.push({ role: "assistant", content: errorMsg });
4309
5036
  postStatus("Execution failed", errorPayload.error.message, "complete");
4310
5037
  postStateSnapshot();
4311
- return result.route;
5038
+ return { route: result.route, taskComplete: false };
4312
5039
  }
4313
5040
  if (result.executedFunctions?.length) {
4314
5041
  for (const fn of result.executedFunctions) {
@@ -4323,7 +5050,7 @@ async function handleUserMessage(text, options) {
4323
5050
  history.push({ role: "assistant", content: msg });
4324
5051
  postStatus("Execution completed", "Function calls finished", "complete");
4325
5052
  postStateSnapshot();
4326
- return result.route;
5053
+ return { route: result.route, taskComplete: true };
4327
5054
  }
4328
5055
  if (result.directToolResult) {
4329
5056
  await maybeWaitForNewTab(result.directToolResult);
@@ -4341,7 +5068,12 @@ async function handleUserMessage(text, options) {
4341
5068
  history.push({ role: "assistant", content: msg });
4342
5069
  postStatus("Execution completed", structuredError?.error.message, "complete");
4343
5070
  postStateSnapshot();
4344
- return result.route;
5071
+ const outcome = deriveDirectToolRunOutcome(result.directToolResult);
5072
+ return {
5073
+ route: result.route,
5074
+ taskComplete: outcome.taskComplete,
5075
+ needsUserInput: outcome.needsUserInput
5076
+ };
4345
5077
  }
4346
5078
  if (result.plannerResponse) {
4347
5079
  postStatus("Verifying planner output", void 0, "verify");
@@ -4360,7 +5092,7 @@ ${qText}`;
4360
5092
  history.push({ role: "assistant", content: msg2 });
4361
5093
  postStatus("Planner needs user input", void 0, "verify");
4362
5094
  postStateSnapshot();
4363
- return result.route;
5095
+ return { route: result.route, taskComplete: false, needsUserInput: true };
4364
5096
  }
4365
5097
  const toolResults = result.plannerResponse.toolResults || [];
4366
5098
  for (const toolResult of toolResults) {
@@ -4377,18 +5109,30 @@ ${qText}`;
4377
5109
  history.push({ role: "assistant", content: msg });
4378
5110
  postStatus("Planner execution completed", response.overallThought, "complete");
4379
5111
  postStateSnapshot();
4380
- return result.route;
5112
+ return {
5113
+ route: result.route,
5114
+ taskComplete: !!response.taskComplete && !responseError,
5115
+ needsUserInput: false
5116
+ };
4381
5117
  }
4382
5118
  postAssistantMessage("Done.");
4383
5119
  history.push({ role: "assistant", content: "Done." });
4384
5120
  postStatus("Completed", void 0, "complete");
4385
5121
  postStateSnapshot();
4386
- return result.route;
5122
+ return { route: result.route, taskComplete: true };
4387
5123
  }
4388
5124
  async function runUserMessage(text, meta) {
4389
5125
  const runId = meta?.runId || crypto.randomUUID();
4390
5126
  if (completedRunIds.has(runId)) {
4391
- self.postMessage({ type: "run_completed", runId, ok: true, route: void 0 });
5127
+ const cachedOutcome = normalizeRunOutcome(completedRunOutcomes.get(runId));
5128
+ self.postMessage({
5129
+ type: "run_completed",
5130
+ runId,
5131
+ ok: true,
5132
+ route: cachedOutcome.route,
5133
+ taskComplete: cachedOutcome.taskComplete,
5134
+ needsUserInput: cachedOutcome.needsUserInput
5135
+ });
4392
5136
  return;
4393
5137
  }
4394
5138
  if (activeRun && activeRun.runId === runId) {
@@ -4402,13 +5146,39 @@ async function runUserMessage(text, meta) {
4402
5146
  self.postMessage({ type: "run_started", runId, text, resume });
4403
5147
  postStateSnapshot();
4404
5148
  try {
4405
- const route = await handleUserMessage(text, { resume });
4406
- self.postMessage({ type: "run_completed", runId, ok: true, route });
5149
+ const outcome = normalizeRunOutcome(await handleUserMessage(text, { resume }));
5150
+ completedRunOutcomes.set(runId, outcome);
5151
+ self.postMessage({
5152
+ type: "run_completed",
5153
+ runId,
5154
+ ok: true,
5155
+ route: outcome.route,
5156
+ taskComplete: outcome.taskComplete,
5157
+ needsUserInput: outcome.needsUserInput
5158
+ });
5159
+ if (outcome.taskComplete && !outcome.needsUserInput) {
5160
+ clearTaskScopedContextAfterCompletion();
5161
+ postStateSnapshot();
5162
+ }
4407
5163
  } catch (error) {
4408
5164
  if (error?.name === "AbortError") {
4409
- self.postMessage({ type: "run_completed", runId, ok: false, error: "Run cancelled" });
5165
+ self.postMessage({
5166
+ type: "run_completed",
5167
+ runId,
5168
+ ok: false,
5169
+ error: "Run cancelled",
5170
+ taskComplete: false,
5171
+ needsUserInput: false
5172
+ });
4410
5173
  } else {
4411
- self.postMessage({ type: "run_completed", runId, ok: false, error: error?.message || String(error) });
5174
+ self.postMessage({
5175
+ type: "run_completed",
5176
+ runId,
5177
+ ok: false,
5178
+ error: error?.message || String(error),
5179
+ taskComplete: false,
5180
+ needsUserInput: false
5181
+ });
4412
5182
  throw error;
4413
5183
  }
4414
5184
  } finally {
@@ -4417,8 +5187,10 @@ async function runUserMessage(text, meta) {
4417
5187
  completedRunIds.add(runId);
4418
5188
  if (completedRunIds.size > 50) {
4419
5189
  const oldest = completedRunIds.values().next().value;
4420
- if (oldest)
5190
+ if (oldest) {
4421
5191
  completedRunIds.delete(oldest);
5192
+ completedRunOutcomes.delete(oldest);
5193
+ }
4422
5194
  }
4423
5195
  postStateSnapshot();
4424
5196
  }
@@ -4457,7 +5229,12 @@ self.onmessage = async (ev) => {
4457
5229
  if (!config)
4458
5230
  throw new Error("Worker not initialized");
4459
5231
  const partial = data.config || {};
4460
- config = { ...config, ...partial };
5232
+ config = {
5233
+ ...config,
5234
+ ...partial,
5235
+ ui: mergeWorkerUi(config.ui, partial.ui),
5236
+ tools: mergeWorkerTools(config.tools, partial.tools)
5237
+ };
4461
5238
  if (typeof partial.sessionId === "string" && partial.sessionId.trim() && partial.sessionId.trim() !== trajectoryId) {
4462
5239
  trajectoryId = partial.sessionId.trim();
4463
5240
  plannerHistory = [];