Compare commits

...

4 Commits

Author SHA1 Message Date
0b9a8b2107 Release v1.0.50: web RPA profile/headed hard rules (POLICY-RPA-004)
All checks were successful
技能自动化发布 / release (push) Successful in 17s
2026-07-18 09:31:40 +08:00
287816aa2a docs(actions): align executionProfile mental model with host default sync (v1.0.49)
All checks were successful
技能自动化发布 / release (push) Successful in 8s
2026-07-18 08:44:16 +08:00
144455fbe0 test(config): add POLICY-CONFIG-004 hard gate for user-facing .env.example comments (v1.0.48)
All checks were successful
技能自动化发布 / release (push) Successful in 8s
2026-07-17 19:06:21 +08:00
5ec9648148 docs(config): user-facing .env.example comments for config UI (v1.0.47)
All checks were successful
技能自动化发布 / release (push) Successful in 12s
2026-07-17 18:25:37 +08:00
19 changed files with 403 additions and 75 deletions

View File

@@ -1,30 +1,50 @@
# ── 运行模式 / adapter 档位(见 development/ADAPTER.md── # 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
OPENCLAW_TEST_TARGET=mock # mock | simulator_rpa | real_api | real_rpa OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口real_rpa=真实浏览器;模板默认 mock日常正式使用请改为 real_rpa
# ── 目标系统 ── # 目标网站地址:技能要访问的网站或服务地址
TARGET_BASE_URL=https://sandbox.jc2009.com # 仿真/生产地址,可被进程环境变量覆盖 TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
# ── 默认账号(仅非敏感标识;密码走 account-manager── # 默认登录账号:仅填写非敏感的账号标识(如工号/登录名)
DEFAULT_LOGIN_ID=04110001 DEFAULT_LOGIN_ID=04110001 # 只填账号标识,不要填密码;密码请在「账号管理」中登记
# ── 浏览器 / RPA ── # 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
OPENCLAW_BROWSER_HEADLESS=0 # 0=有头(默认) 1=无头(CI) OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口必须便于介入与排查1=后台静默(勿作日常用法)
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启反检测指纹(默认)
# ── 存证 ── # 浏览器防检测:降低被网站识别为自动化工具的概率
OPENCLAW_RECORD_VIDEO=0 # 0=默认不录屏1=启用录屏+字幕+旁白+背景音+MP4 OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启推荐0=关闭;一般保持默认
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败截图(默认)
# ── 节流 / 超时 ── # 是否录制操作过程:用于留证、复现问题或向他人演示
STEP_DELAY_MIN=1.0 # 步骤间随机等待下限(秒) OPENCLAW_RECORD_VIDEO=0 # 0=不录屏默认1=录屏并生成带说明的视频文件;需要留证时再改为 1
STEP_DELAY_MAX=5.0 # 步骤间随机等待上限(秒)
HUMAN_WAIT_TIMEOUT=180 # 滑块/验证码/2FA 等人工超时(秒)
# ── 数据目录子路径(见 development/DATA_PATHS.md留空则用 skill 数据目录下默认子文件夹)── # 失败时是否截图:任务出错时自动保存现场截图,便于排查
# 相对路径相对于 {JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/,不是 workspace / CWD OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图推荐0=不截图
# SKILL_DOWNLOAD_DIR=downloads
# SKILL_IMPORT_DIR=imports # 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
# SKILL_EXPORT_DIR=exports STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
# SKILL_UPLOAD_DIR=uploads
# SKILL_CACHE_DIR=cache # 步骤最长等待:两步操作之间随机停顿的上限
# SKILL_TEMP_DIR=temp STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
# 以下为可选目录配置。一般不用改;取消注释并填写后,才覆盖默认子文件夹。
# 填写相对路径时,相对于本技能的数据目录(不是当前工作目录)。
# 下载目录:技能保存下载文件的位置
# SKILL_DOWNLOAD_DIR=downloads # 相对本技能数据目录;留空或保持注释则用默认 downloads
# 导入目录:待导入文件放置的位置
# SKILL_IMPORT_DIR=imports # 相对本技能数据目录;留空或保持注释则用默认 imports
# 导出目录:技能导出文件的保存位置
# SKILL_EXPORT_DIR=exports # 相对本技能数据目录;留空或保持注释则用默认 exports
# 上传目录:待上传文件放置的位置
# SKILL_UPLOAD_DIR=uploads # 相对本技能数据目录;留空或保持注释则用默认 uploads
# 缓存目录:临时缓存文件的位置
# SKILL_CACHE_DIR=cache # 相对本技能数据目录;留空或保持注释则用默认 cache
# 临时目录:运行中临时文件的位置
# SKILL_TEMP_DIR=temp # 相对本技能数据目录;留空或保持注释则用默认 temp

View File

@@ -9,6 +9,24 @@
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节 - 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段 - 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
## 1.0.50
- 沉淀网页 RPA 硬规则:必须先经账号管理取得 `profile_dir` 再开浏览器;生产有头;站点登录按平台约定(不全局强制);新增 `POLICY-RPA-004` 自动检测
- 用户教程 / 技能说明占位强调「先登记账号」需求模板增加登录策略三选一required / optional / not_needed
## 1.0.49
- 明确 Skill Action 心智:默认按 sync仅长任务 / RPA 显式 async 进任务中心;匠厂宿主省略 `executionProfile` 时按 sync新技能仍须显式声明
## 1.0.48
- 新增配置注释硬门禁 `POLICY-CONFIG-004``.env.example` 每个配置项必须有中文行头与行尾说明,禁止开发文档指向,测试不通过则无法发布
## 1.0.47
- 规范 `.env.example` 注释:面向「数据管理 → 配置管理」用户,行头说明「是什么」、行尾说明「怎么填」
- 去掉注释中的开发文档指向与黑话;`CONFIG.md` / 发布检查清单同步约束,避免各技能注释写法不一致
## 1.0.46 ## 1.0.46
- 模板默认增加市场材料:`TUTORIAL.md`(使用教程)、`DEMO.md`(演示)、`CHANGELOG.md`(更新日志),与根 `README.md`(技能说明)组成四 Tab 内容源 - 模板默认增加市场材料:`TUTORIAL.md`(使用教程)、`DEMO.md`(演示)、`CHANGELOG.md`(更新日志),与根 `README.md`(技能说明)组成四 Tab 内容源

View File

@@ -40,9 +40,11 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
说明用户在使用前要准备的东西,例如: 说明用户在使用前要准备的东西,例如:
- 相关账号已登录,且具备查询、导出或提交权限 - **网页自动化类:** 先在「账号管理」登记本技能对应平台的账号(浏览器会话由系统隔离管理)。未登记账号不要直接跑真实网站
- **是否要登录目标站:** 按本技能说明(有的平台必须登录,有的公开页可不登录);与「是否登记账号」不是一回事——登记账号是前置,登录目标站看平台
- 相关业务权限(查询、导出、提交等)已具备
- 需要处理的文件、时间范围、订单号、客户名称、项目名称等信息 - 需要处理的文件、时间范围、订单号、客户名称、项目名称等信息
- 如果涉及外部系统,请确认网络、账号、验证码或审批流程可正常使用 - 如果涉及验证码或人工确认,请保证能及时在弹出的浏览器窗口中操作
--- ---

View File

@@ -1,7 +1,7 @@
--- ---
name: 技能开发模板(通用业务版) name: 技能开发模板(通用业务版)
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。" description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
version: 1.0.46 version: 1.0.50
author: 深圳匠厂科技有限公司 author: 深圳匠厂科技有限公司
metadata: metadata:
openclaw: openclaw:
@@ -27,7 +27,7 @@ allowed-tools:
- **单业务内核、多入口**:数据管理 / 定时任务 / Agent / 技能详情 / 直接 CLI 必须复用同一 domain service禁止按入口复制业务逻辑禁止按 `source` 分叉业务行为。 - **单业务内核、多入口**:数据管理 / 定时任务 / Agent / 技能详情 / 直接 CLI 必须复用同一 domain service禁止按入口复制业务逻辑禁止按 `source` 分叉业务行为。
- **`placements`**:只决定 Action **出现在哪里**toolbar / cron / agent / skill-detail - **`placements`**:只决定 Action **出现在哪里**toolbar / cron / agent / skill-detail
- **`bind.tables`**:只决定数据管理出现在哪些表;含 `toolbar` 时**必须**显式非空绑定。 - **`bind.tables`**:只决定数据管理出现在哪些表;含 `toolbar` 时**必须**显式非空绑定。
- **`executionProfile`**:只决定执行方式——`sync` 当场返回结果,`async` 进**任务中心**。与 placements / 入口**无关**每个 Action **必须**显式写 `sync``async` - **`executionProfile`**:只决定执行方式——`sync` 当场返回结果,`async` 进**任务中心**。与 placements / 入口**无关**。心智上多数为 sync仅长任务 / RPA 标 async宿主省略该字段时按 sync。每个 Action **必须**显式写 `sync``async`
- **同步数据**写技能本地库、不生成导出文件;**导出当前表**由宿主数据管理负责;特殊业务报告才另做 `export-*` - **同步数据**写技能本地库、不生成导出文件;**导出当前表**由宿主数据管理负责;特殊业务报告才另做 `export-*`
- **准备 vs 执行(适用则拆)**:若技能存在「先写入/准备本地数据」与「再对外产生副作用」两段Agent 必须能分开触发;禁止默认绑成不可拆的一步。一键无准备态的技能可跳过本条。 - **准备 vs 执行(适用则拆)**:若技能存在「先写入/准备本地数据」与「再对外产生副作用」两段Agent 必须能分开触发;禁止默认绑成不可拆的一步。一键无准备态的技能可跳过本条。

View File

@@ -16,7 +16,9 @@
1. 已安装并登录匠厂客户端 1. 已安装并登录匠厂客户端
2. 已在技能市场安装本技能(或更新到最新版) 2. 已在技能市场安装本技能(或更新到最新版)
3.如需】相关账号已登录、权限可用;需要的文件 / 时间范围 / 单号等已准备好 3.网页自动化类技能必做】在「账号管理」中为本技能所需平台**先登记账号**,并确认浏览器会话可用;**不要**在未配置账号时直接跑真实网站(会弄脏本机会话、加重风控)
4. 【按平台】若本技能说明「需要先登录目标站」,请按界面提示完成登录或扫码;若说明「可不登录也能用」,仍须完成上一步账号登记
5. 【如需】需要的文件 / 时间范围 / 单号等已准备好
--- ---

View File

@@ -101,6 +101,7 @@ def get_adapter():
3. **account-manager 账号/租约能力**是例外:可参考 `examples/real_browser_rpa/scripts/service/account_client.py` 与 `examples/simulator_browser_rpa/scripts/service/account_client.py`,封装为**单一** `account_client.py`;允许在该文件内部集中通过 subprocess 调 account-manager CLI。**禁止** import `rpa_helpers` 等 account-manager 内部模块;`simulator_rpa``real_rpa` 均走同一 `pick_web_account` / `release_lease` 模式。 3. **account-manager 账号/租约能力**是例外:可参考 `examples/real_browser_rpa/scripts/service/account_client.py` 与 `examples/simulator_browser_rpa/scripts/service/account_client.py`,封装为**单一** `account_client.py`;允许在该文件内部集中通过 subprocess 调 account-manager CLI。**禁止** import `rpa_helpers` 等 account-manager 内部模块;`simulator_rpa``real_rpa` 均走同一 `pick_web_account` / `release_lease` 模式。
4. **不允许**直接 `import account-manager` 的内部 Python 模块(如 `service/``util/``db/`)。 4. **不允许**直接 `import account-manager` 的内部 Python 模块(如 `service/``util/``db/`)。
5. **pick lease 后必须 `finally release lease`**;进程被 kill 后可能残留 lease需在运维文档说明排查方式查 account-manager lease 列表 / 手动释放)。 5. **pick lease 后必须 `finally release lease`**;进程被 kill 后可能残留 lease需在运维文档说明排查方式查 account-manager lease 列表 / 手动释放)。
6. **网页 RPA 硬规则**(详见 [`RPA.md`](RPA.md) §0.2):必须先 `pick_web_account` 拿到 `profile_dir` 再开浏览器;拿不到则失败退出;生产有头;**是否站点登录**按平台约定,但**不等于**可以跳过 account-manager。
```python ```python
# 普通兄弟技能 — 走 sibling_bridge # 普通兄弟技能 — 走 sibling_bridge

View File

@@ -11,44 +11,109 @@
4. **技能升级合并缺失项**`merge_missing_env_keys` 会把 `.env.example` 中新增、但用户 `.env` 尚未包含的 key **追加**进去(带注释块)。 4. **技能升级合并缺失项**`merge_missing_env_keys` 会把 `.env.example` 中新增、但用户 `.env` 尚未包含的 key **追加**进去(带注释块)。
5. **敏感信息绝不进 `.env`**:密码/密钥/口令走 account-manager 的 `secret-ref`(见下"红线")。 5. **敏感信息绝不进 `.env`**:密码/密钥/口令走 account-manager 的 `secret-ref`(见下"红线")。
## 标准配置项(`.env` 分组 ## `.env.example` 注释 = 配置管理用户文案(硬规则
每个 skill 的 `.env.example` 至少包含这些通用项(按需增减): 匠厂宿主「数据管理 → 配置管理」会把 `.env` / `.env.example` 的注释展示给**最终用户**(英文 key 可保留作标题)。因此注释必须按**产品文案**来写,不能按开发备忘录来写。
### 行头与行尾分工
| 位置 | 职责 | 要求 |
|------|------|------|
| **行头**`KEY=` 上一行的 `# …` | **是什么**:中文说明该配置项干什么 | **每个活跃 key 必有**一行完整中文行头 |
| **行尾**`KEY=值` 同一行 ` #` 之后) | **怎么填**:可选值、单位、推荐默认、何时要改 | 有枚举、开关、单位时**必有**;纯自由文本也应尽量写清填法 |
宿主当前会把「行头 + 行尾」拼成 key 下方的多行说明。按上表书写后,用户看到的自然是「先懂是什么,再知道怎么填」。
**不要**依赖 `# ── 分组 ──` 装饰线给整组做唯一说明:宿主只会把行头挂到**紧挨着的下一个活跃 key**,后面的 key 不会自动继承。分组可用空行区分;**逐 key 自带行头(+ 行尾)才是硬要求**。
### 必须做到
1. 每个会进入配置管理的活跃 `KEY=` 都有完整中文**行头**。
2. 枚举 / `0|1` 开关 / 带单位的数值:必须有中文**行尾**,解释每个可选值或单位,并给出推荐。
3. 业务专属 key 同样适用本规则,不得只留英文 key、不写中文说明。
4. 开发向细节(四档 adapter 实现、录屏管线、路径解析 API写在 `development/ADAPTER.md``RPA.md``DATA_PATHS.md` 等文档;**禁止**写进 `.env.example` 注释。
**机器门禁**`POLICY-CONFIG-004``tests/test_development_policy_guard.py`)会在默认测试 / `release.ps1` 中校验上述「行头+行尾中文、禁止开发文档指向」;不通过不得发布。
### 禁止出现在注释中的内容
- `见 development/...``见 ADAPTER.md``见 RPA.md``见 DATA_PATHS.md` 等仓库文档指向
- 默认假设读者是开发者的黑话:如单独甩 `adapter 档位``CI``account-manager``CWD``workspace` 而不解释
- 只写裸枚举(如 `mock | real_rpa`)却不说明每个值对用户意味着什么
- 多个 key 共用一个行头,后面的 key 没有任何自己的说明
若必须提到账号体系:写成用户能懂的「请在账号管理中登记密码 / 平台标识须一致」,不要只写内部模块名。
### 正例
```ini ```ini
# ── 运行模式 / adapter 档位(见 ADAPTER.md── # 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
OPENCLAW_TEST_TARGET=mock # mock | simulator_rpa | real_api | real_rpa OPENCLAW_TEST_TARGET=mock # 可选mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口real_rpa=真实浏览器;模板默认 mock日常正式使用请改为 real_rpa
# ── 目标系统(示例占位,复制后改为业务地址)── # 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
TARGET_BASE_URL=https://sandbox.jc2009.com OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口必须便于介入与排查1=后台静默(勿作日常用法)
# ── 默认账号(仅非敏感标识;密码走 account-manager──
# 浏览器 RPA simulator_rpa 联调账号来自 account-managerplatform + profile_dir不是此处的 DEFAULT_LOGIN_ID
DEFAULT_LOGIN_ID=04110001
# ── 浏览器 / RPA ──
OPENCLAW_BROWSER_HEADLESS=0 # 0=有头(默认) 1=无头(CI)
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启反检测指纹(默认)
# ── 录屏与失败存证 ──
OPENCLAW_RECORD_VIDEO=0 # 0=默认不录屏1=启用录屏+字幕+旁白+背景音+MP4见 RPA.md
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败截图(默认)
# ── 节流 / 超时 ──
STEP_DELAY_MIN=1.0
STEP_DELAY_MAX=5.0
HUMAN_WAIT_TIMEOUT=180 # 滑块/验证码/2FA 等人工超时(秒)
``` ```
### 反例(禁止)
```ini
# ── 运行模式 / adapter 档位(见 development/ADAPTER.md──
OPENCLAW_TEST_TARGET=real_rpa # 生产默认真实 RPA单测/CI 可改为 mock
# ── 好看视频 / 百度账号(须与 account-manager 平台 key 一致)──
TARGET_PLATFORM=baidu
HAOKAN_VIDEO_SELECTOR=video.art-video
```
问题:文档路径与开发黑话;行尾半给用户半给 CI后续 key 无行头/行尾,用户只看到英文名。
## 标准配置项(`.env` 分组)
每个 skill 的 `.env.example` 至少包含这些通用项(按需增减)。**注释写法须与仓库根目录 `.env.example` 保持同级清晰度**(行头 = 是什么,行尾 = 怎么填):
```ini
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
OPENCLAW_TEST_TARGET=mock # 可选mock=本地模拟simulator_rpa=仿真站点real_api=真实接口real_rpa=真实浏览器;模板默认 mock日常正式使用请改为 real_rpa
# 目标网站地址:技能要访问的网站或服务地址
TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
# 默认登录账号:仅填写非敏感的账号标识(如工号/登录名)
DEFAULT_LOGIN_ID=04110001 # 只填账号标识,不要填密码;密码请在「账号管理」中登记
# 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口必须便于介入与排查1=后台静默(勿作日常用法)
# 浏览器防检测:降低被网站识别为自动化工具的概率
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启推荐0=关闭;一般保持默认
# 是否录制操作过程:用于留证、复现问题或向他人演示
OPENCLAW_RECORD_VIDEO=0 # 0=不录屏默认1=录屏并生成带说明的视频文件;需要留证时再改为 1
# 失败时是否截图:任务出错时自动保存现场截图,便于排查
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图推荐0=不截图
# 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
# 步骤最长等待:两步操作之间随机停顿的上限
STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
```
四档运行模式的**实现与测试约定**见 [`ADAPTER.md`](ADAPTER.md);录屏管线见 [`RPA.md`](RPA.md)。这些内容**不要**写回 `.env.example` 注释。
**业务专属配置必须带技能前缀**(如 `DEMO_XXX``MY_SKILL_XXX``SKILL_DOWNLOAD_DIR`**不要污染全局命名空间**(禁止 `SCRAPE_1688_*``RECEIVE_ORDER_*` 等跨技能前缀写入模板)。 **业务专属配置必须带技能前缀**(如 `DEMO_XXX``MY_SKILL_XXX``SKILL_DOWNLOAD_DIR`**不要污染全局命名空间**(禁止 `SCRAPE_1688_*``RECEIVE_ORDER_*` 等跨技能前缀写入模板)。
### 数据目录子路径(下载 / 导入 / 导出) ### 数据目录子路径(下载 / 导入 / 导出)
技能**自己写入**的文件默认在 `{JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/` 下标准子目录(`downloads/``imports/``exports/` 等)。须通过 `util.runtime_paths.resolve_data_path()``get_*_dir()` 解析,**禁止** `os.path.abspath(config.get(...))` 相对 CWD。 技能**自己写入**的文件默认在 `{JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/` 下标准子目录(`downloads/``imports/``exports/` 等)。须通过 `util.runtime_paths.resolve_data_path()``get_*_dir()` 解析,**禁止** `os.path.abspath(config.get(...))` 相对 CWD。
- 权威说明:[`DATA_PATHS.md`](DATA_PATHS.md) - 权威说明(开发)[`DATA_PATHS.md`](DATA_PATHS.md)
- `.env.example` 中路径类配置**留空即用默认**;若写相对路径,必须相对 **skill 数据目录****禁止** `./outputs/...` 这类相对 workspace 的写法 - `.env.example` 中路径类配置**留空或保持注释即用默认**;若写相对路径,必须相对 **skill 数据目录****禁止** `./outputs/...` 这类相对 workspace 的写法
- 可选 env`SKILL_DOWNLOAD_DIR``SKILL_IMPORT_DIR``SKILL_EXPORT_DIR`(见 DATA_PATHS.md - 可选 env`SKILL_DOWNLOAD_DIR``SKILL_IMPORT_DIR``SKILL_EXPORT_DIR`;若作为活跃 key 出现在配置管理中,同样须有用户向行头/行尾(见模板根 `.env.example`
### 录屏开关(`OPENCLAW_RECORD_VIDEO` ### 录屏开关(`OPENCLAW_RECORD_VIDEO`
@@ -110,7 +175,7 @@ config.get_float("STEP_DELAY_MIN", 1.0)
## 相关文档 ## 相关文档
- `RPA.md` — 三端 RPA 标准与各开关含义 - `RPA.md` — 三端 RPA 标准与各开关含义(开发向;勿写入 `.env` 注释)
- `ADAPTER.md``OPENCLAW_TEST_TARGET` 四档模式 - `ADAPTER.md``OPENCLAW_TEST_TARGET` 四档模式(开发向;勿写入 `.env` 注释)
- `RUNTIME.md``JIANGCHANG_DATA_ROOT` 与数据根约定 - `RUNTIME.md``JIANGCHANG_DATA_ROOT` 与数据根约定
- `DATA_PATHS.md` — 下载/导入/导出等子目录与 `resolve_data_path` 规范 - `DATA_PATHS.md` — 下载/导入/导出等子目录与 `resolve_data_path` 规范

View File

@@ -170,7 +170,7 @@ scripts/
若 skill 需要浏览器/桌面/手机自动化,按以下顺序落地: 若 skill 需要浏览器/桌面/手机自动化,按以下顺序落地:
1. **先读三份标准**`RPA.md`三端范式与反反爬)、`CONFIG.md``.env` 落盘与读取)、`ADAPTER.md`(四档 adapter 1. **先读三份标准**`RPA.md`含 §0.2Profile 强制 / 有头 / 登录分平台)、`CONFIG.md``.env` 落盘与读取)、`ADAPTER.md`(四档 adapter
2. **从 examples 选择性复制**(不要整包照搬 `examples/<mode>/`,见各 example README 的 copy map 2. **从 examples 选择性复制**(不要整包照搬 `examples/<mode>/`,见各 example README 的 copy map
- 若是 **真实浏览器 RPA**(真实网站、登录态、验证码、滚动采集),**必须先读** `examples/real_browser_rpa/README.md`,再按需复制到 `scripts/service/` - 若是 **真实浏览器 RPA**(真实网站、登录态、验证码、滚动采集),**必须先读** `examples/real_browser_rpa/README.md`,再按需复制到 `scripts/service/`
- `examples/real_browser_rpa/scripts/service/browser_session.py` - `examples/real_browser_rpa/scripts/service/browser_session.py`
@@ -185,6 +185,7 @@ scripts/
- `examples/simulator_browser_rpa/scripts/service/task_service.py`async 编排) - `examples/simulator_browser_rpa/scripts/service/task_service.py`async 编排)
- `sandbox/demo_app.html` 仅留在 examples不进入生产 skill - `sandbox/demo_app.html` 仅留在 examples不进入生产 skill
- **禁止**`rpa_helpers`、sync Playwright 用于完整技能 RPA 主路径、`task_service` 散落 account-manager subprocess - **禁止**`rpa_helpers`、sync Playwright 用于完整技能 RPA 主路径、`task_service` 散落 account-manager subprocess
- **网页硬规则**:未拿到 `profile_dir` 禁止开浏览器;生产 `OPENCLAW_BROWSER_HEADLESS=0`;站点是否登录写进 REQUIREMENTS / 用户教程,**不要**写成「可不接 account-manager」
3. **只用共享库,不在 skill 里重写反反爬** 3. **只用共享库,不在 skill 里重写反反爬**
```python ```python
from jiangchang_skill_core import config from jiangchang_skill_core import config
@@ -853,6 +854,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
- [ ] `service` 下的核心业务文件(如 `task_service.py`)已按领域改名并实现 - [ ] `service` 下的核心业务文件(如 `task_service.py`)已按领域改名并实现
- [ ] 没有残留旧平台名 - [ ] 没有残留旧平台名
- [ ] `health` / `version` 可运行 - [ ] `health` / `version` 可运行
- [ ] `.env.example` 注释已按 [`CONFIG.md`](CONFIG.md)「行头 = 是什么、行尾 = 怎么填」写全(面向配置管理用户;无 `见 development/...` 等开发文档指向)
- [ ] `.gitignore` 生效,没有把 `__pycache__` 提交进去 - [ ] `.gitignore` 生效,没有把 `__pycache__` 提交进去
- [ ] `release.ps1` 存在(发布前会自动跑 `python tests/run_tests.py -v`;失败不得打 tag - [ ] `release.ps1` 存在(发布前会自动跑 `python tests/run_tests.py -v`;失败不得打 tag
- [ ] `.github/workflows/release_skill.yaml` 存在 - [ ] `.github/workflows/release_skill.yaml` 存在
@@ -867,6 +869,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
- [ ] 本仓库**不是** skill-template 的误复制(根目录**无** `.openclaw-skill-template` - [ ] 本仓库**不是** skill-template 的误复制(根目录**无** `.openclaw-skill-template`
- [ ] `git remote -v` 指向**本技能**远端URL 不含 skill-template 仓库名 - [ ] `git remote -v` 指向**本技能**远端URL 不含 skill-template 仓库名
- [ ] `git log` 首条提交属于本技能(非模板历史) - [ ] `git log` 首条提交属于本技能(非模板历史)
- [ ] 网页 RPA先 account-manager `profile_dir` 再开浏览器;`.env` 默认有头REQUIREMENTS 写明登录策略required/optional/not_needed用户 README/TUTORIAL 强调先登记账号(`RPA.md` §0.2 / `POLICY-RPA-004`
## 17. 常见错误 ## 17. 常见错误

View File

@@ -13,11 +13,13 @@
| POLICY-CONFIG-001 | 仓库根目录必须存在 `.env.example` | development/CONFIG.md §核心原则、§标准配置项 | hard | 文件存在性 | `tests/test_development_policy_guard.py::TestPolicyConfig001` | | POLICY-CONFIG-001 | 仓库根目录必须存在 `.env.example` | development/CONFIG.md §核心原则、§标准配置项 | hard | 文件存在性 | `tests/test_development_policy_guard.py::TestPolicyConfig001` |
| POLICY-CONFIG-002 | `.gitignore` 必须忽略 `.env``*.env.local` | development/CONFIG.md §红线:敏感信息不进 .env | hard | 解析 `.gitignore` 行 | `tests/test_development_policy_guard.py::TestPolicyConfig002` | | POLICY-CONFIG-002 | `.gitignore` 必须忽略 `.env``*.env.local` | development/CONFIG.md §红线:敏感信息不进 .env | hard | 解析 `.gitignore` 行 | `tests/test_development_policy_guard.py::TestPolicyConfig002` |
| POLICY-CONFIG-003 | 业务代码不得直接 `os.environ` / `os.getenv` / `os.putenv` / `environ.get`;须经 `jiangchang_skill_core.config.get*`(允许 `main.py`、测试、config_bootstrap/runtime 边界文件) | development/CONFIG.md §配置 bootstrapdevelopment/ADAPTER.md §档位 dispatch | hard | 扫描 `scripts/**/*.py` 并排除边界文件 | `tests/test_development_policy_guard.py::TestPolicyConfig003` | | POLICY-CONFIG-003 | 业务代码不得直接 `os.environ` / `os.getenv` / `os.putenv` / `environ.get`;须经 `jiangchang_skill_core.config.get*`(允许 `main.py`、测试、config_bootstrap/runtime 边界文件) | development/CONFIG.md §配置 bootstrapdevelopment/ADAPTER.md §档位 dispatch | hard | 扫描 `scripts/**/*.py` 并排除边界文件 | `tests/test_development_policy_guard.py::TestPolicyConfig003` |
| POLICY-CONFIG-004 | `.env.example` 每个活跃 `KEY=` 必须有中文**行头**(是什么)与中文**行尾**(怎么填);注释禁止 `development/``*.md` 文档指向、`account-manager``adapter 档位` 等开发黑话 | development/CONFIG.md §.env.example 注释 = 配置管理用户文案 | hard | 解析 `.env.example` 活跃行与注释 | `tests/test_development_policy_guard.py::TestPolicyConfig004` |
| POLICY-TESTING-001 | 默认根目录 `tests/test_*.py` 不得硬编码 `real_api` / `real_rpa` / `OPENCLAW_TEST_TARGET`(白名单仅 `test_adapter_profile_policy.py`,用于档位 helper 自测) | development/TESTING.md §默认必跑测试要做什么、§4 测试目标档位 | hard | 扫描 `tests/test_*.py` 非注释行;白名单外禁止上述文本 | `tests/test_development_policy_guard.py::TestPolicyTesting001` | | POLICY-TESTING-001 | 默认根目录 `tests/test_*.py` 不得硬编码 `real_api` / `real_rpa` / `OPENCLAW_TEST_TARGET`(白名单仅 `test_adapter_profile_policy.py`,用于档位 helper 自测) | development/TESTING.md §默认必跑测试要做什么、§4 测试目标档位 | hard | 扫描 `tests/test_*.py` 非注释行;白名单外禁止上述文本 | `tests/test_development_policy_guard.py::TestPolicyTesting001` |
| POLICY-TESTING-002 | `tests/integration/` 下 Python 测试文件若存在,默认应为 `.sample`,不得进入默认套件 | development/TESTING.md §7 真实联调测试的安全约束、§8 新 skill 的最小测试清单 | hard | 检查 `tests/integration/*.py``.sample` 后缀 | `tests/test_development_policy_guard.py::TestPolicyTesting002` | | POLICY-TESTING-002 | `tests/integration/` 下 Python 测试文件若存在,默认应为 `.sample`,不得进入默认套件 | development/TESTING.md §7 真实联调测试的安全约束、§8 新 skill 的最小测试清单 | hard | 检查 `tests/integration/*.py``.sample` 后缀 | `tests/test_development_policy_guard.py::TestPolicyTesting002` |
| POLICY-RPA-001 | 不得 import/use account-manager 内部 `rpa_helpers` / `inject_account_manager_scripts_path` / `get_account_credential` | development/ADAPTER.md §兄弟依赖development/RPA.md §1.7 | hard | 扫描 `scripts/**/*.py` 禁止模式 | `tests/test_development_policy_guard.py::TestPolicyRpa001`(另见 `tests/test_no_rpa_helpers_import.py` | | POLICY-RPA-001 | 不得 import/use account-manager 内部 `rpa_helpers` / `inject_account_manager_scripts_path` / `get_account_credential` | development/ADAPTER.md §兄弟依赖development/RPA.md §1.7 | hard | 扫描 `scripts/**/*.py` 禁止模式 | `tests/test_development_policy_guard.py::TestPolicyRpa001`(另见 `tests/test_no_rpa_helpers_import.py` |
| POLICY-RPA-002 | RPA 交付代码不得直接执行 ffmpeg 拼 MP4应使用 `RpaVideoSession`(允许 `ffmpeg_path` 等诊断字段) | development/RPA.md §5.3 录屏成片标准 | hard | 扫描 `scripts/**/*.py` 禁止 `subprocess` / `os.system` / `os.popen` 直接调用 ffmpeg`task_run_support.py` 保留豁免) | `tests/test_development_policy_guard.py::TestPolicyRpa002` | | POLICY-RPA-002 | RPA 交付代码不得直接执行 ffmpeg 拼 MP4应使用 `RpaVideoSession`(允许 `ffmpeg_path` 等诊断字段) | development/RPA.md §5.3 录屏成片标准 | hard | 扫描 `scripts/**/*.py` 禁止 `subprocess` / `os.system` / `os.popen` 直接调用 ffmpeg`task_run_support.py` 保留豁免) | `tests/test_development_policy_guard.py::TestPolicyRpa002` |
| POLICY-RPA-003 | 即使默认 `OPENCLAW_RECORD_VIDEO=0`RPA / 长任务模板也必须保留录屏能力:`RpaVideoSession``video.add_step`、video artifact merge 到 `result_summary` | development/RPA.md §5.3development/LOGGING.mddevelopment/CONFIG.md | hard | 扫描 `scripts/service/*.py`service 层整体须含上述接入) | `tests/test_development_policy_guard.py::TestPolicyRpa003` | | POLICY-RPA-003 | 即使默认 `OPENCLAW_RECORD_VIDEO=0`RPA / 长任务模板也必须保留录屏能力:`RpaVideoSession``video.add_step`、video artifact merge 到 `result_summary` | development/RPA.md §5.3development/LOGGING.mddevelopment/CONFIG.md | hard | 扫描 `scripts/service/*.py`service 层整体须含上述接入) | `tests/test_development_policy_guard.py::TestPolicyRpa003` |
| POLICY-RPA-004 | 网页 RPA`.env.example` 若声明 `OPENCLAW_BROWSER_HEADLESS` 则默认必须为 `0``scripts/` 若使用 `launch_persistent_*` 则必须同时存在 `pick_web_account`(先 Profile 再开浏览器);登录不全局强制,但不得跳过 account-manager | development/RPA.md §0.2development/ADAPTER.md §兄弟依赖 | hard | 解析 `.env.example` + 扫描 `scripts/**/*.py` | `tests/test_development_policy_guard.py::TestPolicyRpa004` |
| POLICY-PACKAGING-001 | `scripts/**/*.py` 单文件 < 1000 行 | development/DEVELOPMENT.md §3.4 发布打包约束development/RUNTIME.md §发布打包约束 | hard | 行数统计 | **已有测试覆盖**`tests/test_release_packaging_constraints.py::test_scripts_py_files_under_pyarmor_line_limit` | | POLICY-PACKAGING-001 | `scripts/**/*.py` 单文件 < 1000 行 | development/DEVELOPMENT.md §3.4 发布打包约束development/RUNTIME.md §发布打包约束 | hard | 行数统计 | **已有测试覆盖**`tests/test_release_packaging_constraints.py::test_scripts_py_files_under_pyarmor_line_limit` |
| POLICY-PACKAGING-002 | 文本文件 UTF-8 without BOM`U+FEFF` | development/DEVELOPMENT.md §3.4development/RUNTIME.md §编码与输出 | hard | BOM / 解码检查 | **已有测试覆盖**`tests/test_release_packaging_constraints.py::test_text_files_are_utf8_without_bom` | | POLICY-PACKAGING-002 | 文本文件 UTF-8 without BOM`U+FEFF` | development/DEVELOPMENT.md §3.4development/RUNTIME.md §编码与输出 | hard | BOM / 解码检查 | **已有测试覆盖**`tests/test_release_packaging_constraints.py::test_text_files_are_utf8_without_bom` |
| POLICY-DOCS-001 | 本矩阵存在且包含上述全部 `policy_id` | 本次规范化约定 | hard | 解析 `development/POLICY_MATRIX.md` | `tests/test_development_policy_guard.py::TestPolicyDocs001` | | POLICY-DOCS-001 | 本矩阵存在且包含上述全部 `policy_id` | 本次规范化约定 | hard | 解析 `development/POLICY_MATRIX.md` | `tests/test_development_policy_guard.py::TestPolicyDocs001` |

View File

@@ -22,6 +22,11 @@
- 中文 description一句话 - 中文 description一句话
- account-manager platform_key如有 - account-manager platform_key如有
- 命名形态:标准型 / scope 型 - 命名形态:标准型 / scope 型
- **网页 RPA如有— 登录策略(三选一,须写明):**
- `required`:目标站必须登录才能闭环(用户须先登记账号,并在有头浏览器中完成/保持登录)
- `optional`:登录可提升能力,但不登录也能完成主路径
- `not_needed`:公开页即可,不要求站点登录
- 无论上列哪一种:**都必须**经 account-manager 取得 `profile_dir`(见 `development/RPA.md` §0.2);不得使用系统默认浏览器用户目录
## 1. 文档目标 ## 1. 文档目标
@@ -103,6 +108,7 @@
- Windows 环境下需保证 UTF-8 输出兼容 - Windows 环境下需保证 UTF-8 输出兼容
- 必须具备基本日志能力;敏感字段脱敏 - 必须具备基本日志能力;敏感字段脱敏
- RPA 类 skillPlaywright 由宿主共享 runtime 提供;技能侧不要 `playwright install`URL 不要放进 `launch_persistent_context``args` - RPA 类 skillPlaywright 由宿主共享 runtime 提供;技能侧不要 `playwright install`URL 不要放进 `launch_persistent_context``args`
- 网页 RPA启动前必须已 pick 到 `profile_dir`;生产有头;站点登录策略见 §0required / optional / not_needed
## 6. 输入输出要求 ## 6. 输入输出要求

View File

@@ -2,7 +2,7 @@
> 本文是团队 RPA 开发的**统一标准**。任何需要"自动操作软件界面"的 skill都应先读这份文档按这里的选型和范式落地不要每个项目重新踩坑。 > 本文是团队 RPA 开发的**统一标准**。任何需要"自动操作软件界面"的 skill都应先读这份文档按这里的选型和范式落地不要每个项目重新踩坑。
我们开发的各类 skill本质上都是在替人操作三类界面**浏览器、桌面软件、手机软件**。三类的底层技术不同,但**工程范式相同**保持登录态、有头运行、拟人操作、失败存证、人工兜底。 我们开发的各类 skill本质上都是在替人操作三类界面**浏览器、桌面软件、手机软件**。三类的底层技术不同,但**工程范式相同**可控会话Profile、有头运行、拟人操作、失败存证、人工兜底。
--- ---
@@ -12,8 +12,8 @@
| 约定 | 说明 | | 约定 | 说明 |
|------|------| |------|------|
| **保持登录态** | 复用持久化 Profile / session避免每次重新登录触发风控账号由 account-manager 下发,不硬编码 | | **可控会话 / Profile** | 网页版见下方 **§0.2**(必须走 account-manager 的 `profile_dir`);桌面/手机用对应持久会话,不硬编码密码 |
| **有头运行** | 默认有头headless 易被识别 / 难人工介入);`OPENCLAW_BROWSER_HEADLESS=1` 仅给 CI | | **有头运行** | 生产 RPA **必须有头**(见 §0.2);禁止把无头当交付/联调常态 |
| **拟人操作** | 真实事件isTrusted=true逐字输入、随机延迟、贝塞尔鼠标轨迹严禁 JS 直接设值/JS 点击/JS 跳转 | | **拟人操作** | 真实事件isTrusted=true逐字输入、随机延迟、贝塞尔鼠标轨迹严禁 JS 直接设值/JS 点击/JS 跳转 |
| **步骤间随机等待** | 每两步操作之间 `random_delay(min,max)`,区间由 `.env` 配置(默认 1~5s | | **步骤间随机等待** | 每两步操作之间 `random_delay(min,max)`,区间由 `.env` 配置(默认 1~5s |
| **人工兜底HITL** | 滑块 / 短信验证码 / 人脸 / U盾 / 动态口令 → **停下来轮询等人工**,超时报 `ERROR:XXX_NEED_HUMAN`,绝不自动硬闯 | | **人工兜底HITL** | 滑块 / 短信验证码 / 人脸 / U盾 / 动态口令 → **停下来轮询等人工**,超时报 `ERROR:XXX_NEED_HUMAN`,绝不自动硬闯 |
@@ -44,6 +44,27 @@ async def open_login(page):
规范权威定义:[`LOGGING.md`](LOGGING.md) §2.5。金样代码:[`scripts/service/task_service.py`](../scripts/service/task_service.py)。 规范权威定义:[`LOGGING.md`](LOGGING.md) §2.5。金样代码:[`scripts/service/task_service.py`](../scripts/service/task_service.py)。
### 0.2 网页版 RPA 硬规则Profile / 有头 / 登录分平台)
凡**打开网页、用浏览器自动化**的技能(真实站或仿真站),必须遵守(`POLICY-RPA-004`
| 规则 | 要求 |
|------|------|
| **Profile 强制** | 启动浏览器前必须已从 **account-manager**(经本技能 `account_client`)拿到可用的 **`profile_dir`(及租约约定)**。只用该目录做 `launch_persistent_context`。 |
| **拿不到就停** | 未配置账号、未拿到 `profile_dir`、租约失败 → **立即失败退出,禁止开浏览器**。不得静默落到系统默认 Chrome/Edge 用户目录。 |
| **有头强制** | 生产与用户侧联调:**`OPENCLAW_BROWSER_HEADLESS=0`(有头)**。禁止把无头(`=1`)当作交付或真实站联调常态(无头易风控、难人工介入,且易污染身份)。 |
| **登录不全局强制** | **是否要在目标站完成登录**由平台决定(如部分公开页可不登录也能闭环)。由做该技能的同事在 `REQUIREMENTS.md` / 用户教程中约定;**模板不强制「所有网页 RPA 必须先登录」**。 |
| **登录 ≠ Profile** | 「可不登录」**绝不等于**「可以不接 account-manager」。无论目标站要不要登录网页操作都依赖账号管理下发的 **Profile**。 |
**反模式(禁止):**
-`pick``profile_dir``launch` / 仍打开页面
- 用系统默认用户数据目录或随意空目录反复跑真实站 → **污染本机 Profile、加重风控**
- 把「本站可不登录」写成「可以不走 account-manager」
- 真实站联调默认无头狂跑
**参考实现:** `examples/real_browser_rpa/``examples/simulator_browser_rpa/``pick_web_account` → RPA → `finally release_lease`)。
--- ---
## 1. 浏览器(标准已成熟) ## 1. 浏览器(标准已成熟)
@@ -55,21 +76,22 @@ async def open_login(page):
| 项 | 标准 | | 项 | 标准 |
|----|------| |----|------|
| 浏览器 | **优先系统 Chrome/Edge** + `launch_persistent_context``channel="chrome"` 或 Edge不用内置 Chromium**技能内不要 `playwright install`** | | 浏览器 | **优先系统 Chrome/Edge** + `launch_persistent_context``channel="chrome"` 或 Edge不用内置 Chromium**技能内不要 `playwright install`** |
| 登录态 | 持久 Profile 目录账号、profile、lease **统一走 account-manager**(或对应兄弟技能),不硬编码密码 | | Profile / 账号 | **必须**经 account-manager 取得 `profile_dir`(见 §0.2);密码不进 `.env`**是否站点登录**按平台在需求/教程约定 |
| CDP | **仅作诊断 / 桌面宿主类场景****不要**作为强风控站点的默认生产路径 | | CDP | **仅作诊断 / 桌面宿主类场景****不要**作为强风控站点的默认生产路径 |
| 行为 | 模拟真实用户:真实点击、键盘、鼠标、地址栏输入;**不要**拼接搜索结果 URL、DOM 注入、`el.value=`、JS 跳转 | | 行为 | 模拟真实用户:真实点击、键盘、鼠标、地址栏输入;**不要**拼接搜索结果 URL、DOM 注入、`el.value=`、JS 跳转 |
| 模式 | 默认有头 `OPENCLAW_BROWSER_HEADLESS=0`;无头仅 CI | | 模式 | **有头** `OPENCLAW_BROWSER_HEADLESS=0`(生产强制,见 §0.2 |
| 反检测 | stealth 默认开 `OPENCLAW_PLAYWRIGHT_STEALTH=1`(见 1.1 | | 反检测 | stealth 默认开 `OPENCLAW_PLAYWRIGHT_STEALTH=1`(见 1.1 |
### 1.1 Playwright 启动标准 ### 1.1 Playwright 启动标准
1. **默认有头**`OPENCLAW_BROWSER_HEADLESS=0``.env.example` 默认)。 1. **有头**`OPENCLAW_BROWSER_HEADLESS=0``.env.example` 默认;网页 RPA 生产路径禁止改成无头常态,见 §0.2)。
2. **stealth 默认开**`OPENCLAW_PLAYWRIGHT_STEALTH=1`;通过 `add_init_script` 注入指纹淡化脚本。 2. **stealth 默认开**`OPENCLAW_PLAYWRIGHT_STEALTH=1`;通过 `add_init_script` 注入指纹淡化脚本。
3. **不要在技能里自行安装 playwright**;由宿主共享 runtime 提供。 3. **不要在技能里自行安装 playwright**;由宿主共享 runtime 提供。
4. **不要默认传 `--no-sandbox`**(除非特定容器环境且已评估风险)。 4. **不要默认传 `--no-sandbox`**(除非特定容器环境且已评估风险)。
5. **不要默认传 `--disable-blink-features=AutomationControlled`**platform-kit stealth 已覆盖,额外 flag 可能适得其反。 5. **不要默认传 `--disable-blink-features=AutomationControlled`**platform-kit stealth 已覆盖,额外 flag 可能适得其反。
6. **可以** `ignore_default_args=["--enable-automation"]`platform-kit `launch_persistent_browser` 已处理)。 6. **可以** `ignore_default_args=["--enable-automation"]`platform-kit `launch_persistent_browser` 已处理)。
7. **强风控平台**:优先真实点击、键盘、鼠标、地址栏、持久 profile**不要**直接拼接搜索结果 URL 或 DOM 注入。 7. **强风控平台**:优先真实点击、键盘、鼠标、地址栏、account-manager 持久 profile**不要**直接拼接搜索结果 URL 或 DOM 注入。
8. **先 Profile 再开浏览器**`user_data_dir` 必须来自 pick 到的 `profile_dir`;未拿到则不得进入本节后续步骤。
指纹淡化stealth典型项`navigator.webdriver=undefined``chrome.runtime``permissions.query``plugins``languages` 等。共享实现见 `jiangchang_skill_core.rpa`platform-kit **>= 1.2.0**)。 指纹淡化stealth典型项`navigator.webdriver=undefined``chrome.runtime``permissions.query``plugins``languages` 等。共享实现见 `jiangchang_skill_core.rpa`platform-kit **>= 1.2.0**)。

View File

@@ -23,16 +23,23 @@
4. **长耗时建议 async** — 开浏览器、RPA、HTTP 大文件下载等建议 `executionProfile: "async"`,进**任务中心**;这是对**方法**的选择,**不**限制可以出现在哪些 placement / 入口。 4. **长耗时建议 async** — 开浏览器、RPA、HTTP 大文件下载等建议 `executionProfile: "async"`,进**任务中心**;这是对**方法**的选择,**不**限制可以出现在哪些 placement / 入口。
5. **Agent短 CLI / 长 Action** — 短查询可用共享 Python bash CLI长任务 / RPA **禁止** `exec` + `process poll`,必须 `run_skill_action` 5. **Agent短 CLI / 长 Action** — 短查询可用共享 Python bash CLI长任务 / RPA **禁止** `exec` + `process poll`,必须 `run_skill_action`
6. **进度走 Journal**`emit` / `video.add_step` → Run JournalUI / Skill Run Card / 任务中心展示;**不靠 stdout 流**。 6. **进度走 Journal**`emit` / `video.add_step` → Run JournalUI / Skill Run Card / 任务中心展示;**不靠 stdout 流**。
7. **显式声明 `executionProfile`** — 每个 action **必须**写明 `sync``async`禁止依赖宿主缺省;禁止第三种模糊模式(`auto` / `background` / `deferred` 等)。 7. **显式声明 `executionProfile`**心智默认 sync、例外才 async每个 action **必须**写明 `sync``async`;宿主省略时按 sync新技能仍禁止赌缺省;禁止第三种模糊模式(`auto` / `background` / `deferred` 等)。
--- ---
## 2. `executionProfile`sync vs async ## 2. `executionProfile`sync vs async
### 心智(先记住)
1. **默认按 sync 想**:多数 Action诊断、统计、入队、秒级查询是短调用当场返回**不进**任务中心。
2. **只有特殊情况标 async**:开浏览器 / RPA / 长下载 / 需要进度·暂停·停止时,才显式 `"executionProfile": "async"`,进入**任务中心**。
3. **匠厂宿主**`actions.json` 若**省略** `executionProfile`,按 **`sync`** 处理(不再把漏写当成进任务中心)。
4. **新技能仍须显式写出** `sync``async`(模板 Schema / `POLICY-SKILL-ACTION-002` 硬门禁),禁止赌宿主缺省;显式写出也便于人和 AI 阅读。
| 值 | 宿主行为 | 超时 | 任务中心 | | 值 | 宿主行为 | 超时 | 任务中心 |
|----|----------|------|----------| |----|----------|------|----------|
| **`sync`** | 调用方等待 CLI 结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 默认约 **30s**`SKILL_ACTION_SYNC_TIMEOUT_MS` | ❌ 不进 | | **`sync`**(或省略时的宿主缺省) | 调用方等待 CLI 结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 默认约 **30s**`SKILL_ACTION_SYNC_TIMEOUT_MS` | ❌ 不进 |
| **`async`** | 后台 spawn Job立即返回 `jobId``emit` / `checkpoint` / `finish`;长任务应支持暂停/停止/取消检查 | Job 本身无默认墙钟上限 | ✅ 进入 | | **`async`**(须显式声明) | 后台 spawn Job立即返回 `jobId``emit` / `checkpoint` / `finish`;长任务应支持暂停/停止/取消检查 | Job 本身无默认墙钟上限 | ✅ 进入 |
直接执行 CLI终端等待进程结束。宿主经 Action 调用同一 CLI再按 `executionProfile` 分流。技能不得自建任务中心。 直接执行 CLI终端等待进程结束。宿主经 Action 调用同一 CLI再按 `executionProfile` 分流。技能不得自建任务中心。
@@ -47,7 +54,7 @@
合法:`sync|async` × `toolbar|cron|agent|skill-detail` 合法:`sync|async` × `toolbar|cron|agent|skill-detail`
历史说明:旧文档曾错误地把数据管理按钮入口与异步执行方式绑死。该约束**已废止**;模板测试与 Schema **不得**再建立「入口位置决定 sync/async」一类硬耦合。宿主省略 `executionProfile` 时的兼容行为以宿主实现为准,**新技能不得依赖** 历史说明:旧文档曾错误地把数据管理按钮入口与异步执行方式绑死。该约束**已废止**;模板测试与 Schema **不得**再建立「入口位置决定 sync/async」一类硬耦合。旧版宿主曾「省略 → async」**当前宿主为「省略 → sync」**。新技能仍须显式声明,不得依赖省略行为
--- ---

View File

@@ -137,7 +137,8 @@ Golden fixture 流程同理([`tests/samples/test_golden_cases.py.sample`](../t
浏览器 RPA 走 `simulator_rpa` 档位联调前,逐项确认: 浏览器 RPA 走 `simulator_rpa` 档位联调前,逐项确认:
- [ ] 数据目录 `.env``OPENCLAW_TEST_TARGET=simulator_rpa`(非 `mock` / `unit` - [ ] 数据目录 `.env``OPENCLAW_TEST_TARGET=simulator_rpa`(非 `mock` / `unit`
- [ ] account-manager`platform ensure` + 账号 `active` + 有 `profile_dir` - [ ] account-manager`platform ensure` + 账号 `active` + 有 `profile_dir`(无 profile 禁止开浏览器,见 `RPA.md` §0.2
- [ ] `OPENCLAW_BROWSER_HEADLESS=0`(联调有头;勿默认无头)
- [ ] 目标 sandbox UI 已部署(跨团队;本地可用 `sandbox/demo_app.html` - [ ] 目标 sandbox UI 已部署(跨团队;本地可用 `sandbox/demo_app.html`
- [ ] 失败先查 `rpa-artifacts/` 截图,再查 Chrome profile 缓存(`--user-data-dir` 手工打开清站点数据) - [ ] 失败先查 `rpa-artifacts/` 截图,再查 Chrome profile 缓存(`--user-data-dir` 手工打开清站点数据)
- [ ] 默认 `python tests/run_tests.py -v` 仍全部通过mock 离线example 内 `pytest` 不启真实浏览器 - [ ] 默认 `python tests/run_tests.py -v` 仍全部通过mock 离线example 内 `pytest` 不启真实浏览器

View File

@@ -79,6 +79,9 @@ real_browser_rpa/
**浏览器与启动约束:** **浏览器与启动约束:**
- **先 Profile 再开浏览器**:必须 `pick_web_account` 拿到 `profile_dir`;拿不到则失败退出,禁止落到系统默认用户目录(见模板 `development/RPA.md` §0.2
- **有头**:生产 / 联调 `OPENCLAW_BROWSER_HEADLESS=0`;勿把无头当交付常态
- **登录按平台**:是否要求站点登录由本技能约定;「可不登录」≠「可不接 account-manager」
- **不要**在技能内安装 Playwright浏览器与 Python 包由宿主/runtime 提供 - **不要**在技能内安装 Playwright浏览器与 Python 包由宿主/runtime 提供
- `launch_persistent_context``args` **只放 Chrome 参数****不要把 URL 放进 `args`** - `launch_persistent_context``args` **只放 Chrome 参数****不要把 URL 放进 `args`**
- 页面必须通过 `new_page()` + `goto()`,或通过真实地址栏/点击进入 - 页面必须通过 `new_page()` + `goto()`,或通过真实地址栏/点击进入

View File

@@ -57,6 +57,12 @@ simulator_browser_rpa/
| `task_service.py` | async 校验输入 → 选 adapter → `await submit_batch` | | `task_service.py` | async 校验输入 → 选 adapter → `await submit_batch` |
| `sandbox/demo_app.html` | 可控 DOM含可选门户门闩 + 原批量提交流程 | | `sandbox/demo_app.html` | 可控 DOM含可选门户门闩 + 原批量提交流程 |
## 网页 RPA 硬规则(与模板 `development/RPA.md` §0.2 一致)
- **先 Profile 再开浏览器**`simulator_rpa` 档必须 `pick_web_account` 拿到 `profile_dir`;拿不到则失败,禁止系统默认用户目录
- **有头**:联调 / 交付默认 `OPENCLAW_BROWSER_HEADLESS=0`
- **登录按平台约定**:本示例需要仿真登录;其他技能可为 optional / not_needed但**仍须**走 account-manager
## 核心流程 ## 核心流程
1. `await run_batch_submit(target, items)` 校验参数 1. `await run_batch_submit(target, items)` 校验参数

View File

@@ -161,16 +161,16 @@ python {skill_root}/scripts/main.py <command> <args...>
### `executionProfile`(仅 sync / async ### `executionProfile`(仅 sync / async
模板与新技能**只允许**这两种值;每个 Action **必须**显式写出;禁止发明 `auto` / `background` / `deferred`**禁止**依赖宿主「省略字段时的兼容默认」 **心智**:多数 Action 按 **sync**(短调用、不进任务中心);只有长耗时 / RPA / 需进度控制时才标 **async**。匠厂宿主若**省略**该字段,按 **sync** 处理;模板与新技能仍要求**每个 Action 显式写出** `sync``async`禁止发明 `auto` / `background` / `deferred`,也禁止赌省略行为
| 值 | 宿主行为 | 任务中心 | | 值 | 宿主行为 | 任务中心 |
|----|----------|----------| |----|----------|----------|
| `sync` | 调用方等待结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 不进 | | `sync`(或省略时的宿主缺省) | 调用方等待结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 不进 |
| `async` | 宿主创建后台 Job立即返回 `jobId`;用 `emit` / `checkpoint` / `finish` 报告进度与终态 | **进入** | | `async`(须显式声明) | 宿主创建后台 Job立即返回 `jobId`;用 `emit` / `checkpoint` / `finish` 报告进度与终态 | **进入** |
合法组合(全部允许):`sync|async` × `toolbar|cron|agent|skill-detail` 合法组合(全部允许):`sync|async` × `toolbar|cron|agent|skill-detail`
经验建议(**不是** placement 硬限制):耗时不可预测、需要暂停/停止/进度展示的任务,用 `async` 经验建议(**不是** placement 硬限制):耗时不可预测、需要暂停/停止/进度展示的任务,用 `async`;其余默认写 `sync`
直接执行短 CLI 时,终端自然等待进程结束。宿主通过 Action 调用同一 CLI 时,再按 `executionProfile` 决定同步等待还是建 Job。技能**不得**自行实现第二套任务中心或后台队列。 直接执行短 CLI 时,终端自然等待进程结束。宿主通过 Action 调用同一 CLI 时,再按 `executionProfile` 决定同步等待还是建 Job。技能**不得**自行实现第二套任务中心或后台队列。

View File

@@ -1,6 +1,6 @@
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger""" """技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger"""
SKILL_SLUG = "your-skill-slug" SKILL_SLUG = "your-skill-slug"
SKILL_VERSION = "1.0.46" SKILL_VERSION = "1.0.50"
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug" LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
PLATFORM_KIT_MIN_VERSION = "1.2.0" PLATFORM_KIT_MIN_VERSION = "1.2.0"

View File

@@ -29,11 +29,13 @@ POLICY_IDS = (
"POLICY-CONFIG-001", "POLICY-CONFIG-001",
"POLICY-CONFIG-002", "POLICY-CONFIG-002",
"POLICY-CONFIG-003", "POLICY-CONFIG-003",
"POLICY-CONFIG-004",
"POLICY-TESTING-001", "POLICY-TESTING-001",
"POLICY-TESTING-002", "POLICY-TESTING-002",
"POLICY-RPA-001", "POLICY-RPA-001",
"POLICY-RPA-002", "POLICY-RPA-002",
"POLICY-RPA-003", "POLICY-RPA-003",
"POLICY-RPA-004",
"POLICY-PACKAGING-001", "POLICY-PACKAGING-001",
"POLICY-PACKAGING-002", "POLICY-PACKAGING-002",
"POLICY-DOCS-001", "POLICY-DOCS-001",
@@ -86,6 +88,22 @@ CONFIG_ENV_FORBIDDEN_PATTERNS = (
re.compile(r"\benviron\.get\b"), re.compile(r"\benviron\.get\b"),
) )
CONFIG_004_SOURCE = "development/CONFIG.md §.env.example 注释 = 配置管理用户文案"
CONFIG_004_ENV_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
CONFIG_004_CJK_RE = re.compile(r"[\u4e00-\u9fff]")
# 注释中禁止出现的开发向指向 / 黑话(大小写不敏感子串)
CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS = (
"development/",
"adapter.md",
"rpa.md",
"data_paths.md",
"config.md",
"account-manager",
"见 development",
"adapter 档位",
"adapter档位",
)
TESTING_REAL_TARGET_ALLOWED_ROOT_TESTS = frozenset({"test_adapter_profile_policy.py"}) TESTING_REAL_TARGET_ALLOWED_ROOT_TESTS = frozenset({"test_adapter_profile_policy.py"})
TESTING_001_FORBIDDEN_SUBSTRINGS = ( TESTING_001_FORBIDDEN_SUBSTRINGS = (
@@ -125,6 +143,18 @@ RPA_003_SOURCE = (
"development/RPA.md §5.3; development/TESTING.md §12; development/POLICY_MATRIX.md" "development/RPA.md §5.3; development/TESTING.md §12; development/POLICY_MATRIX.md"
) )
RPA_004_SOURCE = (
"development/RPA.md §0.2; development/ADAPTER.md §兄弟依赖; development/POLICY_MATRIX.md"
)
RPA_004_HEADLESS_KEY_RE = re.compile(
r"(?m)^\s*OPENCLAW_BROWSER_HEADLESS\s*=\s*([^\s#]+)"
)
RPA_004_LAUNCH_MARKERS = (
"launch_persistent_context",
"launch_persistent_browser",
)
RPA_004_PICK_MARKER = "pick_web_account"
LOGGING_001_SOURCE = "development/LOGGING.md; development/RUNTIME.md" LOGGING_001_SOURCE = "development/LOGGING.md; development/RUNTIME.md"
LOGGING_002_SOURCE = "development/LOGGING.md; development/DEVELOPMENT.md" LOGGING_002_SOURCE = "development/LOGGING.md; development/DEVELOPMENT.md"
LOGGING_003_SOURCE = "development/LOGGING.md" LOGGING_003_SOURCE = "development/LOGGING.md"
@@ -149,6 +179,77 @@ def _policy_msg(policy_id: str, source: str, detail: str) -> str:
return f"{policy_id} [{source}]: {detail}" return f"{policy_id} [{source}]: {detail}"
def _strip_env_quotes(value: str) -> str:
trimmed = value.strip()
if (
(trimmed.startswith('"') and trimmed.endswith('"'))
or (trimmed.startswith("'") and trimmed.endswith("'"))
):
return trimmed[1:-1]
return trimmed
def _iter_env_example_active_entries(path: str):
"""Yield (lineno, key, value, header_comments, inline_comment) for active KEY= lines."""
pending: list[str] = []
with open(path, encoding="utf-8") as handle:
for lineno, line in enumerate(handle, 1):
stripped = line.strip()
if not stripped:
pending = []
continue
if stripped.startswith("#"):
pending.append(stripped.lstrip("#").strip())
continue
eq = stripped.find("=")
if eq <= 0:
pending = []
continue
key = stripped[:eq].strip()
if not CONFIG_004_ENV_KEY_RE.match(key):
pending = []
continue
rest = stripped[eq + 1 :]
inline: str | None = None
if not rest.startswith('"') and not rest.startswith("'"):
hash_at = rest.find(" #")
if hash_at >= 0:
inline = rest[hash_at + 2 :].strip()
rest = rest[:hash_at]
value = _strip_env_quotes(rest)
yield lineno, key, value, list(pending), inline
pending = []
def _config_004_comment_offenders(path: str) -> list[str]:
offenders: list[str] = []
for lineno, key, _value, headers, inline in _iter_env_example_active_entries(path):
if not headers:
offenders.append(
f".env.example:{lineno}: {key}= 缺少行头注释(须用中文说明「是什么」)"
)
elif not any(CONFIG_004_CJK_RE.search(part) for part in headers):
offenders.append(
f".env.example:{lineno}: {key}= 行头注释须含中文(说明「是什么」)"
)
if not inline:
offenders.append(
f".env.example:{lineno}: {key}= 缺少行尾注释(须用中文说明「怎么填」)"
)
elif not CONFIG_004_CJK_RE.search(inline):
offenders.append(
f".env.example:{lineno}: {key}= 行尾注释须含中文(说明「怎么填」)"
)
combined = "\n".join([*headers, inline or ""]).lower()
for token in CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS:
if token.lower() in combined:
offenders.append(
f".env.example:{lineno}: {key}= 注释禁止含 {token!r}(面向用户,勿写开发文档指向/黑话)"
)
break
return offenders
def _rel(skill_root: str, path: str) -> str: def _rel(skill_root: str, path: str) -> str:
return os.path.relpath(path, skill_root).replace("\\", "/") return os.path.relpath(path, skill_root).replace("\\", "/")
@@ -385,6 +486,23 @@ class TestPolicyConfig003(unittest.TestCase):
) )
class TestPolicyConfig004(unittest.TestCase):
def test_env_example_comments_are_user_facing(self) -> None:
path = os.path.join(get_skill_root(), ".env.example")
self.assertTrue(os.path.isfile(path), msg=".env.example missing")
offenders = _config_004_comment_offenders(path)
self.assertEqual(
offenders,
[],
msg=_policy_msg(
"POLICY-CONFIG-004",
CONFIG_004_SOURCE,
"\n".join(offenders)
or "every active KEY in .env.example needs Chinese header+inline comments",
),
)
class TestPolicyTesting001(unittest.TestCase): class TestPolicyTesting001(unittest.TestCase):
def test_root_tests_do_not_hardcode_real_targets(self) -> None: def test_root_tests_do_not_hardcode_real_targets(self) -> None:
skill_root = get_skill_root() skill_root = get_skill_root()
@@ -510,6 +628,47 @@ class TestPolicyRpa003(unittest.TestCase):
) )
class TestPolicyRpa004(unittest.TestCase):
def test_env_example_browser_headless_defaults_to_headed(self) -> None:
skill_root = get_skill_root()
env_path = os.path.join(skill_root, ".env.example")
self.assertTrue(os.path.isfile(env_path), msg=".env.example missing")
text = _read_text(env_path)
match = RPA_004_HEADLESS_KEY_RE.search(text)
if match is None:
return
value = match.group(1).strip().strip("\"'")
self.assertEqual(
value,
"0",
msg=_policy_msg(
"POLICY-RPA-004",
RPA_004_SOURCE,
f".env.example OPENCLAW_BROWSER_HEADLESS must default to 0 (headed), got {value!r}",
),
)
def test_launch_persistent_requires_pick_web_account(self) -> None:
skill_root = get_skill_root()
script_texts = [
_read_text(path) for path in _walk_files(skill_root, "scripts", suffix=".py")
]
combined = "\n".join(script_texts)
uses_launch = any(marker in combined for marker in RPA_004_LAUNCH_MARKERS)
if not uses_launch:
return
self.assertIn(
RPA_004_PICK_MARKER,
combined,
msg=_policy_msg(
"POLICY-RPA-004",
RPA_004_SOURCE,
"scripts/ uses launch_persistent_* but missing pick_web_account "
"(must obtain profile_dir before opening browser)",
),
)
def _scan_sensitive_logging_assignments(skill_root: str) -> list[str]: def _scan_sensitive_logging_assignments(skill_root: str) -> list[str]:
offenders: list[str] = [] offenders: list[str] = []
for path in _walk_files(skill_root, "scripts", suffix=".py"): for path in _walk_files(skill_root, "scripts", suffix=".py"):

View File

@@ -238,6 +238,17 @@ class TestDocsStandards(unittest.TestCase):
msg="RPA.md must mention jc2009 / industry simulator / portal gate", msg="RPA.md must mention jc2009 / industry simulator / portal gate",
) )
def test_rpa_md_covers_web_profile_headed_and_login_split(self) -> None:
text = self._read("development/RPA.md")
self.assertIn("0.2", text)
self.assertIn("profile_dir", text)
self.assertIn("POLICY-RPA-004", text)
self.assertIn("有头", text)
self.assertTrue(
"登录不全局强制" in text or "登录 ≠ Profile" in text or "登录≠Profile" in text,
msg="RPA.md §0.2 must separate site-login from Profile requirement",
)
def test_simulator_example_readme_covers_async_and_account_manager(self) -> None: def test_simulator_example_readme_covers_async_and_account_manager(self) -> None:
text = self._read("examples/simulator_browser_rpa/README.md") text = self._read("examples/simulator_browser_rpa/README.md")
self.assertIn("async", text.lower()) self.assertIn("async", text.lower())