chore: auto release commit (2026-06-17 09:21:19)
All checks were successful
技能自动化发布 / release (push) Successful in 6s
All checks were successful
技能自动化发布 / release (push) Successful in 6s
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
"""示例 adapter 四档 dispatch。
|
||||
|
||||
由 ``OPENCLAW_TEST_TARGET``(或 ``config.get``)决定档位。
|
||||
复制本目录后把 ``example_adapter`` 改名为 ``<domain>_adapter``。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from .mock import MockAdapter
|
||||
from .real_api import RealApiAdapter
|
||||
from .real_rpa import RealRpaAdapter
|
||||
from .sim_rpa import SimRpaAdapter
|
||||
|
||||
|
||||
def get_adapter():
|
||||
"""返回当前档位 adapter 实例。"""
|
||||
target = (os.environ.get("OPENCLAW_TEST_TARGET") or "simulator_rpa").strip().lower()
|
||||
if target in ("unit", "mock"):
|
||||
return MockAdapter()
|
||||
if target == "real_api":
|
||||
return RealApiAdapter()
|
||||
if target == "real_rpa":
|
||||
return RealRpaAdapter()
|
||||
return SimRpaAdapter()
|
||||
@@ -1,37 +0,0 @@
|
||||
"""示例适配器数据契约与基类。
|
||||
|
||||
复制本目录为 ``<domain>_adapter/``,按 ADAPTER.md 实现各档位。
|
||||
业务逻辑只依赖 ``base`` 里的接口,不感知 mock / sim_rpa / real_* 差异。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExampleItem:
|
||||
"""单条业务输入(示例)。"""
|
||||
|
||||
id: str
|
||||
label: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExampleResult:
|
||||
"""批量操作结果(以批为单位返回)。"""
|
||||
|
||||
ok: bool
|
||||
serial_no: Optional[str]
|
||||
error_msg: Optional[str]
|
||||
artifacts: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class AdapterBase:
|
||||
"""四档 adapter 统一接口;子类实现 ``do_batch``。"""
|
||||
|
||||
name = "base"
|
||||
|
||||
def do_batch(self, target: str, items: List[ExampleItem]) -> ExampleResult:
|
||||
raise NotImplementedError
|
||||
@@ -1,24 +0,0 @@
|
||||
"""离线 mock 档位:纯内存仿真,CI / 单测必跑。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import AdapterBase, ExampleItem, ExampleResult
|
||||
|
||||
|
||||
class MockAdapter(AdapterBase):
|
||||
name = "mock"
|
||||
|
||||
def do_batch(self, target: str, items: list[ExampleItem]) -> ExampleResult:
|
||||
if not items:
|
||||
return ExampleResult(
|
||||
ok=False,
|
||||
serial_no=None,
|
||||
error_msg="empty batch",
|
||||
artifacts={},
|
||||
)
|
||||
return ExampleResult(
|
||||
ok=True,
|
||||
serial_no=f"MOCK-{len(items)}",
|
||||
error_msg=None,
|
||||
artifacts={"mode": "mock", "target": target, "count": len(items)},
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
"""真实 API 档位占位:有官方接口时在此实现。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import AdapterBase, ExampleItem, ExampleResult
|
||||
|
||||
|
||||
class RealApiAdapter(AdapterBase):
|
||||
name = "real_api"
|
||||
|
||||
def do_batch(self, target: str, items: list[ExampleItem]) -> ExampleResult:
|
||||
raise NotImplementedError(
|
||||
"RealApiAdapter: 复制 example_adapter 后在此对接真实 API"
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
"""真实 RPA 档位占位:无 API 时最后手段,谨慎实现。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import AdapterBase, ExampleItem, ExampleResult
|
||||
|
||||
|
||||
class RealRpaAdapter(AdapterBase):
|
||||
name = "real_rpa"
|
||||
|
||||
def do_batch(self, target: str, items: list[ExampleItem]) -> ExampleResult:
|
||||
raise NotImplementedError(
|
||||
"RealRpaAdapter: 复制 example_adapter 后在此实现生产界面 RPA"
|
||||
)
|
||||
@@ -1,108 +0,0 @@
|
||||
"""仿真 RPA 档位:演示 RpaVideoSession + launch_persistent_browser(需浏览器,默认开发档)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.rpa import anti_detect, artifacts, errors, launch_persistent_browser
|
||||
from jiangchang_skill_core.rpa.human_login import wait_for_captcha_pass
|
||||
from jiangchang_skill_core.rpa.video_session import RpaVideoSession
|
||||
|
||||
from util.constants import SKILL_SLUG
|
||||
from util.runtime_paths import get_skill_data_dir, get_skill_root
|
||||
|
||||
from .base import AdapterBase, ExampleItem, ExampleResult
|
||||
|
||||
|
||||
class SimRpaAdapter(AdapterBase):
|
||||
name = "sim_rpa"
|
||||
|
||||
def do_batch(self, target: str, items: list[ExampleItem]) -> ExampleResult:
|
||||
return asyncio.run(self._do_batch_async(target, items))
|
||||
|
||||
async def _do_batch_async(self, target: str, items: list[ExampleItem]) -> ExampleResult:
|
||||
example_path = os.path.join(get_skill_root(), ".env.example")
|
||||
config.ensure_env_file(SKILL_SLUG, example_path)
|
||||
|
||||
url = config.get("TARGET_BASE_URL") or target or "https://example.com"
|
||||
batch_id = f"sim_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
data_dir = get_skill_data_dir()
|
||||
profile_dir = os.path.join(data_dir, "browser-profile")
|
||||
os.makedirs(profile_dir, exist_ok=True)
|
||||
|
||||
try:
|
||||
from playwright.async_api import async_playwright
|
||||
except ImportError:
|
||||
return ExampleResult(
|
||||
ok=False,
|
||||
serial_no=None,
|
||||
error_msg="playwright not installed",
|
||||
artifacts={},
|
||||
)
|
||||
|
||||
artifact_paths: dict = {}
|
||||
async with RpaVideoSession(
|
||||
skill_slug=SKILL_SLUG,
|
||||
skill_data_dir=data_dir,
|
||||
batch_id=batch_id,
|
||||
title="技能仿真演示",
|
||||
) as video:
|
||||
video.add_step("正在打开目标页面")
|
||||
try:
|
||||
async with async_playwright() as pw:
|
||||
context = await launch_persistent_browser(
|
||||
pw,
|
||||
profile_dir=profile_dir,
|
||||
headless=config.get_bool("OPENCLAW_BROWSER_HEADLESS"),
|
||||
)
|
||||
page = context.pages[0] if context.pages else await context.new_page()
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=60_000)
|
||||
await anti_detect.human_mouse_wiggle(page)
|
||||
|
||||
search = page.locator('input[type="search"], input[name="q"]').first
|
||||
if await search.count() > 0:
|
||||
demo_text = items[0].label if items else "openclaw-demo"
|
||||
video.step("正在填写演示内容")
|
||||
await anti_detect.human_type(page, search, demo_text)
|
||||
await anti_detect.human_delay_short()
|
||||
|
||||
video.step("正在等待人工验证(如有)")
|
||||
if not await wait_for_captcha_pass(
|
||||
page,
|
||||
timeout_sec=config.get_int("HUMAN_WAIT_TIMEOUT", 180),
|
||||
):
|
||||
shot = await artifacts.capture_failure(
|
||||
page, data_dir, batch_id, "captcha"
|
||||
)
|
||||
if shot:
|
||||
artifact_paths["captcha"] = shot
|
||||
return ExampleResult(
|
||||
ok=False,
|
||||
serial_no=None,
|
||||
error_msg=errors.ERR_CAPTCHA_NEED_HUMAN,
|
||||
artifacts={**artifact_paths, **video.to_artifact_dict()},
|
||||
)
|
||||
|
||||
video.step("演示任务完成")
|
||||
await context.close()
|
||||
except Exception as exc:
|
||||
video.step("演示异常结束")
|
||||
return ExampleResult(
|
||||
ok=False,
|
||||
serial_no=None,
|
||||
error_msg=str(exc),
|
||||
artifacts={**artifact_paths, **video.to_artifact_dict()},
|
||||
)
|
||||
|
||||
arts = {"mode": "sim_rpa", "url": url, **artifact_paths, **video.to_artifact_dict()}
|
||||
if video.output_video_path:
|
||||
arts["video_path"] = video.output_video_path
|
||||
return ExampleResult(
|
||||
ok=True,
|
||||
serial_no=f"SIM-{len(items)}",
|
||||
error_msg=None,
|
||||
artifacts=arts,
|
||||
)
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""浏览器 RPA 最小用法示例(复制到新 skill 后按需改)。
|
||||
|
||||
演示标准流程:
|
||||
1. config.ensure_env_file 首次落盘 .env
|
||||
2. launch_persistent_browser 启动 stealth persistent context
|
||||
3. 拟人操作(mouse_wiggle / human_type / human_click)
|
||||
4. 验证码 HITL 等待
|
||||
5. 失败 capture_failure 截图
|
||||
|
||||
运行(需系统 Chrome/Edge + playwright 包):
|
||||
python scripts/service/example_browser_rpa.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
_scripts_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _scripts_dir not in sys.path:
|
||||
sys.path.insert(0, _scripts_dir)
|
||||
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.rpa import (
|
||||
anti_detect,
|
||||
artifacts,
|
||||
errors,
|
||||
launch_persistent_browser,
|
||||
wait_for_captcha_pass,
|
||||
)
|
||||
from util.constants import SKILL_SLUG
|
||||
from util.runtime_paths import get_skill_data_dir, get_skill_root
|
||||
|
||||
|
||||
async def demo() -> int:
|
||||
example_path = os.path.join(get_skill_root(), ".env.example")
|
||||
config.ensure_env_file(SKILL_SLUG, example_path)
|
||||
|
||||
url = config.get("TARGET_BASE_URL") or "https://example.com"
|
||||
data_dir = get_skill_data_dir()
|
||||
profile_dir = os.path.join(data_dir, "browser-profile")
|
||||
os.makedirs(profile_dir, exist_ok=True)
|
||||
batch_id = "demo"
|
||||
|
||||
try:
|
||||
from playwright.async_api import async_playwright
|
||||
except ImportError:
|
||||
print("ERROR: playwright not installed")
|
||||
return 1
|
||||
|
||||
async with async_playwright() as pw:
|
||||
context = await launch_persistent_browser(
|
||||
pw,
|
||||
profile_dir=profile_dir,
|
||||
headless=config.get_bool("OPENCLAW_BROWSER_HEADLESS"),
|
||||
)
|
||||
page = context.pages[0] if context.pages else await context.new_page()
|
||||
try:
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=60_000)
|
||||
await anti_detect.human_mouse_wiggle(page)
|
||||
|
||||
search = page.locator('input[type="search"], input[name="q"]').first
|
||||
if await search.count() > 0:
|
||||
await anti_detect.human_type(page, search, "openclaw-rpa-demo")
|
||||
await anti_detect.human_click(page, selector='input[type="search"]')
|
||||
|
||||
if not await wait_for_captcha_pass(
|
||||
page,
|
||||
timeout_sec=config.get_int("HUMAN_WAIT_TIMEOUT", 180),
|
||||
):
|
||||
shot = await artifacts.capture_failure(page, data_dir, batch_id, "captcha")
|
||||
print(errors.ERR_CAPTCHA_NEED_HUMAN, shot or "")
|
||||
return 1
|
||||
|
||||
print("OK browser RPA demo finished")
|
||||
return 0
|
||||
except Exception as exc:
|
||||
shot = await artifacts.capture_failure(page, data_dir, batch_id, "error")
|
||||
print(f"ERROR: {exc}", shot or "")
|
||||
return 1
|
||||
finally:
|
||||
await context.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(asyncio.run(demo()))
|
||||
Reference in New Issue
Block a user