当前 Agent 框架已趋于成熟——OpenClaw、Hermes 等方案能力稳定,第三方生态(如 QClaw、WorkBuddy)也在快速涌现。但多数 Agent 仍停留在「能聊」的阶段,离「能干活」还有距离。
本文分享一套 OpenClaw + RPA 的数字员工实现思路:用 Agent 负责理解与决策,用 RPA 负责点击与操作,让 AI 真正替你完成工作。
技术栈
- Agent:OpenClaw
- RPA:影刀 RPA
二者可以互补:
- OpenClaw:绑定企业微信等办公 IM,承担对话入口与任务理解
- 影刀 RPA:负责界面点击、表单填写、流程自动化等「手」上的操作
组合起来,就是「OpenClaw 想、RPA 做」的数字员工模式。
安装与绑定说明
影刀 RPA、OpenClaw 的安装配置,以及与企业微信等办公软件的绑定步骤,网上已有大量教程,本文不再赘述。
影刀 CLI 与 OpenClaw 的对接思路
影刀在最新版本中已开放 CLI,但目前 仅企业版可用,个人版尚未上线。
企业微信 / 用户
↓
OpenClaw Agent(理解任务、编排)
↓ WebSocket / CLI
yingdao_openclaw_send.py ←→ 影刀 RPA 流程
↓
openYingDaoRPA.py / openRPA.py (拉起 RPA 客户端)
↓
yingdao_flow_python.py (UI 自动化操作 ERP)
↓
yingdao_extract_sd.py / _win_ocr.ps1 (OCR 识别结果)
在没有 CLI 的前提下,仍可通过脚本让 OpenClaw 拉起影刀 RPA,并结合 OCR 识别 实现模拟点击,从而完成自动化操作。示例代码如下:
根据自己的安装地址修改,默认为库存查询,使用方式为:告诉open claw 如果我需要执行rpa任务,则open claw去执行:python C:\Users\xxxx\.openclaw\workspace\skills\yingdao-rpa\scripts\openRPA.py 我这边是做成了skill。也可以直接执行下面代码拉起RPA。
import io
import os
import subprocess
import sys
import time
from pathlib import Path
from typing import Optional
from pywinauto import Application, Desktop
from pywinauto.findwindows import ElementNotFoundError
from pywinauto.mouse import click as mouse_click
FLOW_NAME = "库存查询"
def _ensure_stdio_safe() -> None:
"""技能/OpenClaw 常在 GBK 控制台运行:强制 stdout/stderr 用 utf-8 + replace,避免 print Unicode 崩溃。"""
for name in ("stdout", "stderr"):
stream = getattr(sys, name, None)
if stream is None:
continue
try:
if hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8", errors="replace")
continue
except Exception:
pass
try:
buf = getattr(stream, "buffer", None)
if buf is not None:
setattr(
sys,
name,
io.TextIOWrapper(
buf,
encoding="utf-8",
errors="replace",
line_buffering=True,
),
)
except Exception:
pass
def _flush_stdio() -> None:
for stream in (sys.stdout, sys.stderr):
try:
stream.flush()
except Exception:
pass
def _safe_print(*parts: object) -> None:
try:
msg = " ".join(_safe_console_str(p) for p in parts)
print(msg)
except Exception:
print("[输出已省略:控制台编码异常]", file=sys.stderr)
_flush_stdio()
def _resolve_flow_name(flow_name: Optional[str]) -> str:
"""优先:参数 > 环境变量 FLOW_NAME > 模块默认常量。"""
if flow_name is not None and str(flow_name).strip():
return str(flow_name).strip()
env = os.environ.get("FLOW_NAME", "").strip()
if env:
return env
return FLOW_NAME
# 在流程名上右键后,用相对坐标点击右键菜单里的「运行」(菜单在下方,Y 为正向下)
# 若点偏,只改这两个数即可
RUN_MENU_OFFSET_X = 72
RUN_MENU_OFFSET_Y = 24
def _env_bool(name: str) -> bool:
return os.environ.get(name, "").strip().lower() in ("1", "true", "yes", "on")
def _should_skip_running_check() -> bool:
"""默认跳过:影刀常驻后台,不再重复「是否在运行 / 是否启动」。"""
if _env_bool("YINGDAO_CHECK_RUNNING"):
return False
if os.environ.get("YINGDAO_SKIP_RUNNING_CHECK", "").strip().lower() in (
"0",
"false",
"no",
"off",
):
return False
return True
def _apply_speed_profile() -> None:
"""YINGDAO_FAST=1 或 --fast:缩短 exists/激活/右键后等待(不强制关掉 UIA,避免少根 HWND 扫不到)。"""
if not _env_bool("YINGDAO_FAST"):
return
os.environ.setdefault("YINGDAO_WRAP_EXISTS_TIMEOUT", "0.22")
os.environ.setdefault("YINGDAO_AFTER_FOCUS_SEC", "0.12")
os.environ.setdefault("YINGDAO_AFTER_RCLICK_SEC", "0.22")
def _wrap_exists_timeout() -> float:
try:
return float(os.environ.get("YINGDAO_WRAP_EXISTS_TIMEOUT", "0.2"))
except ValueError:
return 0.2
def _after_focus_sleep() -> float:
try:
return float(os.environ.get("YINGDAO_AFTER_FOCUS_SEC", "0.18"))
except ValueError:
return 0.18
def _after_rclick_sleep() -> float:
try:
return float(os.environ.get("YINGDAO_AFTER_RCLICK_SEC", "0.28"))
except ValueError:
return 0.28
def candidate_exes() -> list[Path]:
local = os.environ.get("LOCALAPPDATA", "")
prog = os.environ.get("PROGRAMFILES", "")
prog86 = os.environ.get("PROGRAMFILES(X86)", "")
userprofile = Path(os.environ.get("USERPROFILE", str(Path.home())))
env_exe = os.environ.get("YINGDAO_EXE", "").strip()
return [
Path(env_exe) if env_exe else Path(),
Path(r"C:\Users\user_ff\ShadowBot\ShadowBot.exe"),
Path(r"C:\Users\T490S\ShadowBot\ShadowBot.exe"),
userprofile / "ShadowBot" / "ShadowBot.exe",
userprofile / "AppData" / "Local" / "ShadowBot" / "ShadowBot.exe",
Path(local) / "ShadowBot" / "ShadowBot.exe",
Path(prog) / "ShadowBot" / "ShadowBot.exe",
Path(prog86) / "ShadowBot" / "ShadowBot.exe",
]
def _safe_console_str(text: object) -> str:
"""GBK 等控制台打印含零宽字符时会崩;去掉后再编码输出。"""
s = str(text or "")
for ch in ("\u200b", "\u200c", "\u200d", "\ufeff"):
s = s.replace(ch, "")
enc = getattr(sys.stdout, "encoding", None) or "utf-8"
try:
return s.encode(enc, errors="replace").decode(enc, errors="replace")
except Exception:
return repr(s)
def _yingdao_title_hwnds() -> list[int]:
"""标题含「影刀」的可见顶层 HWND(Win32 枚举,轻量)。"""
hits: list[int] = []
try:
import ctypes
from ctypes import wintypes
user32 = ctypes.windll.user32
@ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
def _enum(hwnd, _lparam):
if not user32.IsWindowVisible(hwnd):
return True
n = user32.GetWindowTextLengthW(hwnd)
if n <= 0:
return True
buf = ctypes.create_unicode_buffer(n + 2)
user32.GetWindowTextW(hwnd, buf, n + 2)
t = buf.value.strip()
if "影刀" in t and "登录" not in t:
hits.append(int(hwnd))
return True
user32.EnumWindows(_enum, 0)
except Exception:
pass
return hits
def _yingdao_process_window_hwnds() -> list[int]:
"""ShadowBot 进程内标题含影刀的窗口(补充 EnumWindows 漏掉的)。"""
out: list[int] = []
try:
exe = resolve_yingdao_exe()
except FileNotFoundError:
return out
# Win32 连接通常更快;若已枚举到标题匹配的 HWND 则不再连 UIA(省一次 connect)
try:
cto = int(float(os.environ.get("YINGDAO_CONNECT_TIMEOUT", "3")))
except ValueError:
cto = 3
cto = max(1, min(cto, 30))
for backend in ("win32", "uia"):
try:
app = Application(backend=backend).connect(path=str(exe), timeout=cto)
for w in app.windows():
try:
title = (w.window_text() or "").strip()
h = int(w.handle)
except Exception:
continue
if "影刀" in title and "登录" not in title:
out.append(h)
except Exception:
continue
if out:
break
return list(dict.fromkeys(out))
def _enum_child_hwnds(parent_hwnd: int, *, max_children: int = 128) -> list[int]:
"""一层子窗口句柄(客户区、分层窗等常在子 HWND 上)。"""
found: list[int] = []
try:
import ctypes
from ctypes import wintypes
user32 = ctypes.windll.user32
@ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
def cb(hwnd, _lparam):
if len(found) >= max_children:
return False
found.append(int(hwnd))
return True
user32.EnumChildWindows(wintypes.HWND(parent_hwnd), cb, 0)
except Exception:
pass
return found
def _expand_hwnds_for_scan(base_hwnds: list[int]) -> list[int]:
"""子 HWND 优先(列表多在客户区子窗),最后再扫主壳,减少无谓的全树遍历。"""
cap = int(os.environ.get("YINGDAO_SCAN_HWND_CAP", "256"))
per_parent = int(os.environ.get("YINGDAO_CHILD_HWND_PER_PARENT", "96"))
out: list[int] = []
seen: set[int] = set()
for h in base_hwnds:
children = _enum_child_hwnds(h, max_children=per_parent)
for ch in children:
if ch not in seen:
seen.add(ch)
out.append(ch)
if len(out) >= cap:
return out
if h not in seen:
seen.add(h)
out.append(h)
if len(out) >= cap:
break
return out
def _wrap_scan_roots(hwnds: list[int]) -> list:
"""
同一 HWND 用 Win32 + UIA 各挂一份:Electron/WPF 壳上 UIA 常为空,Win32 仍有子控件。
顺序默认 win32 优先(可用环境变量 YINGDAO_UI_BACKENDS=win32,uia)。
"""
raw = os.environ.get("YINGDAO_UI_BACKENDS", "win32,uia")
backends = [b.strip() for b in raw.split(",") if b.strip()]
if not backends:
backends = ["win32", "uia"]
roots: list = []
seen_pair: set[tuple[int, str]] = set()
for h in hwnds:
for backend in backends:
key = (h, backend)
if key in seen_pair:
continue
try:
w = Desktop(backend=backend).window(handle=h)
if w.exists(timeout=_wrap_exists_timeout()):
seen_pair.add(key)
roots.append(w)
except Exception:
continue
return roots
def _resolve_yingdao_roots() -> list:
"""候选 HWND = 标题匹配(优先)+ 进程内窗口(兜底),展开一级子窗;再按 Win32/UIA 包装为扫描根。"""
base = _yingdao_title_hwnds()
if not base:
base = _yingdao_process_window_hwnds()
else:
base = list(dict.fromkeys(base))
if not base:
return []
expanded = _expand_hwnds_for_scan(base)
return _wrap_scan_roots(expanded)
def _existing_yingdao_window(*, verbose: bool = True):
"""若影刀已打开,返回用于展示的主窗口句柄包装;否则 None。"""
if verbose:
_safe_print("正在查找影刀主窗口…")
order = list(dict.fromkeys(_yingdao_title_hwnds() + _yingdao_process_window_hwnds()))
for h in order:
for backend in ("win32", "uia"):
try:
w = Desktop(backend=backend).window(handle=h)
if w.exists(timeout=_wrap_exists_timeout()):
return w
except Exception:
continue
return None
def resolve_yingdao_exe() -> Path:
for p in candidate_exes():
if p and p.is_file():
return p.resolve()
raise FileNotFoundError("未找到影刀主程序 ShadowBot.exe")
def launch_yingdao() -> Path:
exe = resolve_yingdao_exe()
subprocess.Popen([str(exe)], cwd=str(exe.parent), shell=False)
return exe
def wait_yingdao_main_window(timeout: int = 40):
start = time.time()
first_scan = True
while time.time() - start < timeout:
w = _existing_yingdao_window(verbose=first_scan)
first_scan = False
if w is not None:
return w
left = max(0, int(timeout - (time.time() - start)))
_safe_print(f"尚未发现影刀主窗口,约 {left}s 内再扫…")
time.sleep(2)
raise TimeoutError("等待影刀主窗口超时")
def _window_wait_seconds() -> int:
try:
return int(os.environ.get("YINGDAO_WINDOW_TIMEOUT", "90"))
except ValueError:
return 90
def _norm(s: str) -> str:
return "".join(str(s or "").split())
def _root_debug_label(root) -> str:
try:
h = int(root.handle)
except Exception:
h = -1
try:
bk = root.backend.name # type: ignore[attr-defined]
except Exception:
bk = "?"
try:
tx = (root.window_text() or "")[:40]
except Exception:
tx = ""
return f"hwnd={h} {bk} 「{_safe_console_str(tx)}」"
# 流程列表控件多时加大上限;仍可能被虚拟列表截断(见下方说明)
_FLOW_SCAN_MAX = int(os.environ.get("YINGDAO_FLOW_SCAN_MAX", "25000"))
def _find_flow_in_roots(roots: list, flow_name: str):
"""在多个候选根中找包含流程名的控件;规范化后全等则立即返回,避免扫完整棵树。"""
_safe_print(
f"正在流程列表中定位控件(共 {len(roots)} 个候选根,命中即停)…"
)
t0 = time.perf_counter()
best = None
best_nt_len = 10**9
needle = _norm(flow_name)
needle_len = len(needle)
for root in roots:
try:
it = root.descendants()
except Exception:
continue
for i, ctrl in enumerate(it):
if i > _FLOW_SCAN_MAX:
break
try:
text = (ctrl.window_text() or "").strip()
except Exception:
continue
if not text:
continue
nt = _norm(text)
if needle not in nt:
continue
nlen = len(nt)
# 全名一致:不可能更短,立刻返回(省掉后续上万次枚举)
if nlen == needle_len:
if _env_bool("YINGDAO_TIMING"):
_safe_print(
f"[timing] 流程扫描 {time.perf_counter() - t0:.2f}s (精确命中)"
)
return ctrl
if nlen < best_nt_len:
best_nt_len = nlen
best = ctrl
if _env_bool("YINGDAO_TIMING"):
_safe_print(f"[timing] 流程扫描 {time.perf_counter() - t0:.2f}s")
return best
def debug_flow_candidates(roots: list, flow_name: str, *, max_lines: int = 120) -> None:
"""找不到流程时调用:打印名称中含关键字片段的控件(排查虚拟列表/名称不一致)。"""
needle = _norm(flow_name)
hint = needle[:2] if len(needle) >= 2 else needle
_safe_print(
f"[调试] 扫描 {len(roots)} 个候选根的子控件(每个最多 {_FLOW_SCAN_MAX} 个),"
f"查找与「{flow_name}」相关的文案…"
)
hits: list[str] = []
total_scanned = 0
for ri, root in enumerate(roots):
try:
it = root.descendants()
except Exception as e:
_safe_print(f"[调试] 候选根#{ri + 1} 无法枚举子控件:", e)
continue
scanned = 0
for i, ctrl in enumerate(it):
if i > _FLOW_SCAN_MAX:
break
scanned = i + 1
try:
text = (ctrl.window_text() or "").strip()
except Exception:
continue
if not text:
continue
nt = _norm(text)
if needle and needle in nt:
hits.append(text)
elif hint and hint in nt:
hits.append(text)
total_scanned += scanned
_safe_print(
f"[调试] 候选根#{ri + 1} {_root_debug_label(root)} 扫描控件数: {scanned}"
)
_safe_print(f"[调试] 共扫描控件数: {total_scanned}")
if hits:
uniq = list(dict.fromkeys(hits))
_safe_print("[调试] 名称中包含流程关键字或前两字的控件(截断显示):")
for i, t in enumerate(uniq):
if i >= max_lines:
_safe_print(f"… 其余省略(共 {len(uniq)} 条去重)")
break
_safe_print(f" - {_safe_console_str(t[:200])}")
else:
_safe_print(
"[调试] 未命中任何文案。常见原因:"
"① 流程在折叠分组里或列表未滚动,虚拟列表不暴露未显示行;"
"② 界面名称与参数不完全一致(空格、全角符号);"
"③ 当前焦点不在「流程」页或左侧列表。"
)
if total_scanned == 0:
_safe_print(
"[调试] 若所有候选根扫描数均为 0:界面可能为自绘/Chromium 且未暴露 Win32 子控件文案,"
"脚本无法通过控件名启动流程;请改用影刀自带命令行/HTTP 调度或使用图像/OCR 定位。"
)
def _prepare_main_window(window, *, verbose: bool = True) -> None:
"""任务栏有图标但主窗口常最小化:尽量还原并抢前台,便于右键坐标命中。"""
if verbose:
_safe_print("尝试还原/激活影刀主窗口…")
for fn in ("restore", "maximize"):
try:
m = getattr(window, fn, None)
if callable(m):
m()
except Exception:
pass
try:
window.set_focus()
except Exception:
pass
time.sleep(_after_focus_sleep())
def run_flow(roots: list, flow_name: str) -> None:
# 同一 hwnd 的 win32/uia 两份包装只还原一次
prepared: set[int] = set()
first_verbose = True
for w in roots:
try:
hw = int(w.handle)
except Exception:
hw = id(w)
if hw in prepared:
continue
_prepare_main_window(w, verbose=first_verbose)
first_verbose = False
prepared.add(hw)
target = _find_flow_in_roots(roots, flow_name)
if not target:
raise ElementNotFoundError(f"未在界面中找到流程: {flow_name}")
rect = target.rectangle()
cx = int((rect.left + rect.right) / 2)
cy = int((rect.top + rect.bottom) / 2)
mouse_click(button="right", coords=(cx, cy))
time.sleep(_after_rclick_sleep())
mouse_click(
button="left",
coords=(cx + RUN_MENU_OFFSET_X, cy + RUN_MENU_OFFSET_Y),
)
def main(flow_name: Optional[str] = None, *, debug_only: bool = False) -> None:
_ensure_stdio_safe()
_apply_speed_profile()
name = _resolve_flow_name(flow_name)
attach_only = os.environ.get("YINGDAO_ATTACH_ONLY", "").strip() in (
"1",
"true",
"yes",
"on",
)
if _should_skip_running_check():
_safe_print("假定影刀已在后台,跳过运行检测,直接定位流程列表…")
else:
window = _existing_yingdao_window(verbose=True)
if window is not None:
_safe_print("已检测到影刀已运行,跳过启动新进程:", window.window_text())
elif attach_only:
raise RuntimeError(
"未找到已打开的影刀主窗口,且已设置 YINGDAO_ATTACH_ONLY=1(不启动新实例)。请先手动打开影刀。"
)
else:
exe = launch_yingdao()
_safe_print("已启动影刀:", exe)
time.sleep(3)
wait_yingdao_main_window(timeout=_window_wait_seconds())
roots = _resolve_yingdao_roots()
if not roots:
raise RuntimeError(
"无法枚举影刀顶层窗口(ShadowBot)。请确认影刀已登录主界面后重试。"
)
try:
wt0 = roots[0].window_text() or ""
except Exception:
wt0 = ""
if not str(wt0).strip():
wt0 = "(无标题子窗)"
_safe_print(
"找到窗口:",
wt0,
f"(候选扫描根 {len(roots)} 个;设 YINGDAO_FAST=1 或 --fast 可再加速)",
)
if debug_only:
debug_flow_candidates(roots, name)
return
try:
run_flow(roots, name)
except ElementNotFoundError:
_safe_print("未找到流程,以下为调试扫描(也可单独运行: python dianJi.py --debug 流程名)…")
debug_flow_candidates(roots, name)
raise
_safe_print("已触发运行:", name)
if __name__ == "__main__":
_ensure_stdio_safe()
import argparse
parser = argparse.ArgumentParser(description="启动影刀并在指定流程上右键运行")
parser.add_argument(
"flow_name",
nargs="?",
default=None,
help="流程名称(与界面列表一致);可省略,此时使用环境变量 FLOW_NAME 或脚本内默认",
)
parser.add_argument(
"--debug",
action="store_true",
help="只扫描 UI 文案并打印候选,不右键运行(排查找不到流程)",
)
parser.add_argument(
"--fast",
action="store_true",
help="快速模式:更短 exists/激活/右键等待(仍扫 Win32+UIA;想极限省时可设 YINGDAO_UI_BACKENDS=win32 自行承担漏检)",
)
parser.add_argument(
"--check-running",
action="store_true",
help="启用「查找主窗口/未运行则启动」检测(默认已跳过,影刀常驻后台时不必开)",
)
parser.add_argument(
"--skip-running-check",
action="store_true",
help="(已默认开启) 跳过运行检测;保留此参数仅为兼容旧调用",
)
args = parser.parse_args()
if args.fast:
os.environ["YINGDAO_FAST"] = "1"
if args.check_running:
os.environ["YINGDAO_CHECK_RUNNING"] = "1"
main(args.flow_name, debug_only=args.debug)
RPA 应用执行完毕后,桌面右下角会弹出执行完成提示(默认匹配「运行成功」)。

弹窗不关闭时,后续流程无法再次触发,因此应在「运行应用」之后追加一步关闭弹窗:
from __future__ import annotations
import argparse
import ctypes
import time
from ctypes import wintypes
from typing import Optional
from pywinauto import Application
from pywinauto.mouse import click as mouse_click, move as mouse_move
user32 = ctypes.windll.user32
SM_CXSCREEN, SM_CYSCREEN = 0, 1
class _RECT(ctypes.Structure):
_fields_ = (
("left", wintypes.LONG),
("top", wintypes.LONG),
("right", wintypes.LONG),
("bottom", wintypes.LONG),
)
def _screen() -> tuple[int, int]:
return int(user32.GetSystemMetrics(SM_CXSCREEN)), int(user32.GetSystemMetrics(SM_CYSCREEN))
def _corner_toast_ok(r: _RECT, sw: int, sh: int) -> bool:
w, h = r.right - r.left, r.bottom - r.top
if w < 80 or h < 24 or w > 1000 or h > 500:
return False
cx = (r.left + r.right) // 2
cy = (r.top + r.bottom) // 2
return cx >= int(sw * 0.48) and cy >= int(sh * 0.42)
def _enum_top_level_hwnds() -> list[int]:
out: list[int] = []
@ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
def cb(hwnd, _lp):
if user32.IsWindowVisible(hwnd) and user32.GetParent(hwnd) == 0:
out.append(int(hwnd))
return True
user32.EnumWindows(cb, 0)
return out
def _rect(hwnd: int) -> Optional[_RECT]:
r = _RECT()
if not user32.GetWindowRect(hwnd, ctypes.byref(r)):
return None
return r
def _uia_window(hwnd: int):
return Application(backend="uia").connect(handle=hwnd).window(handle=hwnd)
def _find(keyword: str) -> Optional[object]:
sw, sh = _screen()
for hwnd in _enum_top_level_hwnds():
r = _rect(hwnd)
if r is None or not _corner_toast_ok(r, sw, sh):
continue
try:
top = _uia_window(hwnd)
except Exception:
continue
if keyword in (top.window_text() or ""):
return top
for i, c in enumerate(top.descendants(control_type="Text")):
if i >= 400:
break
if keyword in (c.window_text() or ""):
try:
return c.top_level_parent()
except Exception:
return top
return None
def _click(x: int, y: int) -> None:
print(f"点击 ({x}, {y})")
mouse_move(coords=(x, y))
time.sleep(0.12)
mouse_click(button="left", coords=(x, y))
def _dismiss(w: object, keyword: str) -> None:
try:
w.set_focus()
except Exception:
pass
try:
b = w.child_window(title="关闭", control_type="Button")
if b.exists(timeout=0.35):
r = b.rectangle()
_click((r.left + r.right) // 2, (r.top + r.bottom) // 2)
return
except Exception:
pass
try:
for i, c in enumerate(w.descendants(control_type="Text")):
if i >= 400:
break
if keyword in (c.window_text() or ""):
r = c.rectangle()
_click(r.left + 315, r.top - 68)
return
except Exception:
pass
rect = w.rectangle()
_click(rect.right + 1, rect.top - 6)
def run(keyword: str, timeout: float, interval: float) -> bool:
deadline = time.time() + timeout
while time.time() < deadline:
w = _find(keyword)
if w is not None:
_dismiss(w, keyword)
return True
time.sleep(interval)
return False
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--keyword", default="运行成功")
p.add_argument("--timeout", type=float, default=12.0)
p.add_argument("--interval", type=float, default=0.25)
args = p.parse_args()
if run(args.keyword, args.timeout, args.interval):
print("已处理。")
else:
print("未在右下角附近找到该提示。")
if __name__ == "__main__":
main()
到这里,从对话到执行的完整闭环已经打通。后续若有新需求,只需录制对应的 RPA 流程并接入 Agent,即可实现:你说一句话,AI 帮你把活干完。

58

被折叠的 条评论
为什么被折叠?



