feat(browser): Brave Origin works for real-profile browsing and default-browser detection
Extends the real-profile machinery (PR #95620) to Brave Origin — Brave's standalone paid build with a fully separate install identity: - new canonical key 'brave-origin' in _CHROMIUM_BROWSERS - Windows: BraveOHTML ProgId -> brave-origin; channel ProgIds BraveOBHTML/ BraveODHTML/BraveOSHTM fail closed (identifiers from brave-core install_static) - macOS: com.brave.Browser.origin bundle id (exact match); .beta/.dev/ .nightly channel bundles fail closed; /Applications/Brave Origin.app - Linux: brave-origin.desktop matched BEFORE the bare 'brave' fragment (substring scan would otherwise resolve an Origin default to stable Brave and drive the wrong profile — #95549 wrong-principal invariant); brave-origin-{beta,nightly,dev} fail closed - profile dirs: BraveSoftware/Brave-Origin on all three OSes (per brave-core kProductPathName + Homebrew cask zap paths) - /browser connect launch tables: Brave Origin split into its OWN group so a 'brave' executable lookup can never resolve to the Origin binary - user-facing strings/docs/desktop tooltip updated Tests: progid/bundle/desktop map params + data-dir resolution for all three OSes; 125 passed in the three browser test files.
This commit is contained in:
@@ -558,7 +558,7 @@ export const FIELD_DESCRIPTIONS: Record<string, string> = defineFieldCopy({
|
||||
timezone: 'IANA timezone identifier. Blank uses the system timezone.',
|
||||
browser: {
|
||||
useRealProfile:
|
||||
"Local browsing uses your real logins. Hermes copies your default browser's profile (cookies, logins, preferences) into a managed snapshot and drives it with its packaged Chromium — your live profile is never opened directly, and the copy is refreshed from it on each run. Also lets the agent open a local real-profile session on request even when a cloud browser backend is configured. Only Chromium browsers (Chrome, Edge, Brave, Chromium) are supported; a non-Chromium default fails with a clear message. Off by default."
|
||||
"Local browsing uses your real logins. Hermes copies your default browser's profile (cookies, logins, preferences) into a managed snapshot and drives it with its packaged Chromium — your live profile is never opened directly, and the copy is refreshed from it on each run. Also lets the agent open a local real-profile session on request even when a cloud browser backend is configured. Only Chromium browsers (Chrome, Edge, Brave, Brave Origin, Chromium) are supported; a non-Chromium default fails with a clear message. Off by default."
|
||||
},
|
||||
agent: {
|
||||
imageInputMode: 'Controls how image attachments are sent to the model.',
|
||||
|
||||
@@ -27,6 +27,7 @@ _DARWIN_APPS = (
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||
"/Applications/Brave Origin.app/Contents/MacOS/Brave Origin",
|
||||
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||
)
|
||||
|
||||
@@ -37,6 +38,13 @@ _WINDOWS_BROWSER_GROUPS = (
|
||||
(("Chromium", "Application", "chrome.exe"), ("Chromium", "Application", "chromium.exe")),
|
||||
),
|
||||
(("brave.exe", "brave"), (("BraveSoftware", "Brave-Browser", "Application", "brave.exe"),)),
|
||||
(
|
||||
("brave-origin.exe", "brave-origin"),
|
||||
(
|
||||
("BraveSoftware", "Brave-Origin", "Application", "brave.exe"),
|
||||
("BraveSoftware", "Brave-Origin", "Application", "brave-origin.exe"),
|
||||
),
|
||||
),
|
||||
(("msedge.exe", "msedge"), (("Microsoft", "Edge", "Application", "msedge.exe"),)),
|
||||
)
|
||||
|
||||
@@ -53,7 +61,7 @@ _LINUX_BROWSER_GROUPS = (
|
||||
("/usr/bin/chromium-browser", "/usr/bin/chromium"),
|
||||
),
|
||||
(
|
||||
("brave-browser", "brave-browser-stable", "brave", "brave-origin", "brave-origin-nightly"),
|
||||
("brave-browser", "brave-browser-stable", "brave"),
|
||||
(
|
||||
"/usr/bin/brave-browser",
|
||||
"/usr/bin/brave-browser-stable",
|
||||
@@ -61,9 +69,20 @@ _LINUX_BROWSER_GROUPS = (
|
||||
"/snap/bin/brave",
|
||||
"/opt/brave.com/brave/brave-browser",
|
||||
"/opt/brave.com/brave/brave",
|
||||
"/opt/brave-bin/brave",
|
||||
),
|
||||
),
|
||||
# Brave Origin is a SEPARATE product identity (side-by-side installable
|
||||
# with Brave), so it gets its own group: the executable fallback in
|
||||
# chromium_executable() matches by group, and mixing Origin binaries into
|
||||
# the brave group would let a "brave" lookup resolve to the Origin binary
|
||||
# (or vice versa) — driving the wrong browser's profile.
|
||||
(
|
||||
("brave-origin", "brave-origin-nightly"),
|
||||
(
|
||||
"/usr/bin/brave-origin",
|
||||
"/opt/brave.com/brave-origin/brave-origin",
|
||||
"/opt/brave.com/brave-origin-nightly/brave-origin",
|
||||
"/opt/brave-bin/brave",
|
||||
),
|
||||
),
|
||||
(
|
||||
@@ -93,7 +112,12 @@ _LINUX_INSTALL_PATHS = tuple(path for _, paths in _LINUX_BROWSER_GROUPS for path
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Canonical Chromium browser keys we support for real-profile driving.
|
||||
_CHROMIUM_BROWSERS = ("chrome", "edge", "brave", "chromium")
|
||||
# ``brave-origin`` is Brave's standalone paid build: same Chromium core, but a
|
||||
# fully distinct install identity (BraveSoftware/Brave-Origin product path,
|
||||
# ``BraveOHTML`` ProgId, ``com.brave.Browser.origin`` bundle id) so it
|
||||
# side-by-side installs with regular Brave — its profile is NOT under
|
||||
# Brave-Browser and must never be conflated with the ``brave`` key.
|
||||
_CHROMIUM_BROWSERS = ("chrome", "edge", "brave", "chromium", "brave-origin")
|
||||
|
||||
# Windows UserChoice ProgId prefixes → canonical browser key. Matched
|
||||
# case-insensitively by prefix so version suffixes (e.g. ``ChromeHTML.X``)
|
||||
@@ -104,6 +128,9 @@ _CHROMIUM_BROWSERS = ("chrome", "edge", "brave", "chromium")
|
||||
_WINDOWS_PROGID_MAP = (
|
||||
("chromehtml", "chrome"),
|
||||
("msedgehtm", "edge"),
|
||||
# Brave Origin stable is ``BraveOHTML`` (brave-core install_static). Listed
|
||||
# before ``bravehtml`` for clarity; the prefixes don't collide either way.
|
||||
("braveohtml", "brave-origin"),
|
||||
("bravehtml", "brave"),
|
||||
("chromiumhtm", "chromium"),
|
||||
)
|
||||
@@ -117,6 +144,9 @@ _WINDOWS_CHANNEL_PROGIDS = (
|
||||
"chromebhtml", "chromedhtml", "chromesshtml", "chromecanaryhtml",
|
||||
"msedgebhtml", "msedgedhtml", "msedgechtml",
|
||||
"bravebetahtml", "bravenightlyhtml",
|
||||
# Brave Origin channels (brave-core install_static): Beta=BraveOBHTML,
|
||||
# Dev=BraveODHTML, Nightly/SxS=BraveOSHTM (no trailing L — 10-char cap).
|
||||
"braveobhtml", "braveodhtml", "braveoshtm",
|
||||
)
|
||||
|
||||
# Linux xdg default-web-browser .desktop name fragments → canonical STABLE key.
|
||||
@@ -128,6 +158,11 @@ _LINUX_DESKTOP_MAP = (
|
||||
("google-chrome", "chrome"),
|
||||
("com.google.chrome", "chrome"),
|
||||
("chromium", "chromium"),
|
||||
# ORDER MATTERS: ``brave-origin.desktop`` contains the bare ``brave``
|
||||
# fragment, so the substring scan must hit the Origin entry first —
|
||||
# otherwise an Origin default resolves to stable Brave and real-profile
|
||||
# mode drives a DIFFERENT browser's profile (wrong-principal, #95549).
|
||||
("brave-origin", "brave-origin"),
|
||||
("brave", "brave"),
|
||||
("microsoft-edge", "edge"),
|
||||
("com.microsoft.edge", "edge"),
|
||||
@@ -141,6 +176,7 @@ _LINUX_CHANNEL_FRAGMENTS = (
|
||||
"com.google.chrome.beta", "com.google.chrome.dev", "com.google.chrome.canary",
|
||||
"microsoft-edge-beta", "microsoft-edge-dev", "microsoft-edge-canary",
|
||||
"brave-browser-beta", "brave-browser-nightly", "brave-browser-dev",
|
||||
"brave-origin-beta", "brave-origin-nightly", "brave-origin-dev",
|
||||
)
|
||||
|
||||
# Where sandboxed Linux packages keep the profile instead of $XDG_CONFIG_HOME.
|
||||
@@ -161,6 +197,10 @@ _DARWIN_BUNDLE_MAP = (
|
||||
("com.google.chrome", "chrome"),
|
||||
("com.microsoft.edgemac", "edge"),
|
||||
("com.brave.browser", "brave"),
|
||||
# Brave Origin reuses the Brave bundle id with an ``.origin`` suffix
|
||||
# (Homebrew cask: com.brave.Browser.origin). Exact matching keeps it from
|
||||
# ever being read as plain ``com.brave.browser``.
|
||||
("com.brave.browser.origin", "brave-origin"),
|
||||
("org.chromium.chromium", "chromium"),
|
||||
)
|
||||
|
||||
@@ -169,6 +209,8 @@ _DARWIN_CHANNEL_BUNDLES = (
|
||||
"com.google.chrome.beta", "com.google.chrome.dev", "com.google.chrome.canary",
|
||||
"com.microsoft.edgemac.beta", "com.microsoft.edgemac.dev", "com.microsoft.edgemac.canary",
|
||||
"com.brave.browser.beta", "com.brave.browser.nightly",
|
||||
"com.brave.browser.origin.beta", "com.brave.browser.origin.dev",
|
||||
"com.brave.browser.origin.nightly",
|
||||
)
|
||||
|
||||
# Sentinel returned when the OS default is a recognized-but-unsupported
|
||||
@@ -201,6 +243,11 @@ def _real_profile_relparts(browser: str) -> tuple:
|
||||
("Chromium", "User Data"),
|
||||
"chromium",
|
||||
),
|
||||
"brave-origin": (
|
||||
("BraveSoftware", "Brave-Origin"),
|
||||
("BraveSoftware", "Brave-Origin", "User Data"),
|
||||
"BraveSoftware/Brave-Origin",
|
||||
),
|
||||
}[browser]
|
||||
|
||||
|
||||
@@ -258,6 +305,7 @@ def chromium_executable(browser: str, system: str | None = None) -> str | None:
|
||||
"chrome": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"chromium": "/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
"brave": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||
"brave-origin": "/Applications/Brave Origin.app/Contents/MacOS/Brave Origin",
|
||||
"edge": "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||
}[browser]
|
||||
return app if os.path.isfile(app) else None
|
||||
@@ -266,6 +314,10 @@ def chromium_executable(browser: str, system: str | None = None) -> str | None:
|
||||
"chrome": (("Google", "Chrome", "Application", "chrome.exe"),),
|
||||
"chromium": (("Chromium", "Application", "chrome.exe"), ("Chromium", "Application", "chromium.exe")),
|
||||
"brave": (("BraveSoftware", "Brave-Browser", "Application", "brave.exe"),),
|
||||
"brave-origin": (
|
||||
("BraveSoftware", "Brave-Origin", "Application", "brave.exe"),
|
||||
("BraveSoftware", "Brave-Origin", "Application", "brave-origin.exe"),
|
||||
),
|
||||
"edge": (("Microsoft", "Edge", "Application", "msedge.exe"),),
|
||||
}[browser]
|
||||
bases = [
|
||||
@@ -280,6 +332,7 @@ def chromium_executable(browser: str, system: str | None = None) -> str | None:
|
||||
"chrome": ("google-chrome", "google-chrome-stable"),
|
||||
"chromium": ("chromium-browser", "chromium"),
|
||||
"brave": ("brave-browser", "brave-browser-stable", "brave"),
|
||||
"brave-origin": ("brave-origin",),
|
||||
"edge": ("microsoft-edge", "microsoft-edge-stable"),
|
||||
}[browser]
|
||||
for name in linux:
|
||||
|
||||
@@ -595,7 +595,7 @@ DEFAULT_CONFIG = {
|
||||
# never contends with the user's running browser. Turning this back off
|
||||
# deletes the snapshot store (~/.hermes/browser-profile/) so copied
|
||||
# credentials don't outlive consent. Only Chromium-family default
|
||||
# browsers are supported (Chrome, Edge, Brave, Chromium); a non-Chromium
|
||||
# browsers are supported (Chrome, Edge, Brave, Brave Origin, Chromium); a non-Chromium
|
||||
# default (e.g. Firefox) fails closed with a clear message. Default
|
||||
# false. Also gates the browser_exec ``local`` argument, which forces a
|
||||
# real-profile local session even under a cloud browser backend. Toggle
|
||||
|
||||
+1
-1
@@ -13298,7 +13298,7 @@ def main():
|
||||
)
|
||||
browser_close.add_argument(
|
||||
"--browser",
|
||||
help="Override detected default browser (chrome/edge/brave/chromium)",
|
||||
help="Override detected default browser (chrome/edge/brave/brave-origin/chromium)",
|
||||
)
|
||||
|
||||
def _dispatch_browser(_args):
|
||||
|
||||
@@ -97,8 +97,11 @@ class TestDetectDefaultDarwin:
|
||||
[
|
||||
("com.google.Chrome", "chrome"),
|
||||
("com.brave.Browser", "brave"),
|
||||
("com.brave.Browser.origin", "brave-origin"),
|
||||
("com.microsoft.edgemac", "edge"),
|
||||
("org.chromium.Chromium", "chromium"),
|
||||
("com.brave.Browser.origin.beta", bc.UNSUPPORTED_CHANNEL),
|
||||
("com.brave.Browser.origin.nightly", bc.UNSUPPORTED_CHANNEL),
|
||||
],
|
||||
)
|
||||
def test_bundle_map(self, bundle, expected):
|
||||
@@ -122,6 +125,9 @@ class TestDetectDefaultLinux:
|
||||
("org.chromium.Chromium.desktop", "chromium"),
|
||||
("brave-browser.desktop", "brave"),
|
||||
("com.brave.Browser.desktop", "brave"),
|
||||
("brave-origin.desktop", "brave-origin"),
|
||||
("brave-origin-beta.desktop", bc.UNSUPPORTED_CHANNEL),
|
||||
("brave-origin-nightly.desktop", bc.UNSUPPORTED_CHANNEL),
|
||||
("microsoft-edge.desktop", "edge"),
|
||||
("com.microsoft.Edge.desktop", "edge"),
|
||||
("firefox.desktop", None),
|
||||
|
||||
@@ -42,6 +42,29 @@ class TestRealProfileResolvers:
|
||||
assert m["chromehtml"] == "chrome"
|
||||
assert m["msedgehtm"] == "edge"
|
||||
assert m["bravehtml"] == "brave"
|
||||
assert m["braveohtml"] == "brave-origin"
|
||||
|
||||
def test_brave_origin_data_dirs(self):
|
||||
import hermes_cli.browser_connect as bc
|
||||
with patch.dict(os.environ, {"LOCALAPPDATA": r"C:\Users\T\AppData\Local"}, clear=False):
|
||||
win = bc.real_profile_data_dir("brave-origin", "Windows")
|
||||
assert win and win.endswith(ntpath.join("BraveSoftware", "Brave-Origin", "User Data"))
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": "/home/t/.config"}, clear=False):
|
||||
assert (
|
||||
bc.real_profile_data_dir("brave-origin", "Linux")
|
||||
== "/home/t/.config/BraveSoftware/Brave-Origin"
|
||||
)
|
||||
mac = bc.real_profile_data_dir("brave-origin", "Darwin")
|
||||
assert mac and mac.endswith("Library/Application Support/BraveSoftware/Brave-Origin")
|
||||
|
||||
def test_brave_origin_channel_progids_fail_closed(self):
|
||||
import hermes_cli.browser_connect as bc
|
||||
# Beta=BraveOBHTML, Dev=BraveODHTML, Nightly=BraveOSHTM must be caught
|
||||
# by the channel list, and must be checked BEFORE the stable map — note
|
||||
# none of them share the braveohtml stable prefix, but ordering is the
|
||||
# invariant the detector relies on for the other families.
|
||||
for chan in ("braveobhtml", "braveodhtml", "braveoshtm"):
|
||||
assert chan in bc._WINDOWS_CHANNEL_PROGIDS
|
||||
|
||||
def test_detect_default_non_chromium_is_none(self):
|
||||
import hermes_cli.browser_connect as bc
|
||||
|
||||
@@ -1596,7 +1596,8 @@ def _real_profile_cdp() -> tuple:
|
||||
if browser is None:
|
||||
return None, (
|
||||
"browser.use_real_profile is on, but your default browser is not a "
|
||||
"supported Chromium browser (Chrome, Edge, Brave, Chromium). "
|
||||
"supported Chromium browser (Chrome, Edge, Brave, Brave Origin, "
|
||||
"Chromium). "
|
||||
"Real-profile browsing requires a Chromium default; set one or turn "
|
||||
"the toggle off."
|
||||
)
|
||||
@@ -1610,7 +1611,8 @@ def _real_profile_cdp() -> tuple:
|
||||
"browser.use_real_profile is on, but your default browser is a "
|
||||
"pre-release Chromium channel (Beta / Dev / Canary), which "
|
||||
"real-profile browsing does not support. Set your default to a "
|
||||
"stable Chrome / Edge / Brave / Chromium, or turn the toggle off."
|
||||
"stable Chrome / Edge / Brave / Brave Origin / Chromium, or turn "
|
||||
"the toggle off."
|
||||
)
|
||||
|
||||
# Reuse BEFORE writing anything. A shared copy-browser may already be up
|
||||
|
||||
@@ -206,7 +206,7 @@ losing unsaved tabs), then retries. If the profile is still locked after that
|
||||
to fully quit the browser — it won't loop or kill again on its own.
|
||||
:::
|
||||
|
||||
- **Supported browsers:** Chrome, Edge, Brave, Chromium (whichever is your OS
|
||||
- **Supported browsers:** Chrome, Edge, Brave, Brave Origin, Chromium (whichever is your OS
|
||||
default). A non-Chromium default (e.g. Firefox) fails closed with a clear
|
||||
message rather than guessing.
|
||||
- **Works on any backend.** On a local backend it's automatic once the toggle
|
||||
|
||||
Reference in New Issue
Block a user