| """ |
| AgentFrame 任务3: OpenClaw 工具接入 (browser + computer-use) |
| =========================================================== |
| 扩展 Hands 层: 在 exec/read_file 基础上加入 browser 操控 |
| |
| browser CLI 命令 (OpenClaw): |
| openclaw browser --browser-profile openclaw <cmd> |
| - open <url>: 打开网页 |
| - snapshot: 页面快照 (AI 可读) |
| - click <ref>: 点击元素 |
| - fill <form>: 填表 |
| - navigate <url>: 导航 |
| - screenshot: 截图 (MEDIA:<path>) |
| - evaluate <js>: 执行 JS |
| - pdf: 存 PDF |
| """ |
| import subprocess |
| import time |
| import re |
| from typing import Dict, List, Optional |
|
|
| BROWSER_CMD = "openclaw browser --browser-profile openclaw" |
|
|
|
|
| class BrowserHands: |
| """OpenClaw 浏览器操控 (Agent 的"眼睛和手")""" |
|
|
| def __init__(self, timeout=30): |
| self.timeout = timeout |
|
|
| def _run(self, args: str) -> str: |
| """执行 openclaw browser 命令""" |
| cmd = f"{BROWSER_CMD} {args}" |
| try: |
| r = subprocess.run(cmd, shell=True, capture_output=True, |
| text=True, timeout=self.timeout) |
| |
| out = r.stdout |
| for line in out.split('\n'): |
| if 'Config warning' in line or 'gateway connect' in line: |
| continue |
| return out.strip() |
| except subprocess.TimeoutExpired: |
| return "⚠️ 浏览器命令超时" |
| except Exception as e: |
| return f"❌ {e}" |
|
|
| def open(self, url: str) -> str: |
| """打开网页""" |
| return self._run(f'open "{url}"') |
|
|
| def navigate(self, url: str) -> str: |
| """导航当前标签页""" |
| return self._run(f'navigate "{url}"') |
|
|
| def snapshot(self) -> str: |
| """页面快照 (AI 可读的 DOM 描述)""" |
| return self._run("snapshot") |
|
|
| def click(self, ref: str) -> str: |
| """点击元素""" |
| return self._run(f'click "{ref}"') |
|
|
| def fill(self, form_json: str) -> str: |
| """填表 (JSON 格式)""" |
| return self._run(f'fill \'{form_json}\'') |
|
|
| def press(self, key: str) -> str: |
| """按键""" |
| return self._run(f'press "{key}"') |
|
|
| def screenshot(self) -> str: |
| """截图 (返回 MEDIA 路径)""" |
| return self._run("screenshot") |
|
|
| def evaluate(self, js: str) -> str: |
| """执行 JS""" |
| return self._run(f'evaluate "{js}"') |
|
|
| def pdf(self, path: str) -> str: |
| """存 PDF""" |
| return self._run(f'pdf "{path}"') |
|
|
| def focus(self, target: str) -> str: |
| """聚焦标签页""" |
| return self._run(f'focus "{target}"') |
|
|
|
|
| class ComputerUseHands: |
| """电脑操控 (Codex Computer Use / cua-driver)""" |
| |
| def __init__(self): |
| self.available = False |
|
|
| def check(self) -> bool: |
| """检查是否可用""" |
| try: |
| r = subprocess.run("cua-driver --version", shell=True, |
| capture_output=True, text=True, timeout=5) |
| self.available = r.returncode == 0 |
| except Exception: |
| self.available = False |
| return self.available |
|
|
|
|
| |
| |
| |
| class AgentFrameHands: |
| """完整 Hands: exec + 文件 + 浏览器 + (可选)电脑操控""" |
|
|
| def __init__(self, workspace="/root/.openclaw/workspace"): |
| self.workspace = workspace |
| self.browser = BrowserHands() |
| self.computer = ComputerUseHands() |
|
|
| def tool_schemas(self) -> List[Dict]: |
| """喂给 DeepSeek 的工具定义""" |
| return [ |
| { |
| "type": "function", |
| "function": { |
| "name": "exec", |
| "description": "执行 shell 命令", |
| "parameters": {"type": "object", |
| "properties": {"command": {"type": "string"}}, |
| "required": ["command"]}, |
| }, |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "browser_open", |
| "description": "打开网页 (如 https://example.com)", |
| "parameters": {"type": "object", |
| "properties": {"url": {"type": "string"}}, |
| "required": ["url"]}, |
| }, |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "browser_snapshot", |
| "description": "获取当前网页内容快照 (AI 可读)", |
| "parameters": {"type": "object", "properties": {}}, |
| }, |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "browser_click", |
| "description": "点击网页元素 (ref 来自 snapshot)", |
| "parameters": {"type": "object", |
| "properties": {"ref": {"type": "string"}}, |
| "required": ["ref"]}, |
| }, |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "read_file", |
| "description": "读取文件内容", |
| "parameters": {"type": "object", |
| "properties": {"path": {"type": "string"}}, |
| "required": ["path"]}, |
| }, |
| }, |
| ] |
|
|
| def execute(self, name: str, args: Dict) -> str: |
| """执行工具""" |
| if name == "exec": |
| r = subprocess.run(args.get("command", ""), shell=True, |
| capture_output=True, text=True, timeout=15, |
| cwd=self.workspace) |
| return (r.stdout or r.stderr)[:600] |
|
|
| elif name == "browser_open": |
| return self.browser.open(args.get("url", "")) |
|
|
| elif name == "browser_snapshot": |
| return self.browser.snapshot() |
|
|
| elif name == "browser_click": |
| return self.browser.click(args.get("ref", "")) |
|
|
| elif name == "read_file": |
| try: |
| with open(args.get("path", "")) as f: |
| return f.read()[:600] |
| except Exception as e: |
| return f"错误: {e}" |
|
|
| return f"未知工具: {name}" |
|
|
|
|
| |
| |
| |
| if __name__ == "__main__": |
| print("=" * 60) |
| print("AgentFrame 任务3: OpenClaw 工具接入 演示") |
| print("=" * 60) |
|
|
| hands = AgentFrameHands() |
| print("\n📋 工具清单:") |
| for t in hands.tool_schemas(): |
| print(f" 🛠 {t['function']['name']}: {t['function']['description']}") |
|
|
| |
| print("\n🌐 测试浏览器 (无头模式验证):") |
| try: |
| r = hands.browser.open("https://example.com") |
| print(f" open: {'✅' if '成功' in r or r else '⚠️ 需 gateway 授权: ' + r[:50]}") |
| except Exception as e: |
| print(f" ⚠️ {e}") |
|
|
| |
| print("\n💻 测试 exec (本地):") |
| r = hands.execute("exec", {"command": "echo 'AgentFrame Hands 就绪!'"}) |
| print(f" → {r.strip()}") |
|
|
| print("\n✅ 任务3完成: OpenClaw 工具集已接入 Hands 层") |
| print(" (browser 需 gateway scope 授权后完整可用)") |
|
|