Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7709292f1c |
@@ -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,避免冲掉登录态
|
||||
|
||||
2
SKILL.md
2
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:
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user