@xopcai/xopc 0.0.274 → 0.0.276

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.
Files changed (137) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +215 -0
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +215 -0
  3. package/dist/browser-ext/dist/assets/{auth-O8RiOF3l.js → auth-ClQTK565.js} +97 -38
  4. package/dist/browser-ext/dist/assets/{sidepanel-C86O7q5Y.css → sidepanel-CeGqObOa.css} +41 -20
  5. package/dist/browser-ext/dist/background.js +36 -4
  6. package/dist/browser-ext/dist/sidepanel.html +2 -2
  7. package/dist/browser-ext/dist/sidepanel.js +416 -299
  8. package/dist/browser-ext/manifest.json +6 -4
  9. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  10. package/dist/extensions/telegram/xopc.extension.json +1 -1
  11. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BckaANxj.js → agent-avatar-dicebear-sj_1IC5H.js} +1 -1
  12. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-B8eyC6K1.js → agent-defaults-settings-panel-B5MRLEy8.js} +1 -1
  13. package/dist/gateway/static/root/assets/{agents-CSWj_yo1.js → agents-CIzev0Qj.js} +1 -1
  14. package/dist/gateway/static/root/assets/{app-management-settings-panel-DY-Zbgmt.js → app-management-settings-panel-DY3Vwxjq.js} +1 -1
  15. package/dist/gateway/static/root/assets/{appearance-settings-CoJR7ZRl.js → appearance-settings-BeXvF-dZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/apps-page-Ch_9Ffgk.js +1 -0
  17. package/dist/gateway/static/root/assets/{archive-plugin-BwAlRT53.js → archive-plugin-BSgaIIGE.js} +1 -1
  18. package/dist/gateway/static/root/assets/{attachment-load-R835uxgL.js → attachment-load-CGNt02hP.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-page-BEZy0P_u.js → automations-page-DoafJiVA.js} +1 -1
  20. package/dist/gateway/static/root/assets/automations-workspace-MACr99IO.js +6 -0
  21. package/dist/gateway/static/root/assets/{binary-plugins-CESFx3yb.js → binary-plugins-BsAL26Aq.js} +1 -1
  22. package/dist/gateway/static/root/assets/{block-editor-DDJcf2wR.js → block-editor-BxB7m8aI.js} +3 -3
  23. package/dist/gateway/static/root/assets/{browser-automations-page-BH4lSfr5.js → browser-automations-page-BNsO-Ip2.js} +2 -2
  24. package/dist/gateway/static/root/assets/browser-settings-page-Nvdoi0B1.js +8 -0
  25. package/dist/gateway/static/root/assets/capabilities-settings-panel-BluLJq5I.js +8 -0
  26. package/dist/gateway/static/root/assets/channels-settings-DYxxhcjb.js +1 -0
  27. package/dist/gateway/static/root/assets/{chat-terminal-dock-D8U97O95.js → chat-terminal-dock-ZCFwco1W.js} +2 -2
  28. package/dist/gateway/static/root/assets/connectors-page-Bpcp8UyE.js +2 -0
  29. package/dist/gateway/static/root/assets/{date-picker-7sDSMswL.js → date-picker-CpxFfgpg.js} +1 -1
  30. package/dist/gateway/static/root/assets/{dependency-picker-DMIC86ZW.js → dependency-picker-eCIGAfHa.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-BtdiY5jK.js → desktop-pet-_MSZvIDO.js} +1 -1
  32. package/dist/gateway/static/root/assets/{desktop-pet-settings-D_B8azDH.js → desktop-pet-settings-feBnJWkV.js} +1 -1
  33. package/dist/gateway/static/root/assets/device-pairing-wizard-D_fB0fml.js +1 -0
  34. package/dist/gateway/static/root/assets/{extension-debug-page-CoFKb01d.js → extension-debug-page-CotBlxJ-.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-page-CEkNsCAY.js → extension-page-CFBmQ7U3.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-settings-page-Bi2R8P5j.js → extension-settings-page-Cd1lfh8z.js} +1 -1
  37. package/dist/gateway/static/root/assets/gateway-settings-BH35C8RI.js +1 -0
  38. package/dist/gateway/static/root/assets/{heartbeat-settings-DQtelFiU.js → heartbeat-settings-DRG0u1ON.js} +1 -1
  39. package/dist/gateway/static/root/assets/home-page-BlZk-aD4.js +1 -0
  40. package/dist/gateway/static/root/assets/{index-SxqsTD4i.js → index-CfQor-TM.js} +34 -34
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BuYmZS1K.js → keyboard-shortcuts-settings-BLHsdFIQ.js} +1 -1
  42. package/dist/gateway/static/root/assets/local-app-workbench-page-DJd71FlC.js +2 -0
  43. package/dist/gateway/static/root/assets/{local-apps-page-CExnZyGZ.js → local-apps-page-BjAj1xhq.js} +1 -1
  44. package/dist/gateway/static/root/assets/{logs-page-CA5ArMoN.js → logs-page-5MWeZ6Ae.js} +2 -2
  45. package/dist/gateway/static/root/assets/{management-settings-1UiJPrkX.js → management-settings-CPHxDaOw.js} +1 -1
  46. package/dist/gateway/static/root/assets/{media-plugins-nNFOk9HL.js → media-plugins-BUOzcXCL.js} +1 -1
  47. package/dist/gateway/static/root/assets/note-detail-panel-Bch98ut3.js +5 -0
  48. package/dist/gateway/static/root/assets/notes-page-CWO5dQKN.js +8 -0
  49. package/dist/gateway/static/root/assets/{page-tabs-CRombigq.js → page-tabs-BgEYFhqS.js} +1 -1
  50. package/dist/gateway/static/root/assets/{preview-open-alternatives-Bcuj43lk.js → preview-open-alternatives-C-pQIx6W.js} +1 -1
  51. package/dist/gateway/static/root/assets/{proactive-card-Cb3Xeoyx.js → proactive-card-DfjMVJUW.js} +1 -1
  52. package/dist/gateway/static/root/assets/{proactive-page-TbF9KVpm.js → proactive-page-BGVcVsbd.js} +1 -1
  53. package/dist/gateway/static/root/assets/project-detail-page-DZFVirjV.js +1 -0
  54. package/dist/gateway/static/root/assets/projects-page-B2g6inq-.js +1 -0
  55. package/dist/gateway/static/root/assets/remote-access-hub-CbkUOkQY.js +1 -0
  56. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BudlvBu-.js → runtime-tools-settings-panel-DkAjFJ-9.js} +2 -2
  57. package/dist/gateway/static/root/assets/{schema-form-Ch3MEX4h.js → schema-form-BpDcuSsl.js} +1 -1
  58. package/dist/gateway/static/root/assets/sessions-page--7-w-0WS.js +1 -0
  59. package/dist/gateway/static/root/assets/settings-advanced-gate-BvZrH_sp.js +1 -0
  60. package/dist/gateway/static/root/assets/{settings-form-section-CIA1AA-A.js → settings-form-section-RElRijJw.js} +1 -1
  61. package/dist/gateway/static/root/assets/{settings-page-B0Khoduh.js → settings-page-CaixunoA.js} +1 -1
  62. package/dist/gateway/static/root/assets/setup-status-panel-Ul06Y5YX.js +1 -0
  63. package/dist/gateway/static/root/assets/{share-preview-page-Cj9XWg-X.js → share-preview-page-CC-bFOTf.js} +1 -1
  64. package/dist/gateway/static/root/assets/shares-settings-DwV2Wh7S.js +1 -0
  65. package/dist/gateway/static/root/assets/{skill-reload-api-BM4Ezjbx.js → skill-reload-api-CcOtxf53.js} +1 -1
  66. package/dist/gateway/static/root/assets/{skills-page-Dyh1kf9t.js → skills-page-DmRvRxtY.js} +1 -1
  67. package/dist/gateway/static/root/assets/{skills-page.utils-DsQZkMti.js → skills-page.utils-CT0-daZD.js} +1 -1
  68. package/dist/gateway/static/root/assets/{system-settings-panel-BSho_Tpk.js → system-settings-panel-DRIJ5mXq.js} +1 -1
  69. package/dist/gateway/static/root/assets/task-detail-page-BQjLHqSk.js +1 -0
  70. package/dist/gateway/static/root/assets/{tunnel-api-4DUHxSQG.js → tunnel-api-IvWcgZdD.js} +1 -1
  71. package/dist/gateway/static/root/assets/{use-autosave-BG8Yx7hj.js → use-autosave-Cq4--C1i.js} +1 -1
  72. package/dist/gateway/static/root/assets/user-model-page-hwc5-nqV.js +2 -0
  73. package/dist/gateway/static/root/assets/{voice-api-key-field-DYXwDhHq.js → voice-api-key-field-CbJ6tKGI.js} +1 -1
  74. package/dist/gateway/static/root/assets/{work-discovery-overlay-CQqjzaRo.js → work-discovery-overlay-BG2pmXL3.js} +1 -1
  75. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-C5H-Fd-U.js → workflow-run-setup-panel-vw1229Fh.js} +2 -2
  76. package/dist/gateway/static/root/assets/workflows-page-DZWpJHDr.js +6 -0
  77. package/dist/gateway/static/root/index.html +1 -1
  78. package/dist/package.js +1 -1
  79. package/dist/src/agent/child-agent-factory.d.ts +3 -0
  80. package/dist/src/agent/child-agent-factory.js +2 -1
  81. package/dist/src/agent/external-tools/endpoint-provider.js +1 -1
  82. package/dist/src/agent/tools/factory.d.ts +1 -0
  83. package/dist/src/agent/tools/factory.js +21 -4
  84. package/dist/src/agent/workflow/workflow-child-tools.js +2 -0
  85. package/dist/src/browser/providers/browser-ext-install.d.ts +1 -1
  86. package/dist/src/browser/providers/browser-ext-install.js +86 -14
  87. package/dist/src/browser/providers/realtime-extension.d.ts +4 -1
  88. package/dist/src/browser/providers/realtime-extension.js +26 -10
  89. package/dist/src/browser/readiness.d.ts +4 -1
  90. package/dist/src/browser/readiness.js +11 -5
  91. package/dist/src/cli/commands/channels.js +1 -1
  92. package/dist/src/cli/commands/connectors.js +1 -1
  93. package/dist/src/cli/commands/extension-marketplace.js +2 -2
  94. package/dist/src/cli/commands/models.js +1 -1
  95. package/dist/src/cli/commands/onboard.js +1 -1
  96. package/dist/src/extensions/sdk/index.js +1 -1
  97. package/dist/src/extensions/update.js +1 -1
  98. package/dist/src/gateway/gateway-workflow-host.types.d.ts +2 -0
  99. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  100. package/dist/src/gateway/hono/routes/browser.js +24 -11
  101. package/dist/src/gateway/hono/routes/endpoint-tools.js +9 -0
  102. package/dist/src/gateway/hono/routes/models.js +6 -0
  103. package/dist/src/gateway/service/config-coordinator.js +1 -1
  104. package/dist/src/gateway/service.js +3 -3
  105. package/dist/src/infra/update-runner.js +1 -1
  106. package/dist/src/providers/model-catalog-persistence.js +4 -0
  107. package/dist/src/providers/model-catalog-types.d.ts +1 -0
  108. package/dist/src/providers/xopc-cloud-model-source.js +12 -1
  109. package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +1 -0
  110. package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +9 -1
  111. package/dist/src/storage/sqlite/index.d.ts +1 -1
  112. package/dist/src/storage/sqlite/index.js +2 -2
  113. package/dist/src/tui/backends/embedded-backend.js +1 -1
  114. package/dist/src/workflows/service/workflow-run-service.js +4 -1
  115. package/package.json +4 -4
  116. package/dist/gateway/static/root/assets/apps-page-CSMvkuot.js +0 -1
  117. package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +0 -6
  118. package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +0 -8
  119. package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +0 -8
  120. package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +0 -1
  121. package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +0 -2
  122. package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.js +0 -1
  123. package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +0 -1
  124. package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +0 -1
  125. package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +0 -2
  126. package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +0 -5
  127. package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +0 -8
  128. package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +0 -1
  129. package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +0 -1
  130. package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +0 -1
  131. package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +0 -1
  132. package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +0 -1
  133. package/dist/gateway/static/root/assets/setup-status-panel-COJFl4N_.js +0 -1
  134. package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +0 -1
  135. package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +0 -1
  136. package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +0 -2
  137. package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +0 -6
@@ -5782,6 +5782,62 @@ array(browserPageContextInputSchema).max(MAX_BROWSER_CONTEXTS_PER_TURN).superRef
5782
5782
  }
5783
5783
  });
5784
5784
 
5785
+ const LOCALE_STORAGE_KEY = "xopc.sidepanel.locale";
5786
+ const LOCALE_PATHS = {
5787
+ en: "_locales/en/messages.json",
5788
+ "zh-CN": "_locales/zh_CN/messages.json"
5789
+ };
5790
+ let activeLocale;
5791
+ let activeCatalog;
5792
+ function isExtensionLocale(value) {
5793
+ return value === "en" || value === "zh-CN";
5794
+ }
5795
+ function browserLocale() {
5796
+ return chrome.i18n.getUILanguage().replaceAll("_", "-").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
5797
+ }
5798
+ function messageWithSubstitutions(entry, substitutions) {
5799
+ const values = substitutions === void 0 ? [] : Array.isArray(substitutions) ? substitutions : [substitutions];
5800
+ const valueAt = (content) => content.replace(/\$(\d+)/g, (_match, index) => values[Number(index) - 1] ?? "");
5801
+ let message = entry.message;
5802
+ for (const [name, placeholder] of Object.entries(entry.placeholders ?? {})) {
5803
+ message = message.replaceAll(new RegExp(`\\$${name}\\$`, "gi"), valueAt(placeholder.content));
5804
+ }
5805
+ let sequentialIndex = 0;
5806
+ return message.replace(/%(?:(\d+)\$)?s/g, (_match, position) => {
5807
+ const index = position ? Number(position) - 1 : sequentialIndex++;
5808
+ return values[index] ?? "";
5809
+ }).replace(/\$(\d+)/g, (_match, index) => values[Number(index) - 1] ?? "");
5810
+ }
5811
+ function t(key, substitutions) {
5812
+ const entry = activeCatalog?.[key];
5813
+ return entry ? messageWithSubstitutions(entry, substitutions) : chrome.i18n.getMessage(key, substitutions) || key;
5814
+ }
5815
+ function extensionLocale() {
5816
+ return activeLocale ?? chrome.i18n.getUILanguage().replaceAll("_", "-");
5817
+ }
5818
+ async function loadExtensionLocalePreference() {
5819
+ const stored = await chrome.storage.local.get(LOCALE_STORAGE_KEY);
5820
+ const locale = isExtensionLocale(stored[LOCALE_STORAGE_KEY]) ? stored[LOCALE_STORAGE_KEY] : browserLocale();
5821
+ await applyExtensionLocale(locale);
5822
+ return locale;
5823
+ }
5824
+ async function saveExtensionLocalePreference(locale) {
5825
+ await chrome.storage.local.set({ [LOCALE_STORAGE_KEY]: locale });
5826
+ await applyExtensionLocale(locale);
5827
+ }
5828
+ async function applyExtensionLocale(locale) {
5829
+ const response = await fetch(chrome.runtime.getURL(LOCALE_PATHS[locale]));
5830
+ if (!response.ok) throw new Error(`Could not load extension locale: ${locale}`);
5831
+ activeCatalog = await response.json();
5832
+ activeLocale = locale;
5833
+ initializeDocumentLocale();
5834
+ }
5835
+ function initializeDocumentLocale() {
5836
+ document.documentElement.lang = extensionLocale();
5837
+ document.documentElement.dir = chrome.i18n.getMessage("@@bidi_dir") || "ltr";
5838
+ document.title = t("appName");
5839
+ }
5840
+
5785
5841
  const PENDING_CONTEXT_KEY = "xopc.browser.pending-context";
5786
5842
  const TAB_BINDING_PREFIX = "xopc.browser.tab-binding.";
5787
5843
  function truncateUtf8(value, maxBytes) {
@@ -5801,13 +5857,13 @@ function sanitizedUrl(raw) {
5801
5857
  try {
5802
5858
  url = new URL(raw);
5803
5859
  } catch {
5804
- throw new Error("This tab does not have a valid web address");
5860
+ throw new Error(t("errorInvalidWebAddress"));
5805
5861
  }
5806
5862
  if (url.protocol !== "http:" && url.protocol !== "https:") {
5807
- throw new Error("Chrome does not allow xopc to read this page. Open a regular http(s) page and try again.");
5863
+ throw new Error(t("errorRestrictedPage"));
5808
5864
  }
5809
5865
  if (url.hostname === "chromewebstore.google.com" || url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
5810
- throw new Error("Chrome does not allow extensions to read Chrome Web Store pages.");
5866
+ throw new Error(t("errorWebStorePage"));
5811
5867
  }
5812
5868
  url.username = "";
5813
5869
  url.password = "";
@@ -5818,14 +5874,14 @@ function siteAccessError(cause, url) {
5818
5874
  const message = cause instanceof Error ? cause.message : String(cause);
5819
5875
  const hostname = new URL(url).hostname;
5820
5876
  if (/cannot access|cannot be scripted|missing host permission|extensions gallery|chrome web store/i.test(message)) {
5821
- return new Error(`Chrome blocked access to ${hostname}. Check xopc's site access for this page and try again.`);
5877
+ return new Error(t("errorSiteAccessBlocked", hostname));
5822
5878
  }
5823
5879
  return cause instanceof Error ? cause : new Error(message);
5824
5880
  }
5825
5881
  async function runWithTabSiteAccess(tabId, operation) {
5826
5882
  const tab = await chrome.tabs.get(tabId);
5827
5883
  if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) {
5828
- throw new Error("The selected tab is no longer available");
5884
+ throw new Error(t("errorTabUnavailable"));
5829
5885
  }
5830
5886
  const url = sanitizedUrl(tab.url);
5831
5887
  const originPattern = `${new URL(url).origin}/*`;
@@ -5838,7 +5894,7 @@ async function runWithTabSiteAccess(tabId, operation) {
5838
5894
  } catch (cause) {
5839
5895
  throw siteAccessError(cause, url);
5840
5896
  }
5841
- if (!granted) throw new Error(`Allow xopc to access ${new URL(url).hostname} to continue.`);
5897
+ if (!granted) throw new Error(t("errorSiteAccessRequired", new URL(url).hostname));
5842
5898
  newlyGranted = true;
5843
5899
  }
5844
5900
  try {
@@ -5852,15 +5908,15 @@ async function sha256(value) {
5852
5908
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
5853
5909
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5854
5910
  }
5855
- function extractPage(mode) {
5911
+ function extractPage(mode, messages) {
5856
5912
  const normalize = (value) => value.replace(/\s+/g, " ").trim();
5857
5913
  const selection = normalize(window.getSelection()?.toString() ?? "");
5858
5914
  if (mode === "selection") {
5859
- if (!selection) throw new Error("Select text on the page first");
5915
+ if (!selection) throw new Error(messages.selectText);
5860
5916
  return { title: document.title, url: location.href, timeOrigin: performance.timeOrigin, selection };
5861
5917
  }
5862
5918
  const source = document.querySelector("article, main") ?? document.body;
5863
- if (!source) throw new Error("This page has no readable content");
5919
+ if (!source) throw new Error(messages.noReadableContent);
5864
5920
  const clone = source.cloneNode(true);
5865
5921
  clone.querySelectorAll([
5866
5922
  "script",
@@ -5882,23 +5938,26 @@ function extractPage(mode) {
5882
5938
  "[inert]"
5883
5939
  ].join(",")).forEach((element) => element.remove());
5884
5940
  const text = normalize(clone.innerText || clone.textContent || "");
5885
- if (!text) throw new Error("This page has no readable content");
5941
+ if (!text) throw new Error(messages.noReadableContent);
5886
5942
  return { title: document.title, url: location.href, timeOrigin: performance.timeOrigin, text };
5887
5943
  }
5888
5944
  async function captureTabPage(tabId, mode) {
5889
5945
  const tab = await chrome.tabs.get(tabId);
5890
- if (!tab.url) throw new Error("The selected tab is no longer available");
5946
+ if (!tab.url) throw new Error(t("errorTabUnavailable"));
5891
5947
  sanitizedUrl(tab.url);
5892
5948
  const result = await chrome.scripting.executeScript({
5893
5949
  target: { tabId },
5894
5950
  func: extractPage,
5895
- args: [mode]
5951
+ args: [mode, {
5952
+ selectText: t("errorSelectText"),
5953
+ noReadableContent: t("errorNoReadableContent")
5954
+ }]
5896
5955
  });
5897
5956
  const raw = result[0]?.result;
5898
- if (!raw) throw new Error("Could not read this page");
5957
+ if (!raw) throw new Error(t("errorReadPage"));
5899
5958
  const url = sanitizedUrl(raw.url);
5900
5959
  if (new URL(url).origin !== new URL(sanitizedUrl(tab.url)).origin) {
5901
- throw new Error("The page changed while xopc was reading it. Try again.");
5960
+ throw new Error(t("errorPageChangedReading"));
5902
5961
  }
5903
5962
  const selected = raw.selection ? truncateUtf8(raw.selection, MAX_BROWSER_SELECTION_BYTES) : void 0;
5904
5963
  const page = raw.text ? truncateUtf8(raw.text, MAX_BROWSER_PAGE_TEXT_BYTES) : void 0;
@@ -5927,17 +5986,17 @@ async function activeTabId() {
5927
5986
  }
5928
5987
  async function currentTabDescriptor() {
5929
5988
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
5930
- if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error("No active web page");
5989
+ if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error(t("errorNoActivePage"));
5931
5990
  return runWithTabSiteAccess(tab.id, async (accessibleTab) => {
5932
5991
  const result = await chrome.scripting.executeScript({
5933
5992
  target: { tabId: accessibleTab.id },
5934
5993
  func: () => ({ url: location.href, timeOrigin: performance.timeOrigin })
5935
5994
  });
5936
5995
  const marker = result[0]?.result;
5937
- if (!marker) throw new Error("Could not identify this page");
5996
+ if (!marker) throw new Error(t("errorIdentifyPage"));
5938
5997
  const currentUrl = sanitizedUrl(marker.url);
5939
5998
  if (new URL(currentUrl).origin !== new URL(accessibleTab.url).origin) {
5940
- throw new Error("The page changed while xopc was connecting to it. Try again.");
5999
+ throw new Error(t("errorPageChangedConnecting"));
5941
6000
  }
5942
6001
  return {
5943
6002
  tabId: String(accessibleTab.id),
@@ -5961,7 +6020,7 @@ function openDatabase() {
5961
6020
  }
5962
6021
  };
5963
6022
  request.onsuccess = () => resolve(request.result);
5964
- request.onerror = () => reject(request.error ?? new Error("Could not open the browser chat outbox"));
6023
+ request.onerror = () => reject(request.error ?? new Error(t("errorOpenOutbox")));
5965
6024
  });
5966
6025
  }
5967
6026
  function transactionResult(database, mode, createRequest) {
@@ -5972,10 +6031,10 @@ function transactionResult(database, mode, createRequest) {
5972
6031
  request.onsuccess = () => {
5973
6032
  result = request.result;
5974
6033
  };
5975
- request.onerror = () => reject(request.error ?? new Error("Browser chat outbox request failed"));
6034
+ request.onerror = () => reject(request.error ?? new Error(t("errorOutboxRequest")));
5976
6035
  transaction.oncomplete = () => resolve(result);
5977
- transaction.onerror = () => reject(transaction.error ?? new Error("Browser chat outbox transaction failed"));
5978
- transaction.onabort = () => reject(transaction.error ?? new Error("Browser chat outbox transaction was aborted"));
6036
+ transaction.onerror = () => reject(transaction.error ?? new Error(t("errorOutboxTransaction")));
6037
+ transaction.onabort = () => reject(transaction.error ?? new Error(t("errorOutboxAborted")));
5979
6038
  });
5980
6039
  }
5981
6040
  async function readBrowserOutbox(sessionKey) {
@@ -6055,7 +6114,7 @@ async function discoverLocalGateway(options) {
6055
6114
  }
6056
6115
  );
6057
6116
  if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.invitation !== "string" || typeof response.expiresAt !== "number") {
6058
- if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
6117
+ if (options?.force) throw new Error(response.error || t("errorInvalidLocalHostResponse"));
6059
6118
  return void 0;
6060
6119
  }
6061
6120
  return {
@@ -6138,12 +6197,12 @@ async function forgetIdentity() {
6138
6197
  const request = indexedDB.deleteDatabase(KEY_DATABASE);
6139
6198
  request.onsuccess = () => resolve();
6140
6199
  request.onerror = () => reject(request.error);
6141
- request.onblocked = () => reject(new Error("Browser identity is still in use"));
6200
+ request.onblocked = () => reject(new Error(t("errorIdentityInUse")));
6142
6201
  });
6143
6202
  }
6144
6203
  async function publicKeyJwk(pair) {
6145
6204
  const exported = await crypto.subtle.exportKey("jwk", pair.publicKey);
6146
- if (!exported.x || !exported.y) throw new Error("Browser identity key export failed");
6205
+ if (!exported.x || !exported.y) throw new Error(t("errorIdentityExport"));
6147
6206
  return { kty: "EC", crv: "P-256", x: exported.x, y: exported.y };
6148
6207
  }
6149
6208
  async function sign(pair, text) {
@@ -6206,7 +6265,7 @@ async function post(origin, path, body) {
6206
6265
  signal: controller.signal
6207
6266
  });
6208
6267
  } catch (cause) {
6209
- if (controller.signal.aborted) throw new Error(`Gateway did not respond within ${GATEWAY_REQUEST_TIMEOUT_MS / 1e3} seconds`);
6268
+ if (controller.signal.aborted) throw new Error(t("errorGatewayTimeout", String(GATEWAY_REQUEST_TIMEOUT_MS / 1e3)));
6210
6269
  throw cause;
6211
6270
  } finally {
6212
6271
  clearTimeout(timeout);
@@ -6214,7 +6273,7 @@ async function post(origin, path, body) {
6214
6273
  const json = await response.json().catch(() => ({}));
6215
6274
  if (!response.ok) {
6216
6275
  throw new GatewayHttpError(
6217
- json.error?.code ?? json.error?.message ?? `Gateway returned ${response.status}`,
6276
+ json.error?.code ?? json.error?.message ?? t("errorGatewayStatus", String(response.status)),
6218
6277
  response.status
6219
6278
  );
6220
6279
  }
@@ -6242,14 +6301,14 @@ async function signedPairingRequest(pair, payload, origin, requestId, action, co
6242
6301
  const path = action === "request" ? "/api/device-pairing/requests" : `/api/device-pairing/requests/${requestId}/${action}`;
6243
6302
  const response = await post(origin, path, { ...body, signature });
6244
6303
  if (typeof response.signedPayload !== "string" || typeof response.signature !== "string") {
6245
- throw new Error("Gateway pairing response is unsigned");
6304
+ throw new Error(t("errorUnsignedPairing"));
6246
6305
  }
6247
6306
  if (!await verifyGateway(payload.gatewayPublicKey, response.signedPayload, response.signature)) {
6248
- throw new Error("Gateway identity could not be verified");
6307
+ throw new Error(t("errorGatewayVerification"));
6249
6308
  }
6250
6309
  const decoded = decodeJson(response.signedPayload);
6251
6310
  if (decoded.gateway.id !== payload.gatewayId || decoded.nonce !== nonce || decoded.request.requestId !== requestId) {
6252
- throw new Error("Gateway pairing response does not match this request");
6311
+ throw new Error(t("errorPairingMismatch"));
6253
6312
  }
6254
6313
  return decoded;
6255
6314
  }
@@ -6310,14 +6369,14 @@ ${nextRefreshToken}`;
6310
6369
  }
6311
6370
  }
6312
6371
  if (!response || !gatewayUrl) {
6313
- throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
6372
+ throw lastRouteError instanceof Error ? lastRouteError : new Error(t("errorNoReachableGatewayRoute"));
6314
6373
  }
6315
- if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error("Gateway identity could not be verified");
6374
+ if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error(t("errorGatewayVerification"));
6316
6375
  const responseProof = decodeJson(response.signedPayload);
6317
6376
  if (responseProof.purpose !== "device-refresh-v3" || responseProof.gatewayId !== gateway.gatewayId || responseProof.requestId !== requestId || responseProof.nonce !== nonce || !(responseProof.expiresAt > Date.now()) || responseProof.expiresAt > Date.now() + 6e4) throw new Error("Gateway refresh response does not match this request");
6318
6377
  const payload = responseProof.tokens;
6319
6378
  if (!payload?.accessToken || !payload.accessTokenExpiresAt || payload.refreshToken !== nextRefreshToken) {
6320
- throw new Error("Gateway returned invalid browser credentials");
6379
+ throw new Error(t("errorInvalidCredentials"));
6321
6380
  }
6322
6381
  return { ...payload, gatewayUrl };
6323
6382
  }
@@ -6341,7 +6400,7 @@ async function pairGateway(invitation, onApproval, requestedOrigin) {
6341
6400
  throw new Error("PAIRING_ALREADY_PENDING");
6342
6401
  }
6343
6402
  if (!journal) {
6344
- if (payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
6403
+ if (payload.expiresAt <= Date.now()) throw new Error(t("errorExpiredPairingLink"));
6345
6404
  journal = {
6346
6405
  invitation: invitation.trim(),
6347
6406
  payload,
@@ -6381,7 +6440,7 @@ async function pairGateway(invitation, onApproval, requestedOrigin) {
6381
6440
  }
6382
6441
  if (result.request.status !== "approved" && result.request.status !== "completed") {
6383
6442
  await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
6384
- throw new Error(`Pairing ${result.request.status}`);
6443
+ throw new Error(t("errorPairingStatus", result.request.status));
6385
6444
  }
6386
6445
  if (result.request.status !== "completed") {
6387
6446
  const response = await signedPairingRequestAcrossRoutes(
@@ -6397,7 +6456,7 @@ async function pairGateway(invitation, onApproval, requestedOrigin) {
6397
6456
  );
6398
6457
  ({ origin, result } = response);
6399
6458
  }
6400
- if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
6459
+ if (!result.request.deviceId) throw new Error(t("errorBrowserNotRegistered"));
6401
6460
  journal.origin = origin;
6402
6461
  journal.completed = { deviceId: result.request.deviceId, gatewayName: result.gateway.name };
6403
6462
  journal.refresh ??= { requestId: crypto.randomUUID(), nextRefreshToken: createRefreshToken() };
@@ -6470,7 +6529,7 @@ async function readProfile() {
6470
6529
  }
6471
6530
  async function getAccessProfile() {
6472
6531
  const profile = await readProfile();
6473
- if (!profile) throw new Error("Browser is not paired");
6532
+ if (!profile) throw new Error(t("errorBrowserNotPaired"));
6474
6533
  if (profile.accessTokenExpiresAt > Date.now() + 3e4) return profile;
6475
6534
  if (refreshTask) return refreshTask;
6476
6535
  refreshTask = (async () => {
@@ -6520,7 +6579,7 @@ async function registerBrowserEndpoint() {
6520
6579
  publicKey
6521
6580
  })
6522
6581
  });
6523
- if (!response.ok) throw new Error(`Browser endpoint registration failed (${response.status})`);
6582
+ if (!response.ok) throw new Error(t("errorEndpointRegistration", String(response.status)));
6524
6583
  }
6525
6584
  async function createBrowserEndpointHello(tools = []) {
6526
6585
  const profile = await getAccessProfile();
@@ -6568,4 +6627,4 @@ async function revokeAndForgetProfile() {
6568
6627
  }
6569
6628
  }
6570
6629
 
6571
- export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, canonicalJson as b, cancelPendingBrowserPairing as c, captureTabWithPermission as d, createBrowserEndpointHello as e, currentTabDescriptor as f, deleteBrowserOutbox as g, discoverLocalGateway as h, endpointToolContentSchema as i, gatewayFetch as j, getAccessProfile as k, readPendingBrowserPairing as l, readProfile as m, registerBrowserEndpoint as n, revokeAndForgetProfile as o, pairGateway as p, runWithTabSiteAccess as q, readBrowserOutbox as r, setAutoConnectEnabled as s, writeBrowserOutbox as w };
6630
+ export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, canonicalJson as b, cancelPendingBrowserPairing as c, captureTabWithPermission as d, createBrowserEndpointHello as e, currentTabDescriptor as f, deleteBrowserOutbox as g, discoverLocalGateway as h, endpointToolContentSchema as i, extensionLocale as j, gatewayFetch as k, getAccessProfile as l, loadExtensionLocalePreference as m, readPendingBrowserPairing as n, readProfile as o, pairGateway as p, registerBrowserEndpoint as q, readBrowserOutbox as r, revokeAndForgetProfile as s, runWithTabSiteAccess as t, saveExtensionLocalePreference as u, setAutoConnectEnabled as v, t as w, writeBrowserOutbox as x };
@@ -73,12 +73,26 @@ svg { display: block; }
73
73
  .brand { display: flex; align-items: center; gap: 8px; flex: 1; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
74
74
  .brand-mark { width: 25px; height: 25px; display: grid; place-items: center; border: 1px solid var(--edge); border-radius: 8px; background: var(--surface-panel); color: var(--accent); box-shadow: 0 1px 2px rgb(17 17 17 / 5%); }
75
75
  .brand-mark svg { width: 15px; height: 15px; stroke-width: 1.7; }
76
- .status { margin-right: 76px; display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--fg-muted); }
76
+ .status { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--fg-muted); }
77
77
  .status-dot, .mini-status { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 999px; background: var(--fg-disabled); }
78
78
  .status-dot.online, .mini-status.online { background: var(--success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 12%, transparent); }
79
79
  .status-dot.error { background: var(--danger); }
80
- .disconnect-link { position: absolute; z-index: 3; top: 13px; right: 13px; border: 0; border-radius: 6px; padding: 3px 7px; background: transparent; color: var(--fg-muted); font-size: 11px; }
81
- .disconnect-link:hover { background: var(--surface-hover); color: var(--fg); }
80
+ .header-actions { position: relative; flex: 0 0 auto; }
81
+ .header-icon-button { width: 32px; height: 32px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 9px; background: transparent; color: var(--fg-muted); }
82
+ .header-icon-button:hover, .header-icon-button[aria-expanded='true'] { border-color: var(--edge); background: var(--surface-hover); color: var(--fg); }
83
+ .header-icon-button svg { width: 17px; height: 17px; }
84
+ .settings-menu { position: absolute; z-index: 30; top: calc(100% + 8px); right: 0; width: min(260px, calc(100vw - 28px)); overflow: hidden; border: 1px solid var(--edge-strong); border-radius: 13px; padding: 7px; background: var(--surface-panel); box-shadow: var(--shadow-popover); }
85
+ .settings-menu-title { padding: 7px 8px 9px; font-size: 12px; font-weight: 680; }
86
+ .settings-group { border-top: 1px solid var(--edge); padding: 8px 0; }
87
+ .settings-label { display: flex; align-items: center; gap: 7px; padding: 2px 8px 7px; color: var(--fg-subtle); font-size: 10px; font-weight: 650; }
88
+ .settings-label svg { width: 14px; height: 14px; }
89
+ .settings-options { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
90
+ .settings-options button { min-width: 0; height: 34px; display: flex; align-items: center; justify-content: center; gap: 5px; border: 1px solid var(--edge); border-radius: 8px; background: var(--surface-base); color: var(--fg-muted); font-size: 11px; }
91
+ .settings-options button:hover { border-color: var(--edge-strong); background: var(--surface-hover); color: var(--fg); }
92
+ .settings-options button.active { border-color: color-mix(in srgb, var(--accent) 35%, var(--edge)); background: var(--accent-soft); color: var(--accent-fg); font-weight: 650; }
93
+ .settings-options button svg { width: 13px; height: 13px; }
94
+ .settings-disconnect { width: 100%; border: 0; border-top: 1px solid var(--edge); padding: 10px 8px 6px; background: transparent; color: var(--danger); font-size: 11px; text-align: left; }
95
+ .settings-disconnect:hover { color: color-mix(in srgb, var(--danger) 82%, var(--fg)); }
82
96
 
83
97
  .center { flex: 1; display: grid; place-items: center; padding: 24px; overflow: auto; }
84
98
  .card { width: min(100%, 420px); border: 1px solid var(--edge); border-radius: 16px; padding: 22px; background: var(--surface-panel); box-shadow: var(--shadow-surface); }
@@ -211,13 +225,14 @@ svg { display: block; }
211
225
  @keyframes pulse { 50% { opacity: .35; transform: scale(.8); } }
212
226
  @keyframes skeleton { to { background-position: -200% 0; } }
213
227
 
214
- .composer { position: relative; flex: 0 0 auto; margin: 0 10px 10px; border: 1px solid var(--edge-strong); border-radius: 15px; background: var(--surface-panel); padding: 8px; box-shadow: var(--shadow-surface); transition: border-color 120ms ease, box-shadow 120ms ease; }
215
- .composer:focus-within { border-color: color-mix(in srgb, var(--accent) 45%, var(--edge)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 7%, transparent), var(--shadow-surface); }
216
- .composer.dragging { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent), var(--shadow-surface); }
217
- .file-drop-overlay { position: absolute; z-index: 20; inset: 0; display: flex; align-items: center; justify-content: center; gap: 7px; border-radius: 14px; background: color-mix(in srgb, var(--accent-soft) 94%, var(--surface-panel)); color: var(--accent-fg); font-size: 12px; font-weight: 650; pointer-events: none; }
228
+ .composer { position: relative; flex: 0 0 auto; margin: 0 12px 12px; overflow: visible; border: 1px solid var(--edge); border-radius: 18px; background: var(--surface-panel); padding: 10px 10px 8px; box-shadow: var(--shadow-surface); transition: border-color 120ms ease, box-shadow 120ms ease; }
229
+ .composer:focus-within { border-color: var(--edge-strong); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 7%, transparent), var(--shadow-surface); }
230
+ .composer.dragging { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 12%, transparent), var(--shadow-surface); }
231
+ .file-drop-overlay { position: absolute; z-index: 20; inset: 0; display: flex; align-items: center; justify-content: center; gap: 7px; border-radius: 17px; background: color-mix(in srgb, var(--accent-soft) 94%, var(--surface-panel)); color: var(--accent-fg); font-size: 12px; font-weight: 650; pointer-events: none; }
218
232
  .file-drop-overlay svg { width: 17px; height: 17px; }
219
- .composer-input-row { min-height: 38px; display: flex; align-items: flex-end; gap: 7px; }
220
- .composer textarea { min-width: 0; min-height: 34px; max-height: 144px; flex: 1; resize: none; overflow-y: auto; border: 0; padding: 7px 2px 6px; outline: none; background: transparent; color: var(--fg); font-size: 13px; line-height: 1.5; }
233
+ .composer-input-row { min-height: 58px; display: block; }
234
+ .composer textarea { width: 100%; min-width: 0; min-height: 54px; max-height: 144px; display: block; resize: none; overflow-y: auto; border: 0; padding: 5px 8px 8px; outline: none; background: transparent; color: var(--fg); font-size: 14px; line-height: 1.55; }
235
+ .composer textarea::placeholder { color: var(--fg-disabled); }
221
236
  .composer-icon-button { width: 34px; height: 34px; border-color: transparent; color: var(--fg-muted); }
222
237
  .tools-anchor { position: relative; flex: 0 0 auto; }
223
238
  .tools-menu { position: absolute; z-index: 12; left: -1px; bottom: calc(100% + 9px); width: min(310px, calc(100vw - 38px)); overflow: hidden; border: 1px solid var(--edge-strong); border-radius: 13px; background: var(--surface-panel); padding: 6px; box-shadow: var(--shadow-popover); }
@@ -232,20 +247,23 @@ svg { display: block; }
232
247
  .tools-menu button.active-access { background: var(--success-soft); }
233
248
  .tools-menu button.active-access > svg { color: var(--success); }
234
249
  .tools-menu button.active-access > svg:last-child { width: 13px; height: 13px; color: var(--fg-subtle); }
235
- .send-button { width: 34px; height: 34px; flex: 0 0 auto; display: grid; place-items: center; border: 0; border-radius: 10px; background: var(--accent); color: #fff; transition: background-color 120ms ease, transform 120ms ease; }
250
+ .send-button { width: 34px; height: 34px; flex: 0 0 auto; display: grid; place-items: center; border: 0; border-radius: 9px; background: transparent; color: var(--accent-fg); transition: background-color 120ms ease, color 120ms ease, transform 120ms ease; }
236
251
  .send-button svg { width: 17px; height: 17px; }
237
- .send-button:hover:not(:disabled) { background: var(--accent-hover); transform: translateY(-1px); }
238
- .send-button:disabled { background: var(--surface-active); color: var(--fg-disabled); }
239
- .stop-button { background: var(--fg); color: var(--surface-panel); }
240
- .stop-button:hover:not(:disabled) { background: var(--fg-muted); }
241
- .composer-footer { min-height: 25px; display: flex; align-items: center; gap: 8px; padding: 4px 3px 0 5px; }
252
+ .send-button:hover:not(:disabled) { background: var(--accent-soft); transform: translateY(-1px); }
253
+ .send-button:disabled { background: transparent; color: var(--fg-disabled); }
254
+ .stop-button { background: var(--surface-hover); color: var(--fg-muted); }
255
+ .stop-button:hover:not(:disabled) { background: var(--surface-active); color: var(--fg); }
256
+ .composer-footer { min-height: 36px; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 3px 0 0; }
257
+ .composer-toolbar-start, .composer-toolbar-end { min-width: 0; display: flex; align-items: center; gap: 6px; }
258
+ .composer-toolbar-end { margin-left: auto; }
242
259
  .composer-state { min-width: 0; display: flex; align-items: center; gap: 5px; color: var(--fg-subtle); font-size: 9px; }
260
+ .composer-state-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
243
261
  .access-pill { display: inline-flex; align-items: center; gap: 3px; border-radius: 999px; padding: 2px 5px; background: var(--success-soft); color: color-mix(in srgb, var(--success) 72%, var(--fg)); font-weight: 600; }
244
262
  .access-pill svg { width: 10px; height: 10px; }
245
- .model-controls { min-width: 0; margin-left: auto; display: flex; align-items: center; gap: 3px; }
246
- .model-controls select { min-width: 0; max-width: 112px; border: 0; border-radius: 5px; padding: 2px 3px; outline: none; background: transparent; color: var(--fg-subtle); font-size: 9px; text-overflow: ellipsis; }
247
- .model-controls select:hover { background: var(--surface-hover); color: var(--fg-muted); }
248
- .model-controls select:last-child { max-width: 64px; }
263
+ .model-controls { min-width: 0; height: 32px; display: flex; align-items: center; gap: 0; border-radius: 9px; padding: 0 2px; background: var(--surface-hover); }
264
+ .model-controls select { min-width: 0; max-width: 118px; height: 28px; border: 0; border-radius: 7px; padding: 4px 5px; outline: none; background: transparent; color: var(--fg-muted); font-size: 10px; font-weight: 560; text-overflow: ellipsis; }
265
+ .model-controls select:hover, .model-controls select:focus { background: var(--surface-active); color: var(--fg); }
266
+ .model-controls select:last-child { max-width: 72px; color: var(--fg-subtle); }
249
267
 
250
268
  .composer-contexts { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }
251
269
  .context-chip, .attachment-chip { min-width: 0; max-width: 100%; display: flex; align-items: center; gap: 5px; border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--edge)); border-radius: 8px; padding: 5px 6px; color: var(--accent-fg); background: var(--accent-soft); font-size: 10px; }
@@ -290,7 +308,10 @@ svg { display: block; }
290
308
  .status { margin-right: 68px; }
291
309
  .messages { padding-inline: 12px; }
292
310
  .message.user { width: 92%; }
293
- .model-controls select { max-width: 86px; }
311
+ .composer { margin-inline: 8px; }
312
+ .composer-state-label { display: none; }
313
+ .model-controls select { max-width: 82px; }
314
+ .model-controls select:last-child { max-width: 58px; }
294
315
  .quick-prompts { gap: 6px; }
295
316
  }
296
317
 
@@ -1,4 +1,4 @@
1
- import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, b as canonicalJson, T as TAB_BINDING_PREFIX, d as captureTabWithPermission, P as PENDING_CONTEXT_KEY, m as readProfile, n as registerBrowserEndpoint, k as getAccessProfile, R as RealtimeClient, e as createBrowserEndpointHello, j as gatewayFetch } from './assets/auth-O8RiOF3l.js';
1
+ import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, b as canonicalJson, T as TAB_BINDING_PREFIX, d as captureTabWithPermission, P as PENDING_CONTEXT_KEY, w as t, o as readProfile, q as registerBrowserEndpoint, l as getAccessProfile, R as RealtimeClient, e as createBrowserEndpointHello, k as gatewayFetch } from './assets/auth-ClQTK565.js';
2
2
 
3
3
  const BROWSER_EXTENSION_PROTOCOL_VERSION = 4;
4
4
  const BROWSER_CONTROL_ENDPOINT_TOOL_NAME = "browser.control";
@@ -1143,11 +1143,20 @@ function diff(previous, next) {
1143
1143
 
1144
1144
  const log = createLogger("Background");
1145
1145
  const BACKGROUND_CLIENT_ID_KEY = "xopc.browser.background-client-id";
1146
+ const RECONNECT_ALARM = "xopc.browser.reconnect";
1147
+ const RECONNECT_DELAY_MINUTES = 1;
1146
1148
  let realtime;
1147
1149
  let endpointClaim;
1148
1150
  let lastConnectionError;
1149
1151
  let connectTask;
1150
1152
  let connectionGeneration = 0;
1153
+ function clearReconnectAlarm() {
1154
+ void chrome.alarms.clear(RECONNECT_ALARM);
1155
+ }
1156
+ function scheduleReconnectAlarm() {
1157
+ if (endpointClaim) return;
1158
+ chrome.alarms.create(RECONNECT_ALARM, { delayInMinutes: RECONNECT_DELAY_MINUTES });
1159
+ }
1151
1160
  const browserToolRegistry = new EndpointToolRegistry([{
1152
1161
  descriptor: BROWSER_CONTROL_ENDPOINT_DESCRIPTOR,
1153
1162
  execute: async (args) => {
@@ -1210,7 +1219,11 @@ async function connect(generation) {
1210
1219
  createWebSocket: (url) => new WebSocket(url),
1211
1220
  onStateChange: (state, error) => {
1212
1221
  lastConnectionError = error;
1213
- if (state === "connected") lastConnectionError = void 0;
1222
+ if (state === "connected") {
1223
+ lastConnectionError = void 0;
1224
+ } else {
1225
+ scheduleReconnectAlarm();
1226
+ }
1214
1227
  if (error) log.warn("Browser Realtime connection changed", { state, error });
1215
1228
  }
1216
1229
  });
@@ -1219,6 +1232,7 @@ async function connect(generation) {
1219
1232
  onReady: ({ endpointId, turnToken }) => {
1220
1233
  endpointClaim = { endpointId, token: turnToken };
1221
1234
  lastConnectionError = void 0;
1235
+ clearReconnectAlarm();
1222
1236
  endpointHost.connect((message) => client.sendEndpointMessage(message));
1223
1237
  void chrome.runtime.sendMessage({ type: "browser/endpoint-ready", claim: endpointClaim }).catch(() => void 0);
1224
1238
  log.info("Browser control connected through Gateway Realtime", { endpointId });
@@ -1229,6 +1243,7 @@ async function connect(generation) {
1229
1243
  onDisconnected: () => {
1230
1244
  endpointClaim = void 0;
1231
1245
  endpointHost.disconnect();
1246
+ scheduleReconnectAlarm();
1232
1247
  }
1233
1248
  });
1234
1249
  if (generation !== connectionGeneration || realtime) return;
@@ -1247,6 +1262,7 @@ function connectWithLogging() {
1247
1262
  const generation = connectionGeneration;
1248
1263
  connectTask = connect(generation).catch((error) => {
1249
1264
  lastConnectionError = error instanceof Error ? error.message : String(error);
1265
+ scheduleReconnectAlarm();
1250
1266
  log.warn("Could not connect browser Realtime transport", { error: lastConnectionError });
1251
1267
  }).finally(() => {
1252
1268
  connectTask = void 0;
@@ -1284,17 +1300,33 @@ chrome.runtime.onInstalled.addListener(() => {
1284
1300
  chrome.contextMenus.removeAll(() => {
1285
1301
  chrome.contextMenus.create({
1286
1302
  id: "xopc-ask-selection",
1287
- title: "Ask xopc about “%s”",
1303
+ title: t("contextMenuAskSelection"),
1288
1304
  contexts: ["selection"]
1289
1305
  });
1290
1306
  });
1291
1307
  connectWithLogging();
1292
1308
  });
1293
1309
  chrome.runtime.onStartup.addListener(connectWithLogging);
1310
+ chrome.alarms.onAlarm.addListener((alarm) => {
1311
+ if (alarm.name !== RECONNECT_ALARM) return;
1312
+ void readProfile().then((profile) => {
1313
+ if (!profile) {
1314
+ clearReconnectAlarm();
1315
+ return;
1316
+ }
1317
+ if (endpointClaim) {
1318
+ clearReconnectAlarm();
1319
+ return;
1320
+ }
1321
+ disconnect();
1322
+ connectWithLogging();
1323
+ });
1324
+ });
1294
1325
  chrome.storage.onChanged.addListener((changes, area) => {
1295
1326
  if (area === "local" && changes["xopc.browser.profile"]) {
1296
1327
  disconnect();
1297
- connectWithLogging();
1328
+ if (changes["xopc.browser.profile"].newValue) connectWithLogging();
1329
+ else clearReconnectAlarm();
1298
1330
  }
1299
1331
  });
1300
1332
  void chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>xopc</title>
7
7
  <script type="module" crossorigin src="./sidepanel.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/auth-O8RiOF3l.js">
9
- <link rel="stylesheet" crossorigin href="./assets/sidepanel-C86O7q5Y.css">
8
+ <link rel="modulepreload" crossorigin href="./assets/auth-ClQTK565.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/sidepanel-CeGqObOa.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>