From 7709292f1c56a7dcade28ef57bfa76b8c942cbd1 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Wed, 16 Sep 2026 14:13:38 +0800 Subject: [PATCH] fix: recognize modern Chrome Sessions so open restores login --- CHANGELOG.md | 4 ++ SKILL.md | 2 +- scripts/service/browser_service.py | 14 +++--- scripts/service/chrome_profile_prefs.py | 35 +++++++++++++ tests/test_browser_open.py | 66 +++++++++++++++++++++++-- 5 files changed, 110 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0becab7..b412e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 本文件对应技能市场 **「更新日志」** Tab。 发布 CI 会按当前 tag 版本提取匹配的 `##` 小节。 +## 2.0.28 + +- 「打开浏览器」识别现代 Chrome `Default/Sessions/Session_*`(及 `Tabs_*`),有可恢复会话时不再强制带平台首页 URL,避免冲掉 Session Cookie 登录态 + ## 2.0.27 - 「打开浏览器」:Profile 已占用时复用窗口;加固 Preferences/Local State「继续上次会话」;有历史会话时冷启动不带 URL,避免冲掉登录态 diff --git a/SKILL.md b/SKILL.md index 6aab8cd..27aef18 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号与凭据管理 description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。" -version: 2.0.27 +version: 2.0.28 author: 深圳匠厂科技有限公司 metadata: openclaw: diff --git a/scripts/service/browser_service.py b/scripts/service/browser_service.py index 1da2a31..ae9a335 100644 --- a/scripts/service/browser_service.py +++ b/scripts/service/browser_service.py @@ -14,7 +14,10 @@ import sys from db.connection import get_conn, init_db from db.accounts_repo import get_account_by_id, get_platform_from_db -from service.chrome_profile_prefs import ensure_session_restore +from service.chrome_profile_prefs import ( + ensure_session_restore, + profile_has_restorable_session, +) from util.logging_config import get_skill_logger from util.platforms import PLATFORMS @@ -314,12 +317,9 @@ def cmd_open(account_id): finally: conn.close() - # 有可恢复会话时不带 URL;首次/空 Profile 仍打开平台首页便于登录 - session_dir = os.path.join(profile_abs, "Default") - has_session = any( - os.path.isfile(os.path.join(session_dir, name)) - for name in ("Current Session", "Last Session", "Current Tabs", "Last Tabs") - ) + # 有可恢复会话时不带 URL;首次/空 Profile 仍打开平台首页便于登录。 + # 须识别现代 Chrome 的 Default/Sessions/Session_*(仅认旧文件名会导致每次都开首页冲掉 Session Cookie)。 + has_session = profile_has_restorable_session(profile_abs) launch_url = None if has_session else url cmd = [browser_exe, *human_chrome_launch_args(profile_abs, launch_url)] diff --git a/scripts/service/chrome_profile_prefs.py b/scripts/service/chrome_profile_prefs.py index 7dd58d6..dc32490 100644 --- a/scripts/service/chrome_profile_prefs.py +++ b/scripts/service/chrome_profile_prefs.py @@ -10,6 +10,41 @@ from util.logging_config import get_skill_logger RESTORE_CONTINUE_WHERE_LEFT_OFF = 1 +# Chromium ≤ roughly mid-era: session files lived under Default/ +_LEGACY_SESSION_FILES = ( + "Current Session", + "Last Session", + "Current Tabs", + "Last Tabs", +) + + +def profile_has_restorable_session(profile_dir: str) -> bool: + """True when the profile has on-disk session data Chrome can restore. + + Modern Chrome/Edge write ``Default/Sessions/Session_*`` (and ``Tabs_*``). + Older builds used ``Default/Current Session`` etc. Either form counts. + """ + default_dir = os.path.join(os.path.abspath(profile_dir), "Default") + if any( + os.path.isfile(os.path.join(default_dir, name)) + for name in _LEGACY_SESSION_FILES + ): + return True + + sessions_dir = os.path.join(default_dir, "Sessions") + if not os.path.isdir(sessions_dir): + return False + try: + names = os.listdir(sessions_dir) + except OSError: + return False + return any( + name.startswith("Session_") or name.startswith("Tabs_") + for name in names + if os.path.isfile(os.path.join(sessions_dir, name)) + ) + def preferences_path(profile_dir: str) -> str: return os.path.join(os.path.abspath(profile_dir), "Default", "Preferences") diff --git a/tests/test_browser_open.py b/tests/test_browser_open.py index 56331be..569ebed 100644 --- a/tests/test_browser_open.py +++ b/tests/test_browser_open.py @@ -18,16 +18,45 @@ class TestPlaywrightLaunchHelpers: assert "--start-maximized" in args +class TestProfileHasRestorableSession: + def test_empty_profile_false(self, tmp_path): + from service.chrome_profile_prefs import profile_has_restorable_session + + assert profile_has_restorable_session(str(tmp_path / "prof")) is False + + def test_legacy_current_session(self, tmp_path): + from service.chrome_profile_prefs import profile_has_restorable_session + + default = tmp_path / "prof" / "Default" + default.mkdir(parents=True) + (default / "Current Session").write_bytes(b"x") + assert profile_has_restorable_session(str(tmp_path / "prof")) is True + + def test_modern_sessions_dir(self, tmp_path): + from service.chrome_profile_prefs import profile_has_restorable_session + + sessions = tmp_path / "prof" / "Default" / "Sessions" + sessions.mkdir(parents=True) + (sessions / "Session_13434010951759956").write_bytes(b"sess") + assert profile_has_restorable_session(str(tmp_path / "prof")) is True + + def test_modern_tabs_only(self, tmp_path): + from service.chrome_profile_prefs import profile_has_restorable_session + + sessions = tmp_path / "prof" / "Default" / "Sessions" + sessions.mkdir(parents=True) + (sessions / "Tabs_13434010952144072").write_bytes(b"tabs") + assert profile_has_restorable_session(str(tmp_path / "prof")) is True + + class TestCmdOpenDirectBrowser: """account open = same-process Chrome/Edge launch (like reveal-profile).""" - def test_cmd_open_launches_browser_exe(self, clean_db, tmp_path, capsys, monkeypatch): + def _seed_account(self, clean_db, tmp_path, profile): from db.accounts_repo import insert_account - from service import browser_service from util.platforms import seed_platforms seed_platforms(clean_db) - profile = tmp_path / "prof" aid = insert_account( clean_db, platform_key="sohu", @@ -45,7 +74,9 @@ class TestCmdOpenDirectBrowser: auth_strategy="password_auto", ) clean_db.commit() + return aid + def _patch_launch(self, monkeypatch, browser_service, tmp_path): fake_exe = str(tmp_path / "chrome.exe") monkeypatch.setattr( browser_service, @@ -64,6 +95,14 @@ class TestCmdOpenDirectBrowser: return FakeProc() monkeypatch.setattr(browser_service.subprocess, "Popen", fake_popen) + return fake_exe, spawned + + def test_cmd_open_launches_browser_exe(self, clean_db, tmp_path, capsys, monkeypatch): + from service import browser_service + + profile = tmp_path / "prof" + aid = self._seed_account(clean_db, tmp_path, profile) + fake_exe, spawned = self._patch_launch(monkeypatch, browser_service, tmp_path) browser_service.cmd_open(str(aid)) out = capsys.readouterr().out.strip() @@ -88,6 +127,27 @@ class TestCmdOpenDirectBrowser: assert "open_child_runner" not in joined assert "python" not in joined.lower() + def test_cmd_open_skips_url_when_modern_sessions_exist( + self, clean_db, tmp_path, capsys, monkeypatch + ): + """Modern Chrome Sessions/ must restore without forcing homepage URL.""" + from service import browser_service + + profile = tmp_path / "prof" + sessions = profile / "Default" / "Sessions" + sessions.mkdir(parents=True) + (sessions / "Session_13434010951759956").write_bytes(b"sess") + aid = self._seed_account(clean_db, tmp_path, profile) + _fake_exe, spawned = self._patch_launch(monkeypatch, browser_service, tmp_path) + + browser_service.cmd_open(str(aid)) + out = capsys.readouterr().out.strip() + payload = json.loads(out.splitlines()[-1]) + + assert payload["success"] is True + assert "https://example.com" not in spawned["cmd"] + assert spawned["cmd"][-1].startswith("--") + class TestHumanChromeLaunchArgs: def test_flags_then_url(self, tmp_path):