Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c15e23e0d4 | |||
| 0ea924958f | |||
| 20e89774ca | |||
| 4ed49495cf | |||
| 8101b5ac01 | |||
| c15854c20d | |||
| 8570d5803d |
10
.env.example
10
.env.example
@@ -1,5 +1,5 @@
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
OPENCLAW_TEST_TARGET=real_rpa # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认真实浏览器;本地单测/CI 请用 mock
|
||||
|
||||
# 目标网站地址:技能要访问的网站或服务地址
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
|
||||
@@ -13,8 +13,8 @@ DEFAULT_ACCOUNT_ID= # 留空=自动;只填纯数字;手
|
||||
# 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(必须,便于介入与排查);1=后台静默(勿作日常用法)
|
||||
|
||||
# 浏览器防检测:降低被网站识别为自动化工具的概率
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启(推荐),0=关闭;一般保持默认
|
||||
# 浏览器兼容配置:仅在项目授权与平台规则允许时使用共享兼容能力
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=使用共享兼容配置,0=关闭;不得用于伪造身份或绕过平台限制
|
||||
|
||||
# 是否录制操作过程:用于留证、复现问题或向他人演示
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并生成带说明的视频文件;需要留证时再改为 1
|
||||
@@ -22,11 +22,11 @@ OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并
|
||||
# 失败时是否截图:任务出错时自动保存现场截图,便于排查
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图(推荐),0=不截图
|
||||
|
||||
# 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
|
||||
# 步骤最短等待:两个正常业务动作之间随机停顿的下限
|
||||
STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
|
||||
|
||||
# 步骤最长等待:两步操作之间随机停顿的上限
|
||||
STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
|
||||
STEP_DELAY_MAX=3.0 # 单位:秒;默认与最短等待组成 1~3 秒节奏;状态等待须另行判断
|
||||
|
||||
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
|
||||
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -9,6 +9,47 @@
|
||||
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
|
||||
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
|
||||
|
||||
## 1.0.58
|
||||
|
||||
- 新增 account-manager 接入标准,说明一账号一 Profile、多账号矩阵、登录状态持久化与完整运行生命周期
|
||||
- 明确账号创建后的查看位置:匠厂数据管理、account-manager CLI、本地用户数据目录及 Profile 目录
|
||||
- 强制所有账号选择路径取得租约并在异常出口释放,修复模板中指定数字账号 ID 时缺少租约的问题
|
||||
- 增加关闭重开保持登录、多账号隔离、租约冲突、异常释放和自助排障验收项
|
||||
- 新增机器门禁,防止后续模板退回到裸浏览器、空租约或只做单账号冒烟测试
|
||||
|
||||
## 1.0.57
|
||||
|
||||
- 新增 AI + RPA 技能开发总纲,明确 Agent 负责理解与编排、RPA 负责浏览器 / 桌面 / 手机的确定性执行
|
||||
- 增加第三方软件自动化合规红线:禁止破解、逆向、私有协议、安全验证绕过、Hook / 注入、自动换号和无限重试
|
||||
- 补齐工业级 RPA 风控、人工介入、状态等待、串行互斥、有限重试、结果核验与前后缓冲标准
|
||||
- 真实业务默认步骤节奏统一为 1~3 秒,并增加自动测试守护
|
||||
- 需求模板和交活 Checklist 增加授权、风险确认、不可逆操作、风控信号与结构化结果验收
|
||||
|
||||
## 1.0.56
|
||||
|
||||
- 新增第三方交活验收清单 `development/CHECKLIST.md`(观察结果、不重复开发规范)
|
||||
- 验收报告强制写入仓库根目录 `acceptance-reports/YYYYMMDD-HHMMSS-acceptance.md`,便于技术人员按 FAIL 项整改
|
||||
|
||||
## 1.0.55
|
||||
|
||||
- 模板 `.env.example` 默认 `OPENCLAW_TEST_TARGET=real_rpa`;四档保留;明确 mock 只保单测/CI,mock 通 ≠ 交活
|
||||
- 验收要求 release 到匠厂并看用户体验(进度、失败提示、有头 RPA 等)
|
||||
|
||||
## 1.0.54
|
||||
|
||||
- 新增 `development/SHARED_REPOS.md`:可扩展的公开公共仓目录(skill-template / account-manager / jiangchang-platform-kit),含类型、Gitea 地址、clone 与红线
|
||||
- 开发入口与 RUNTIME / RPA / tools 文档挂接该目录,便于技术人员与 AI 定位公共依赖
|
||||
|
||||
## 1.0.53
|
||||
|
||||
- 宿主入口对齐:`row` / `batch` 升格为正式 placements;支持 `bind.inputMapping` 与 `readOnly`;已声明入口均须自测(含技能详情直调)
|
||||
- 开发文档:自建 Gitea(git.jc2009.com)与 scaffold 双来源;`developer_ids` 示例改为占位「你的用户ID」;压缩 AI 工具章节
|
||||
|
||||
## 1.0.52
|
||||
|
||||
- 开发文档补齐全流程:Gitea 克隆与本地模板复制两种拿仓方式;`developer_ids` 作为开发期自测通行证(下载安装注册 → 设置复制用户 ID)
|
||||
- 发布后宿主验收明确覆盖新建任务、数据管理、定时任务与任务中心;`development/README` 增加步骤索引
|
||||
|
||||
## 1.0.51
|
||||
|
||||
- 沉淀网页 RPA 首启标准:业务技能默认 `ensure-web`(有则用、无则登记);登记账号 ≠ 已登录目标站;租约全忙返回占用错误且不重复建号
|
||||
|
||||
@@ -41,6 +41,7 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
|
||||
说明用户在使用前要准备的东西,例如:
|
||||
|
||||
- **网页自动化类:** 首次运行可由技能自动登记平台账号(浏览器会话由系统隔离管理)。也可在「账号管理」里指定登录标识。登记账号 ≠ 已登录目标站
|
||||
- **账号在哪里看:** 已登记账号可在「数据管理 / 账号管理」中查看;每个账号使用独立浏览器会话,批量或多账号任务不会共用登录状态
|
||||
- **是否要登录目标站:** 按本技能说明(有的平台必须登录,有的公开页可不登录);与「是否有账号记录」不是一回事
|
||||
- 相关业务权限(查询、导出、提交等)已具备
|
||||
- 需要处理的文件、时间范围、订单号、客户名称、项目名称等信息
|
||||
@@ -100,3 +101,7 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
|
||||
### 结果文件在哪里查看?
|
||||
|
||||
> 请根据 Agent 返回的结果说明查看生成文件、任务记录或处理报告。
|
||||
|
||||
### 已经登记的账号在哪里查看?
|
||||
|
||||
> 请到匠厂的「数据管理 / 账号管理」查看账号记录和状态。若任务仍提示账号异常,请把账号编号、任务时间和界面提示原文提供给支持人员;不要发送密码、Cookie 或验证码。
|
||||
|
||||
14
SKILL.md
14
SKILL.md
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 技能开发模板(通用业务版)
|
||||
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
|
||||
version: 1.0.51
|
||||
version: 1.0.58
|
||||
author: 深圳匠厂科技有限公司
|
||||
metadata:
|
||||
openclaw:
|
||||
@@ -22,10 +22,17 @@ allowed-tools:
|
||||
|
||||
## Agent / 宿主编排(复制为业务技能后必改)
|
||||
|
||||
### AI + RPA 定位
|
||||
|
||||
- **Agent 是入口与编排者**:理解用户意图、补齐参数、判断风险、选择 Action,并用业务语言解释结果。
|
||||
- **RPA 是确定性执行器**:只按已验证步骤操作授权范围内的浏览器、桌面软件或手机;不得破解、逆向、调用未授权私有协议或绕过验证码、安全验证和平台限制。
|
||||
- 遇到验证码、人脸、U盾、频控、账号风险或权限不足时,转人工或安全停止,禁止自动硬闯、换号或无限重试。
|
||||
- 开发者与 AI 编程工具先读 `development/AI_RPA.md`;浏览器账号、Profile、多账号和租约再读 `development/ACCOUNT_MANAGER.md`;三端执行细则读 `development/RPA.md`。
|
||||
|
||||
### 架构铁律(先读)
|
||||
|
||||
- **单业务内核、多入口**:数据管理 / 定时任务 / Agent / 技能详情 / 直接 CLI 必须复用同一 domain service;禁止按入口复制业务逻辑,禁止按 `source` 分叉业务行为。
|
||||
- **`placements`**:只决定 Action **出现在哪里**(toolbar / cron / agent / skill-detail)。
|
||||
- **`placements`**:只决定 Action **出现在哪里**(toolbar / row / batch / cron / agent / skill-detail)。
|
||||
- **`bind.tables`**:只决定数据管理出现在哪些表;含 `toolbar` 时**必须**显式非空绑定。
|
||||
- **`executionProfile`**:只决定执行方式——`sync` 当场返回结果,`async` 进**任务中心**。与 placements / 入口**无关**。心智上多数为 sync,仅长任务 / RPA 标 async;宿主省略该字段时按 sync。每个 Action **仍必须**显式写 `sync` 或 `async`。
|
||||
- **同步数据**写技能本地库、不生成导出文件;**导出当前表**由宿主数据管理负责;特殊业务报告才另做 `export-*`。
|
||||
@@ -150,6 +157,7 @@ python {baseDir}/scripts/main.py init-db
|
||||
|
||||
## 平台元数据
|
||||
|
||||
- `metadata.openclaw.developer_ids`:技能发布后的默认开发者可见用户 ID 列表。
|
||||
- `metadata.openclaw.developer_ids`:技能发布后的默认开发者可见用户 ID 列表(ID 来自匠厂宿主「设置 → 用户信息」)。
|
||||
- 开发期技能常为不公开:不加本人 ID,开发者自己也无法在市场安装自测。取 ID 与写入步骤见 `development/DEVELOPMENT.md` §6。
|
||||
- 当 `access_scope = 0`(不公开)时,平台会把 `developer_ids` 中的用户自动补写到 `skill_user_access`。
|
||||
- `developer_ids` 建议写为正整数数组;第一个 ID 会作为主开发者同步到 `skills.developer_id`。
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
2. 已在技能市场安装本技能(或更新到最新版)
|
||||
3. 【网页自动化类技能】首次运行一般会自动登记本平台账号;也可在「账号管理」中指定登录标识。浏览器会话由系统隔离管理——**不要**用本机日常浏览器用户目录去跑真实站
|
||||
4. 【按平台】若本技能说明「需要先登录目标站」,请在弹出的有头浏览器中按提示登录或扫码;「登记账号」不等于「已登录目标站」。若说明「可不登录也能用」,仍须有可用的账号会话目录
|
||||
5. 【如需】需要的文件 / 时间范围 / 单号等已准备好
|
||||
5. 【网页自动化类技能】已登记账号可在「数据管理 / 账号管理」查看;每个账号都有独立浏览器会话
|
||||
6. 【如需】需要的文件 / 时间范围 / 单号等已准备好
|
||||
|
||||
---
|
||||
|
||||
@@ -58,6 +59,7 @@
|
||||
| 现象 | 你可以先试 |
|
||||
|------|------------|
|
||||
| 提示未登录 / 无权限 | 确认匠厂已登录;业务账号是否在线、权限是否足够 |
|
||||
| 提示账号不存在 / 被占用 | 到「数据管理 / 账号管理」确认账号记录和状态,稍后再试;不要重复创建账号 |
|
||||
| 提示缺少必要信息 | 把时间范围、单号、文件等说完整后再试一次 |
|
||||
| 一直没有结果 | 到任务中心查看进度或失败原因;必要时重新发起 |
|
||||
| 结果不对 | 用更明确的条件再说一次,或缩小范围重试 |
|
||||
|
||||
30
acceptance-reports/README.md
Normal file
30
acceptance-reports/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 验收报告目录(acceptance-reports)
|
||||
|
||||
本目录存放**技能交活验收报告**,供技术人员查看并按未通过项整改。
|
||||
|
||||
## 谁写、写什么
|
||||
|
||||
- 验收人(或 AI 编程工具)对照 `development/CHECKLIST.md` 执行验收后,**必须**在本目录落盘报告。
|
||||
- 标准与条目定义见:`development/CHECKLIST.md`(含强制输出规则)。
|
||||
|
||||
## 命名(强制)
|
||||
|
||||
```text
|
||||
YYYYMMDD-HHMMSS-acceptance.md
|
||||
```
|
||||
|
||||
示例:`20260722-175000-acceptance.md`
|
||||
|
||||
- 使用验收执行时的本地时间
|
||||
- 不要覆盖旧报告;保留历史便于对比是否修好
|
||||
|
||||
## 技术人员怎么用
|
||||
|
||||
1. `git pull` 拉取含报告的最新代码(或验收人推送后的提交)
|
||||
2. 打开本目录中**时间戳最新**的一份 `*-acceptance.md`
|
||||
3. 只看文末「给技术人员的修改清单」及所有 `FAIL` / 未通过的 `NEED_MANUAL`
|
||||
4. 改完后告知验收人复验;新一轮会再生成一份新时间戳报告
|
||||
|
||||
## 模板仓说明
|
||||
|
||||
本 `skill-template` 仓库通常不产生业务验收报告;此目录随模板复制到新技能后使用。
|
||||
@@ -4,7 +4,7 @@
|
||||
- `examples/`:CLI 成功输出形状示例(虚构路径与数据)。
|
||||
- `schemas/`:轻量 JSON Schema(`skill-actions.schema.json` 为**新技能严格规范**;`task-log-record.schema.json` 等)。
|
||||
|
||||
`skill-actions.schema.json` 使用 `additionalProperties: false`。正式支持 `bind.tables`(toolbar Action 必填);`row`/`batch` placements 与 `concurrency`/`locks` 仍为预留。`placements` 与 `executionProfile` 正交——进任务中心只看 `async`,与入口无关。表级 Action 与 Agent 契约见 [`references/ACTIONS.md`](../references/ACTIONS.md);开发深度规范见 [`development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。
|
||||
`skill-actions.schema.json` 使用 `additionalProperties: false`。正式支持 `bind.tables`(toolbar Action 必填)与可选 `bind.inputMapping`;placements 含 `toolbar` / `row` / `batch` / `cron` / `agent` / `skill-detail`(对齐匠厂宿主)。`concurrency`/`locks` 仍为预留。`placements` 与 `executionProfile` 正交——进任务中心只看 `async`,与入口无关。表级 Action 与 Agent 契约见 [`references/ACTIONS.md`](../references/ACTIONS.md);开发深度规范见 [`development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。
|
||||
|
||||
- 用户市场四 Tab 见根目录 [`README.md`](../README.md) / [`TUTORIAL.md`](../TUTORIAL.md) / [`DEMO.md`](../DEMO.md) / [`CHANGELOG.md`](../CHANGELOG.md)
|
||||
- Agent 调用/编排参考见 [`references/`](../references/)(含 [`ACTIONS.md`](../references/ACTIONS.md))
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"placement": {
|
||||
"type": "string",
|
||||
"enum": ["toolbar", "row", "batch", "cron", "agent", "skill-detail"],
|
||||
"description": "稳定支持 toolbar/cron/agent/skill-detail;row/batch 为预留入口,新技能示例不得使用。placements 与 executionProfile 正交,不做条件限制。"
|
||||
"description": "稳定支持 toolbar/row/batch/cron/agent/skill-detail(对齐匠厂宿主)。placements 与 executionProfile 正交,不做条件限制。"
|
||||
},
|
||||
"scalarArg": {
|
||||
"type": ["string", "number", "boolean"]
|
||||
@@ -48,7 +48,11 @@
|
||||
"type": "array",
|
||||
"minItems": 1
|
||||
},
|
||||
"sensitive": { "type": "boolean" }
|
||||
"sensitive": { "type": "boolean" },
|
||||
"readOnly": {
|
||||
"type": "boolean",
|
||||
"description": "宿主数据管理表单只读展示该字段(仍会随提交传入 entrypoint);用于行内/勾选预填的主键等"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputSchema": {
|
||||
@@ -110,10 +114,15 @@
|
||||
"pattern": "^[a-z][a-z0-9_]*$",
|
||||
"description": "英文 snake_case 业务表名;须真实存在于技能 SQLite / _jiangchang_tables"
|
||||
},
|
||||
"description": "数据管理表级绑定:决定 toolbar Action 出现在哪些表。宿主兼容旧技能时,缺失 bind 可视为全表展示;新技能 toolbar Action 必须显式声明。"
|
||||
"description": "数据管理表级绑定:决定 toolbar/row/batch Action 出现在哪些表。宿主兼容旧技能时,缺失 bind 可视为全表展示;新技能 toolbar Action 必须显式声明。"
|
||||
},
|
||||
"inputMapping": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string", "minLength": 1 },
|
||||
"description": "可选。将 $row.$pk / $row.col / $selection.ids 等映射到 inputSchema 字段名,供 row/batch 预填。"
|
||||
}
|
||||
},
|
||||
"description": "Phase 1 仅沉淀 bind.tables;不包含 selection/inputMapping/columns/row/concurrency/locks"
|
||||
"description": "表级绑定;toolbar 必填。可选 inputMapping 供行内/批量预填。concurrency/locks 勿写入。"
|
||||
},
|
||||
"action": {
|
||||
"type": "object",
|
||||
|
||||
216
development/ACCOUNT_MANAGER.md
Normal file
216
development/ACCOUNT_MANAGER.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# account-manager 接入标准(账号 / Profile / 租约)
|
||||
|
||||
> 本文是浏览器 RPA 技能接入账号体系的统一标准。开发者和 AI 编程工具在编写 `account_client.py`、处理多账号、排查登录状态或验收浏览器任务前,必须阅读本文。
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 为什么必须依赖 account-manager](#1-为什么必须依赖-account-manager)
|
||||
- [2. 核心模型](#2-核心模型)
|
||||
- [3. 强制规则](#3-强制规则)
|
||||
- [4. 标准生命周期](#4-标准生命周期)
|
||||
- [5. 账号选择与多账号](#5-账号选择与多账号)
|
||||
- [6. 创建后去哪里查看](#6-创建后去哪里查看)
|
||||
- [7. 登录状态与 Profile 验收](#7-登录状态与-profile-验收)
|
||||
- [8. 租约与异常退出](#8-租约与异常退出)
|
||||
- [9. 工业级测试](#9-工业级测试)
|
||||
- [10. 排障顺序与提问证据](#10-排障顺序与提问证据)
|
||||
- [11. 安全边界](#11-安全边界)
|
||||
|
||||
## 1. 为什么必须依赖 account-manager
|
||||
|
||||
真实用户会长期使用同一个账号,也会在批量发布、矩阵运营、多店铺、多租户等场景中使用多个账号。一次打开临时浏览器、成功操作一两条数据,不能证明技能可交付。
|
||||
|
||||
account-manager 统一负责:
|
||||
|
||||
- 账号身份及平台归属;
|
||||
- 一账号一份持久化浏览器 Profile;
|
||||
- Cookie、会话和登录状态的持续复用;
|
||||
- 多任务之间的账号租约与并发互斥;
|
||||
- 账号状态、最近错误和运维排查入口。
|
||||
|
||||
业务技能只负责编排业务动作,不得再造一套账号库,也不得随意找一个浏览器目录直接运行。
|
||||
|
||||
## 2. 核心模型
|
||||
|
||||
| 对象 | 含义 | 业务技能应如何使用 |
|
||||
|------|------|--------------------|
|
||||
| `account` | 某个平台上的一个账号身份 | 通过 account-manager CLI 获取,不自行落库 |
|
||||
| `profile_dir` | 该账号独占的浏览器用户数据目录 | 只传给 `launch_persistent_context`,长期复用 |
|
||||
| 登录状态 | 目标站 Cookie / Session 等实际会话 | 由业务技能打开同一 Profile 后检查;账号已登记不代表已登录 |
|
||||
| `lease_token` | 当前任务对账号的临时独占权 | 打开浏览器前取得,所有退出路径释放 |
|
||||
| `session_status` / error | 会话健康和最近故障线索 | 登录失效或账号异常时按约定回写 |
|
||||
|
||||
关系必须保持为:
|
||||
|
||||
```text
|
||||
一个账号记录 ──绑定──► 一个稳定 profile_dir
|
||||
│
|
||||
└──任务运行前取得 lease_token ──► 同一时刻只允许一个任务使用
|
||||
```
|
||||
|
||||
## 3. 强制规则
|
||||
|
||||
1. 所有浏览器 RPA,包括公开页和无需登录的页面,都必须先经 account-manager 取得 `profile_dir`。
|
||||
2. 一个账号只能绑定一个稳定 Profile;不同账号禁止共用 Profile。
|
||||
3. 禁止使用本机日常 Chrome / Edge 用户目录,禁止每次运行创建无归属的临时 Profile。
|
||||
4. 获取账号时必须同时取得租约;仅 `account get <id>` 不能代替租约。
|
||||
5. 业务代码只通过单一 `scripts/service/account_client.py` 集中调用 account-manager CLI。
|
||||
6. 禁止 import account-manager 的 `service/`、`db/`、`util/`、`rpa_helpers` 等内部模块。
|
||||
7. 未取得非空 `profile_dir` 和 `lease_token` 时禁止打开浏览器。
|
||||
8. 账号均被占用时返回 `ERROR:LEASE_CONFLICT`,不得自动创建重复账号或换号绕过。
|
||||
9. 所有成功、失败、取消和异常路径都必须在 `finally` 中释放租约。
|
||||
10. 密码、Cookie、Token、短信验证码等敏感信息不得写入业务技能配置、数据库、日志或错误信息。
|
||||
|
||||
对应机器门禁:`POLICY-RPA-004`、`POLICY-RPA-006`。
|
||||
|
||||
## 4. 标准生命周期
|
||||
|
||||
```text
|
||||
解析用户指定账号
|
||||
→ ensure / pick 账号并取得租约
|
||||
→ 校验账号平台、profile_dir、lease_token
|
||||
→ 用 profile_dir 启动持久浏览器
|
||||
→ 打开目标站并检查实际登录状态
|
||||
→ 执行业务并记录结果
|
||||
→ 回写会话状态 / 最近错误
|
||||
→ 关闭浏览器
|
||||
→ finally 释放租约
|
||||
```
|
||||
|
||||
参考结构:
|
||||
|
||||
```python
|
||||
account = pick_web_account(platform=TARGET_PLATFORM, account_id=account_id, login_id=login_id)
|
||||
lease_token = account["lease_token"]
|
||||
try:
|
||||
profile_dir = account["profile_dir"]
|
||||
# 只用 profile_dir 启动 persistent context,然后检查登录并执行任务
|
||||
finally:
|
||||
release_lease(lease_token)
|
||||
```
|
||||
|
||||
浏览器启动、登录门禁、业务执行和关闭顺序见 [`RPA.md`](RPA.md) §0.2;CLI 封装边界见 [`ADAPTER.md`](ADAPTER.md)。
|
||||
|
||||
## 5. 账号选择与多账号
|
||||
|
||||
选择优先级:
|
||||
|
||||
1. 用户明确指定纯数字 `DEFAULT_ACCOUNT_ID`:先确认账号存在且平台一致,再通过可定位该账号的 pick 流程取得租约;不得只 `get` 后返回空租约。
|
||||
2. 用户指定 `DEFAULT_LOGIN_ID`:使用 `ensure-web --login-id` 获取或登记,并取得租约。
|
||||
3. 用户未指定账号:按平台 `ensure-web`,有则选择可用账号,无则登记一条。
|
||||
4. 多条记录无法唯一确定:返回 `ERROR:AMBIGUOUS_ACCOUNT`,要求用户明确选择。
|
||||
5. 匹配账号均被占用:返回 `ERROR:LEASE_CONFLICT`,等待或由运维确认租约状态。
|
||||
|
||||
批量和矩阵任务必须以账号为隔离单元:
|
||||
|
||||
- 每个账号独立 Profile、独立租约、独立任务结果和失败原因;
|
||||
- 同一账号默认串行,不因追求吞吐提高同账号并发;
|
||||
- 一个账号失败不得污染其他账号的 Profile 或结果;
|
||||
- 设置单账号批量上限、单位时间操作上限和任务总时长;
|
||||
- 风控、封禁或权限错误不得自动切换下一个账号继续试探。
|
||||
|
||||
## 6. 创建后去哪里查看
|
||||
|
||||
账号创建成功后必须可见、可查,不得只相信命令没有报错。
|
||||
|
||||
### 6.1 宿主数据管理
|
||||
|
||||
优先在匠厂宿主的**数据管理页面**查看 account-manager 数据:
|
||||
|
||||
- 账号记录:确认账号 ID、平台、账号名称 / 登录标识、状态、`profile_dir`、会话状态;
|
||||
- 租约记录:确认当前持有者、用途、到期时间和是否已释放;
|
||||
- 凭据记录:只能查看类型、引用和脱敏值,不应出现明文密码或 Token。
|
||||
|
||||
宿主版本若直接展示数据库表,对应核心表为 `accounts`、`account_leases`、`credentials`、`platforms`。不得在数据管理页面或 SQLite 工具中随意修改记录。
|
||||
|
||||
### 6.2 account-manager CLI
|
||||
|
||||
在 account-manager 仓库或宿主提供的入口中检查:
|
||||
|
||||
```powershell
|
||||
python scripts/main.py account list --platform <platform_key> --limit 50
|
||||
python scripts/main.py account get <account_id>
|
||||
python scripts/main.py lease list --active
|
||||
python scripts/main.py account open <account_id>
|
||||
```
|
||||
|
||||
`account open` 仅用于查看指定 Profile。业务运行仍须走带租约的 pick / ensure 流程。
|
||||
|
||||
### 6.3 本地用户数据目录
|
||||
|
||||
实际数据按宿主注入的用户隔离:
|
||||
|
||||
```text
|
||||
{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/account-manager/
|
||||
├── account-manager.db
|
||||
└── profiles/
|
||||
└── {domain}/
|
||||
└── {platform_key}/
|
||||
└── {safe_label}[_{login_id_suffix}]/
|
||||
```
|
||||
|
||||
- `account-manager.db` 保存账号、平台、凭据元数据和租约。
|
||||
- `profiles/...` 是真实浏览器用户数据目录,保存对应账号的 Cookie / Session 等浏览器状态。
|
||||
- 实际路径以 `account get <id>` 返回的 `profile_dir` 为准,不得在代码里拼写固定盘符或用户 ID。
|
||||
- Profile 正在使用时禁止复制、移动、删除或同时打开;需要迁移或清理时先停止任务并释放租约。
|
||||
|
||||
## 7. 登录状态与 Profile 验收
|
||||
|
||||
“账号已创建”只表示账号记录和 Profile 已准备,不代表目标站已经登录。
|
||||
|
||||
最小验收必须完成:
|
||||
|
||||
1. 数据管理页面或 `account list` 能看到账号记录。
|
||||
2. `profile_dir` 非空、真实存在,并位于当前用户的 account-manager 数据目录。
|
||||
3. 两个不同账号的 `profile_dir` 不相同。
|
||||
4. 第一次用该 Profile 打开目标站,按平台要求人工登录或扫码。
|
||||
5. 正常关闭浏览器并释放租约。
|
||||
6. 再次取得同一账号租约,用同一 `profile_dir` 重开浏览器。
|
||||
7. 验证登录状态仍有效;失效时返回 `ERROR:REQUIRE_LOGIN`,不得假成功。
|
||||
|
||||
## 8. 租约与异常退出
|
||||
|
||||
- 租约持有者应使用技能 slug 或可追踪任务标识,`purpose` 写明用途。
|
||||
- 租约 TTL 应覆盖正常任务时长;长任务需要平台支持的续租策略,不能依赖永不过期。
|
||||
- `finally release_lease(...)` 是最低要求,浏览器关闭失败也不能跳过释放尝试。
|
||||
- 进程被强制结束后可能留下租约;先用 `lease list --active` 核对,确认没有真实任务仍在使用后再释放或清理。
|
||||
- 禁止看到租约冲突就直接删数据库记录、删 Profile、换账号或新建重复账号。
|
||||
|
||||
## 9. 工业级测试
|
||||
|
||||
除单账号冒烟测试外,至少覆盖:
|
||||
|
||||
- 指定账号 ID、指定登录标识和自动选择三条路径;
|
||||
- 账号不存在、平台不匹配、Profile 缺失、返回格式异常;
|
||||
- 同一账号并发抢占和 `LEASE_CONFLICT`;
|
||||
- 业务成功、业务失败、用户取消、浏览器崩溃后的租约释放;
|
||||
- 登录过期、验证码 / 二次验证、账号风险和权限不足;
|
||||
- 两个以上账号轮换且 Profile、登录态、结果互不污染;
|
||||
- 关闭重开后登录保持;
|
||||
- 批量长时间运行、限流、有限重试和断点续跑。
|
||||
|
||||
不得以“单账号成功操作一两次”作为多账号或矩阵能力已经交付的证明。
|
||||
|
||||
## 10. 排障顺序与提问证据
|
||||
|
||||
开发者和 AI 在群里提问前按顺序自查:
|
||||
|
||||
1. 阅读本文以及 [`RPA.md`](RPA.md)、[`ADAPTER.md`](ADAPTER.md)。
|
||||
2. 在数据管理页面确认账号记录是否存在、平台和状态是否正确。
|
||||
3. 执行 `account list/get`,核对账号 ID、`profile_dir`、`session_status`。
|
||||
4. 在文件系统确认 `profile_dir` 是否存在、是否错用了其他用户目录。
|
||||
5. 执行 `lease list --active`,确认是否被其他任务占用或残留。
|
||||
6. 查看任务日志、错误码、失败截图和浏览器当前页面。
|
||||
7. 判断问题属于账号不存在、租约冲突、登录失效、Profile 损坏、页面变化还是业务失败。
|
||||
|
||||
仍需协助时必须提供:技能 slug、平台、脱敏账号标识 / 账号 ID、任务时间、错误码、账号与租约检查结果、`profile_dir` 是否存在、脱敏日志和截图。禁止发送密码、Cookie、Token 或验证码。
|
||||
|
||||
## 11. 安全边界
|
||||
|
||||
- 账号和 Profile 只能用于用户或企业明确授权的正常业务操作。
|
||||
- 多账号管理用于账号隔离、矩阵业务和稳定调度,不得用于绕过频控、封禁或平台限制。
|
||||
- 禁止破解协议、逆向客户端、伪造设备指纹、验证码破解、养号、撞库、代理池轮换或自动换号继续试探。
|
||||
- 风控、封禁、安全警告和权限不足必须停止或转人工。
|
||||
- 不手工编辑 `account-manager.db`,不在账号使用期间操作 Profile 文件。
|
||||
|
||||
完整合规与风控边界见 [`AI_RPA.md`](AI_RPA.md)。
|
||||
@@ -1,6 +1,8 @@
|
||||
# 适配器标准:真实/仿真 × API/RPA 四档模式
|
||||
|
||||
> 凡是"连接三方系统"(ERP、CRM、SaaS、银行等)的 skill,都应采用本文的 **adapter 四档模式**。这样同一套业务逻辑可以在不同档位间切换:开发用 mock、半集成用 simulator、上线用真实,互不影响。
|
||||
> 凡是"连接三方系统"(ERP、CRM、SaaS、银行等)的 skill,都应采用本文的 **adapter 四档模式**。同一套业务逻辑可在不同档位间切换;**四档都保留**。
|
||||
|
||||
先遵守 [`AI_RPA.md`](AI_RPA.md) 的授权与技术红线:`real_api` 只连接公开或明确授权的官方接口;不得通过抓包、逆向或猜测私有协议构造“API”。没有授权 API 时使用正常界面 RPA;正常界面无法闭环且只能绕过安全机制时,停止并上报。
|
||||
|
||||
## 为什么要分档
|
||||
|
||||
@@ -15,17 +17,17 @@
|
||||
|
||||
| 档位 | 含义 | 默认策略 |
|
||||
|------|------|----------|
|
||||
| **`mock`** | 纯内存或 fixture,**默认单测/CI** | 模板 `.env.example` 默认 `OPENCLAW_TEST_TARGET=mock` |
|
||||
| **`simulator_rpa`** | 仿真站点或桌面仿真,可半集成 | 开发联调可选 |
|
||||
| **`real_api`** | 真实 API | 生产 / 集成测试显式设 `OPENCLAW_TEST_TARGET=real_api` |
|
||||
| **`real_rpa`** | 真实浏览器/真实系统 | 生产 / 集成测试显式设 `OPENCLAW_TEST_TARGET=real_rpa` |
|
||||
| **`mock`** | 纯内存或 fixture,离线 | **单测 / CI 必过**;mock 不通不要往下走 |
|
||||
| **`simulator_rpa`** | 仿真站点或桌面仿真,可半集成 | 按业务场景选用 |
|
||||
| **`real_api`** | 真实 API | 按业务场景选用(有官方接口时) |
|
||||
| **`real_rpa`** | 真实浏览器/真实系统 | **模板 `.env.example` 默认**;业务自测与交活用此档 |
|
||||
|
||||
- **mock**:纯离线、不联网,给单测 / CI / 开发自测,**保证可重复**。
|
||||
- **simulator_rpa**:操作仿真平台(如 `sandbox.jc2009.com`),跑端到端流程但不碰生产。
|
||||
- **real_api**:有官方接口时**首选**(最稳、最快、最易维护)。
|
||||
- **real_rpa**:没有 API 只能操作生产界面,**风险最高、放最后**。
|
||||
- **mock**:纯离线、不联网,给单测 / CI,**保证可重复**。**mock 通 ≠ 开发完成**。
|
||||
- **simulator_rpa**:操作仿真平台(如 `sandbox.jc2009.com`),按场景选用。
|
||||
- **real_api**:有官方接口时按场景选用。
|
||||
- **real_rpa**:真实浏览器路径;模板默认,须真实跑通后再交活。
|
||||
|
||||
> 推荐优先级:**real_api > simulator_rpa > real_rpa**,mock 永远保留做 CI。
|
||||
> **口径**:业务配置默认 `real_rpa`;单测/CI 用 `mock`。不得只 mock 通就交活;须 release 到匠厂宿主验收(含用户体验)。
|
||||
|
||||
配置读取见 `CONFIG.md`:**bootstrap 之后业务代码只通过 `config.get*()` 读 `OPENCLAW_TEST_TARGET` 等项**(进程 env > 用户 `.env` > `.env.example`)。
|
||||
|
||||
@@ -72,7 +74,7 @@ scripts/service/
|
||||
from jiangchang_skill_core import config
|
||||
|
||||
def get_adapter():
|
||||
target = (config.get("OPENCLAW_TEST_TARGET") or "mock").lower()
|
||||
target = (config.get("OPENCLAW_TEST_TARGET") or "real_rpa").lower()
|
||||
if target in ("unit", "mock"):
|
||||
return MockAdapter()
|
||||
if target == "real_api":
|
||||
@@ -100,9 +102,10 @@ def get_adapter():
|
||||
2. **普通兄弟技能调用**,优先走统一 **`service.sibling_bridge`**(`call_sibling_json`),**不要**在 `task_service.py`、`task_rpa.py` 等业务流程文件中到处散落 `subprocess.run`。
|
||||
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/`)。
|
||||
5. **pick/ensure lease 后必须 `finally release lease`**;进程被 kill 后可能残留 lease,需在运维文档说明排查方式(查 account-manager lease 列表 / 手动释放)。
|
||||
5. **所有选择路径都必须取得 lease**:包括纯数字 `DEFAULT_ACCOUNT_ID`。`account get <id>` 只能读取账号,不能单独作为运行结果;必须再通过可定位该账号的 pick 流程取得非空 `lease_token`。随后必须 `finally release lease`;进程被 kill 后可能残留 lease,需按 [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md) 排查。
|
||||
6. **网页 RPA 硬规则**(详见 [`RPA.md`](RPA.md) §0.2):必须先经 `pick_web_account`(底层 **`ensure-web` / `pick-web --ensure`**)拿到 `profile_dir` 再开浏览器;拿不到则失败退出;生产有头;**是否站点登录**按平台约定,但**不等于**可以跳过 account-manager。登记账号 ≠ 站点已登录。
|
||||
7. **账号选择优先级**:纯数字 `DEFAULT_ACCOUNT_ID` → `get <id>`;`DEFAULT_LOGIN_ID` / 非数字标识 → `ensure-web --login-id`;皆空 → 按平台 `ensure-web`。人读展示优先 `login_id` / `account_label`,不要只用数字 id。多条匹配 → `ERROR:AMBIGUOUS_ACCOUNT`。
|
||||
7. **账号选择优先级**:纯数字 `DEFAULT_ACCOUNT_ID` → 校验指定账号后带租约 pick;`DEFAULT_LOGIN_ID` / 非数字标识 → `ensure-web --login-id --lease`;皆空 → 按平台 `ensure-web --lease`。人读展示优先 `login_id` / `account_label`,不要只用数字 id。多条匹配或无法确保拿到指定 ID → `ERROR:AMBIGUOUS_ACCOUNT`。
|
||||
8. **运行前校验**:`profile_dir` 和 `lease_token` 均须非空;账号平台必须匹配;缺任一字段立即失败,不打开浏览器。
|
||||
|
||||
```python
|
||||
# 普通兄弟技能 — 走 sibling_bridge
|
||||
@@ -125,5 +128,6 @@ finally:
|
||||
## 相关文档
|
||||
|
||||
- `RPA.md` — 三端 RPA 技术选型与拟人/反反爬范式
|
||||
- `ACCOUNT_MANAGER.md` — 账号、Profile、租约、多账号、查看与排障标准
|
||||
- `CONFIG.md` — `.env` 里如何配置运行模式与目标地址
|
||||
- `TESTING.md` — 测试 target 与隔离体系
|
||||
|
||||
227
development/AI_RPA.md
Normal file
227
development/AI_RPA.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# AI + RPA 技能开发总纲
|
||||
|
||||
本文是技术人员与 AI 编程工具进入本模板后的**第一份架构说明**。先理解本文,再按业务类型阅读 [`RPA.md`](RPA.md)、[`ADAPTER.md`](ADAPTER.md)、[`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md) 与对应 `examples/`。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [一句话定义](#1-一句话定义)
|
||||
2. [职责边界](#2-职责边界)
|
||||
3. [标准执行闭环](#3-标准执行闭环)
|
||||
4. [合规与技术红线](#4-合规与技术红线)
|
||||
5. [工业级 RPA 风控](#5-工业级-rpa-风控)
|
||||
6. [等待与操作节奏](#6-等待与操作节奏)
|
||||
7. [三端统一与选型](#7-三端统一与选型)
|
||||
8. [结果契约](#8-结果契约)
|
||||
9. [开发与交付顺序](#9-开发与交付顺序)
|
||||
|
||||
## 1. 一句话定义
|
||||
|
||||
**AI Agent 是用户入口与业务编排者,RPA 是确定性执行器。**
|
||||
|
||||
- Agent 负责理解用户意图、补齐参数、判断风险、选择 Action、发起任务和解释结果。
|
||||
- RPA 负责按已验证的步骤操作浏览器、桌面软件或手机,不临时理解模糊业务。
|
||||
- 业务规则放在唯一 domain service 中;Agent、CLI、Action、定时任务与数据管理入口复用同一内核。
|
||||
- RPA 必须返回结构化结果、进度与存证,由 Agent 用业务语言反馈给用户。
|
||||
|
||||
本模板追求的是**稳定、合规、可控制、可追溯、可交付的工业级界面自动化**,不是与第三方安全机制对抗。
|
||||
|
||||
## 2. 职责边界
|
||||
|
||||
| 层级 | 负责 | 不负责 |
|
||||
|------|------|--------|
|
||||
| Agent | 意图理解、缺参追问、风险确认、Action 选择、结果解释 | 不猜界面步骤,不临时改业务规则,不直接绕过安全验证 |
|
||||
| Action / 编排层 | 参数校验、鉴权、任务状态、进度、暂停停止、日志、人工介入 | 不按入口复制业务逻辑 |
|
||||
| Domain Service | 业务规则、幂等、防重复、状态流转、结果汇总 | 不关心来自 Agent、定时任务还是工具栏 |
|
||||
| Adapter / RPA | 调官方 API 或按确定流程操作界面 | 不理解模糊意图,不自行扩大权限或操作范围 |
|
||||
| 宿主 | Action 展示、任务中心、数据管理、共享 runtime | 不替技能实现业务规则 |
|
||||
|
||||
Agent 与 RPA 的边界必须保持清晰:
|
||||
|
||||
- 把不确定的理解与沟通交给 Agent。
|
||||
- 把可验证、可重复的执行步骤交给代码和 RPA。
|
||||
- 不让 Agent 在运行时自由生成高风险界面操作。
|
||||
- 不让 RPA 根据模糊文本自行决定付款、删除、审批、发布或发送范围。
|
||||
|
||||
## 3. 标准执行闭环
|
||||
|
||||
```text
|
||||
用户自然语言
|
||||
→ Agent 识别意图与参数
|
||||
→ 缺参追问 / 风险确认
|
||||
→ 选择已声明的 Skill Action
|
||||
→ 编排层做校验、鉴权、预检、幂等检查
|
||||
→ Domain Service 执行业务规则
|
||||
→ Adapter 选择 API / 浏览器 / 桌面 / 手机
|
||||
→ RPA 分步骤执行并持续上报进度
|
||||
→ 验证码、人脸、U盾等转人工处理
|
||||
→ 返回结构化结果、日志、截图与可选视频
|
||||
→ Agent 向用户解释结果与下一步
|
||||
```
|
||||
|
||||
### 执行前
|
||||
|
||||
Agent 与编排层必须确认:
|
||||
|
||||
- 用户意图、操作对象、范围与必填参数明确。
|
||||
- 当前账号、数据、设备和目标软件属于用户或企业授权范围。
|
||||
- 查询、准备数据与外部副作用已经分开。
|
||||
- 付款、删除、审批、发布、发送等不可逆动作已取得明确确认。
|
||||
- 浏览器 Profile、桌面窗口或手机设备已通过预检。
|
||||
- 同一业务键不会被重复提交。
|
||||
|
||||
### 执行中
|
||||
|
||||
- 每个用户可感知的动作都要有中文进度 step。
|
||||
- 使用可暂停、可停止的等待;禁止 RPA 主路径裸 `asyncio.sleep`。
|
||||
- 同一账号、Profile 或设备默认串行执行。
|
||||
- 遇到安全验证、权限不足、频控或风险提示时,转人工或安全停止。
|
||||
- 批量任务记录处理位置,支持失败定位和必要的断点续跑。
|
||||
|
||||
### 执行后
|
||||
|
||||
- 先验证业务成功标志,再记录成功;不得把“点击完成”当成“业务成功”。
|
||||
- 返回成功、部分成功、失败、停止或等待人工等明确状态。
|
||||
- 保存必要的任务日志、错误码、失败截图和可选录屏。
|
||||
- Agent 根据结构化结果解释完成数量、失败原因和下一步,不直接粘贴技术日志。
|
||||
|
||||
## 4. 合规与技术红线
|
||||
|
||||
### 允许
|
||||
|
||||
- 使用用户或企业合法拥有、明确授权的账号与业务权限。
|
||||
- 通过正常界面入口完成用户本来有权完成的操作。
|
||||
- 使用浏览器 DOM、Windows UI Automation、Android/iOS 控件树等公开自动化能力。
|
||||
- 采用真实点击、逐字输入、自然鼠标轨迹、分段滚动和合理业务节奏。
|
||||
- 遇到验证码、人脸、指纹、短信、U盾或动态口令时暂停并请求人工处理。
|
||||
- 在授权范围内保存必要的日志、截图、视频和业务结果。
|
||||
|
||||
### 禁止
|
||||
|
||||
- 破解、反编译、逆向第三方软件或分析其私有协议。
|
||||
- 调用未经公开授权的隐藏接口,或通过抓包拼装私有请求代替正常界面。
|
||||
- 绕过登录、验证码、人脸、指纹、短信、U盾、设备绑定、签名或权限校验。
|
||||
- 注入、Hook、篡改第三方进程、客户端、APK、IPA、安全模块或通信内容。
|
||||
- 窃取、共享或复用未授权的 Cookie、Token、Session、密码或设备凭据。
|
||||
- 撞库、暴力尝试、自动破解验证码、批量养号或伪造身份。
|
||||
- 通过换账号、换代理、伪造设备指纹、提高并发等方式规避平台限制。
|
||||
- 识别到风险提示或封禁后继续自动重试、试探或隐藏自动化行为。
|
||||
|
||||
“采用 RPA”不代表任何任务都可以自动执行。技能仍须遵守账号授权、数据权限、目标平台规则、合同约定和适用法律;边界不清时停止并请求负责人确认。
|
||||
|
||||
## 5. 工业级 RPA 风控
|
||||
|
||||
### 风控信号与处理
|
||||
|
||||
| 信号 | 标准处理 |
|
||||
|------|----------|
|
||||
| 页面或软件加载缓慢 | 等待真实状态,必要时有限重试 |
|
||||
| 普通网络错误 | 递增退避后有限重试 |
|
||||
| 登录失效 | 请求用户重新登录 |
|
||||
| 验证码、短信、人脸、指纹、U盾 | 进入人工处理,禁止自动绕过 |
|
||||
| 频率限制 | 停止当前批次或长时间退避 |
|
||||
| 账号风险、封禁或安全警告 | 立即停止,禁止自动换号或继续试探 |
|
||||
| 权限不足 | 返回明确失败,不尝试扩大或绕过权限 |
|
||||
| 页面或控件结构变化 | 截图并停止,交由开发人员重新验证 |
|
||||
| 提交结果不明确 | 查询业务状态;确认前禁止再次提交 |
|
||||
|
||||
### 运行限制
|
||||
|
||||
- 同一账号、Profile、桌面会话或手机设备默认只运行一个任务。
|
||||
- 设置单批数量、单位时间操作量和总运行时长上限。
|
||||
- 只对明确可重试错误做有限重试;禁止无限循环。
|
||||
- 采用递增退避,不以快速重复点击或刷新解决延迟。
|
||||
- 关键副作用采用“准备数据 → 用户确认 → 执行 → 结果核验”两阶段流程。
|
||||
- 风控策略用于减少误操作、系统压力和误判,不得用于规避第三方安全控制。
|
||||
|
||||
## 6. 等待与操作节奏
|
||||
|
||||
区分三类等待,禁止只靠固定睡眠碰运气。
|
||||
|
||||
### 状态等待
|
||||
|
||||
页面加载、控件可用、弹窗消失、保存成功、下载完成等必须等待**真实条件**并设置超时。固定等待不能代替状态判断。
|
||||
|
||||
### 操作节奏
|
||||
|
||||
真实业务环境中,两个正常业务动作之间默认随机等待 **1~3 秒**:
|
||||
|
||||
```env
|
||||
STEP_DELAY_MIN=1.0
|
||||
STEP_DELAY_MAX=3.0
|
||||
```
|
||||
|
||||
- 输入采用逐字符随机延迟。
|
||||
- 鼠标采用自然移动轨迹与小幅随机偏移。
|
||||
- 滚动采用分段操作。
|
||||
- 提交后等待业务结果,禁止重复点击。
|
||||
- 测试 / sandbox 可使用更短节奏,但不得把测试节奏用于真实第三方系统。
|
||||
|
||||
### 前后缓冲
|
||||
|
||||
- 浏览器或软件启动:先等待会话与目标页面/窗口真实就绪,再额外缓冲约 `1.5~2.5` 秒。
|
||||
- 页面导航:关键控件就绪后,再缓冲约 `1~2` 秒。
|
||||
- 普通流程结束:确认业务结果、写日志与存证后,再缓冲约 `2~3` 秒关闭。
|
||||
- 付款、审批、发布等重要动作:确认结果并截图后,可缓冲约 `3~5` 秒。
|
||||
|
||||
缓冲时间只用于稳定界面和改善用户观感,不能作为成功判定。RPA 等待必须使用 `interruptible_sleep` 或共享可控制原语。
|
||||
|
||||
## 7. 三端统一与选型
|
||||
|
||||
优先选择最稳定、最受支持的执行方式:
|
||||
|
||||
1. 本地确定性逻辑可以完成时,不启动 RPA。
|
||||
2. 有稳定且获得授权的官方 API 时,优先 API。
|
||||
3. 只能通过网页操作时,使用浏览器 RPA。
|
||||
4. 目标为 Windows 原生软件时,优先 UI Automation。
|
||||
5. 目标为手机 App 时,使用系统控件树与正常设备自动化。
|
||||
6. 只有破解、逆向或绕过安全机制才能完成时,停止开发并上报,不改走违规路线。
|
||||
|
||||
三端详细标准见 [`RPA.md`](RPA.md)。当前浏览器 RPA 已有可复制案例;桌面与手机仍须基于真实项目继续沉淀,未验证内容不得包装为成熟方案。
|
||||
|
||||
## 8. 结果契约
|
||||
|
||||
RPA 至少返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"message": "任务已完成",
|
||||
"summary": {
|
||||
"total": 10,
|
||||
"success_count": 9,
|
||||
"failed_count": 1
|
||||
},
|
||||
"items": [],
|
||||
"errors": [],
|
||||
"artifacts": {
|
||||
"screenshots": [],
|
||||
"video_path": null
|
||||
},
|
||||
"next_actions": []
|
||||
}
|
||||
```
|
||||
|
||||
状态至少能区分:
|
||||
|
||||
- `success`:全部完成。
|
||||
- `partial`:部分成功,列出失败项。
|
||||
- `failed`:未完成,给出稳定错误码与阶段。
|
||||
- `waiting_human`:等待人工验证或接管,不是假失败。
|
||||
- `confirmation_required`:等待用户批准副作用。
|
||||
- `stopped`:用户或宿主主动停止。
|
||||
|
||||
具体字段可按业务扩展,但不得只返回控制台文本或把技术 traceback 直接交给用户。
|
||||
|
||||
## 9. 开发与交付顺序
|
||||
|
||||
1. 填写 [`REQUIREMENTS.md`](REQUIREMENTS.md) 中的 Agent、授权、风险、人工介入与结果设计。
|
||||
2. 判断采用 API、浏览器、桌面或手机,并选择真实 / 仿真 adapter。
|
||||
3. 定义用户意图、Action、输入 schema 与确认策略。
|
||||
4. 先实现 domain service、结构化结果和 mock 测试。
|
||||
5. 再实现 RPA 会话、真实操作、状态等待、进度、截图与人工介入。
|
||||
6. 在真实界面上确认 selector / 控件,禁止凭经验猜测。
|
||||
7. 完成仿真测试、真实业务闭环和宿主多入口验收。
|
||||
8. 使用 [`CHECKLIST.md`](CHECKLIST.md) 做第三方交活验收。
|
||||
|
||||
AI 编程工具不得为了“跑通”擅自采用破解、逆向、私有协议、验证码绕过、进程注入、无限重试或未授权凭据。正常界面无法完成闭环时,应停止并向负责人报告边界。
|
||||
169
development/CHECKLIST.md
Normal file
169
development/CHECKLIST.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 技能交活验收 Checklist(第三方视角)
|
||||
|
||||
checklist_version: 1.0
|
||||
|
||||
本清单用于**旁人 / 测试 / AI 编程工具**对任意业务技能做交活验收。
|
||||
它描述的是「验什么、怎样算过」,**不是**开发规范摘要。写法与实现约束见同目录其他文档。
|
||||
|
||||
---
|
||||
|
||||
## 验收输出强制规则(凡执行本清单必须遵守)
|
||||
|
||||
> 面向 AI 与人工验收员:只要开始按本 Checklist 验收,下列规则**全部强制**,无需用户在提示词里再重复。
|
||||
|
||||
1. **必须落盘报告**,禁止只在对话里给出口头结论。
|
||||
2. **报告目录(唯一合法位置)**:技能仓库**根目录**下的 `acceptance-reports/`
|
||||
(与 `development/`、`scripts/` 平级;**禁止**写到 `development/`、`tests/`、临时目录或其他路径。)
|
||||
3. **报告文件名(唯一合法格式)**:`YYYYMMDD-HHMMSS-acceptance.md`
|
||||
示例:`20260722-175000-acceptance.md`
|
||||
时间取验收执行时的本地时间;**禁止**使用 `report.md`、`验收.md`、无时间戳的文件名。
|
||||
4. 若 `acceptance-reports/` 不存在,**先创建该目录再写入**。
|
||||
5. 报告**开头**必须写明:
|
||||
- 完整相对路径:`acceptance-reports/YYYYMMDD-HHMMSS-acceptance.md`
|
||||
- 技能 slug、当前 git 提交短哈希(若可得)、`checklist_version`
|
||||
6. 报告正文必须**逐条**对应下方条目,每条给出:`PASS` / `FAIL` / `N/A` / `NEED_MANUAL`,并附简短证据(文件路径、命令、观察结果)。
|
||||
7. 报告末尾必须有「给技术人员的修改清单」:只列出未通过项(`FAIL` 与未通过的 `NEED_MANUAL`),按 P0 → P1 → P2 排序。
|
||||
8. **禁止修改业务代码**来“凑”验收通过;本清单的执行结果是报告,不是改代码。
|
||||
9. 若用户只说「按 Checklist 验收」而未指定路径:仍必须遵守本节省略规则,不得省略落盘。
|
||||
|
||||
**结果取值**
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| PASS | 已满足 |
|
||||
| FAIL | 不满足 |
|
||||
| N/A | 本技能不适用(须说明原因) |
|
||||
| NEED_MANUAL | 须人工点选/真机;报告中写清已做或未做 |
|
||||
|
||||
**级别**
|
||||
|
||||
| 级别 | 含义 |
|
||||
|------|------|
|
||||
| P0 | 不通过不可交活 |
|
||||
| P1 | 限期整改 |
|
||||
| P2 | 体验建议 |
|
||||
|
||||
---
|
||||
|
||||
## 0. 技能画像(验收前先填,用于裁剪)
|
||||
|
||||
先根据技能实际情况勾选;后续章节不适用则整项标 `N/A` 并注明原因。
|
||||
|
||||
- [ ] S0-1 交互形态:纯本地 / API / 网页RPA / 桌面RPA / 手机RPA / 编排(调兄弟技能)
|
||||
- [ ] S0-2 目标站登录策略:required / optional / not_needed / 不适用
|
||||
- [ ] S0-3 是否依赖账号会话(account-manager 或等价 Profile):是 / 否
|
||||
- [ ] S0-4 持久化:无 / 仅任务日志 / 另有业务表
|
||||
- [ ] S0-5 Skill Action:无 / 仅同步短任务 / 含异步长任务
|
||||
- [ ] S0-6 是否需要在任务中心查看进度或结果:是 / 否
|
||||
- [ ] S0-7 外部系统账号、数据、设备与操作权限的授权来源:
|
||||
- [ ] S0-8 是否包含提交、付款、删除、审批、发布、发送等不可逆副作用:是 / 否
|
||||
|
||||
---
|
||||
|
||||
## 1. 安装与可运行(所有技能)
|
||||
|
||||
- [ ] A1-1 **P0** 按平台方式加载/安装后,技能可被识别(有名称,能打开详情或执行入口)
|
||||
- [ ] A1-2 **P0** 健康检查(如 `health`)能跑完且结果可读;缺关键条件时有明确失败提示,而非空白崩溃
|
||||
- [ ] A1-3 **P0** 版本查询(如 `version`)返回的版本与对外宣称一致
|
||||
- [ ] A1-4 **P1** 若有用户可见配置:说明可读;按说明改关键项后行为符合说明
|
||||
|
||||
---
|
||||
|
||||
## 2. 对外说明是否自洽(所有技能)
|
||||
|
||||
- [ ] A2-1 **P0** 市场「说明」能让非开发者理解:做什么、给谁用、开始前准备什么
|
||||
- [ ] A2-2 **P0** 「说明/教程」不以开发目录、内部实现细节作为用户主路径
|
||||
- [ ] A2-3 **P1** 「教程」可按真实顺序跟做(准备 → 步骤 → 失败怎么办)
|
||||
- [ ] A2-4 **P1** 「演示」结构完整(有演示信息位;无视频则标明暂无,不假装有)
|
||||
- [ ] A2-5 **P0** 「更新日志」能对应到当前交付版本
|
||||
- [ ] A2-6 **P0** 平台/Agent 侧说明中,常见用户意图的触发方式与真实能力一致(短操作 vs 长任务不混淆)
|
||||
|
||||
---
|
||||
|
||||
## 3. 主能力行为(所有技能)
|
||||
|
||||
- [ ] A3-1 **P0** 主成功路径能按宣称完成核心动作(至少 1 次完整闭环)
|
||||
- [ ] A3-2 **P0** 缺参或非法输入时稳定失败(可读错误,不假成功)
|
||||
- [ ] A3-3 **P1** 失败可诊断:能从任务记录/提示判断失败阶段,而非只有“失败了”
|
||||
- [ ] A3-4 **P0** 同一能力从已声明的不同入口触发时,业务结果一致(不出现入口分叉成两套逻辑)
|
||||
|
||||
---
|
||||
|
||||
## 4. 调度与长任务(有 Action 或长任务时适用)
|
||||
|
||||
- [ ] A4-1 **P0** 秒级只读/短操作:不进入任务中心也能完成(若技能提供此类能力)
|
||||
- [ ] A4-2 **P0** 长耗时/浏览器/需进度或取消的能力:经 Agent 或技能动作触发后进入任务中心(或平台等价异步队列)
|
||||
- [ ] A4-3 **P0** 异步任务在任务中心能看到进行中与结束状态;结束有成功或失败结论
|
||||
- [ ] A4-4 **P1** 进行中的进度/步骤信息对用户可理解
|
||||
- [ ] A4-5 **P1** 已对外暴露的动作参数覆盖主路径关键输入(无“命令行能做、动作入口不能做”的关键缺口)
|
||||
|
||||
---
|
||||
|
||||
## 5. 账号与浏览器会话(网页 RPA 适用;其他形态标 N/A 或改测等价项)
|
||||
|
||||
- [ ] A5-1 **P0** 未准备好账号/浏览器档案时,主路径失败并提示,不会默默用本机默认浏览器身份跑起来
|
||||
- [ ] A5-2 **P0** 登录策略为 required 时:未登录不能把做不成的事当成成功
|
||||
- [ ] A5-3 **P0** 交付联调默认能看见浏览器窗口(有头);不能把无头当作唯一可验收形态
|
||||
- [ ] A5-4 **P1** 浏览器窗口启动形态满足交付约定(如最大化或可视区域足够操作与人工介入)
|
||||
- [ ] A5-5 **P1** 验证码或人工确认:有等待与超时;超时后任务失败可理解,不无限挂死或假成功
|
||||
- [ ] A5-6 **P0** **NEED_MANUAL** 真实站或约定仿真站主路径至少完整跑通 1 次;仅模拟数据通过不算交活
|
||||
- [ ] A5-7 **P0** 账号创建后可在宿主数据管理和 `account list/get` 查到,返回的 `profile_dir` 在当前用户数据目录真实存在
|
||||
- [ ] A5-8 **P0** 每个账号使用独立且稳定的 Profile;不同账号不共用,本机日常浏览器目录不参与运行
|
||||
- [ ] A5-9 **P0** 指定账号 ID、指定登录标识和自动选择均取得非空租约;成功、失败、取消和异常路径最终释放
|
||||
- [ ] A5-10 **P0** **NEED_MANUAL** 登录后关闭浏览器并释放租约,再取同一账号重开,登录状态仍有效;失效时明确提示重新登录
|
||||
- [ ] A5-11 **P1** 至少两个账号连续或轮换测试通过,Profile、登录态、结果和失败互不污染
|
||||
- [ ] A5-12 **P1** 租约冲突、残留租约和账号登录失效能按文档自助定位,不通过删库、删 Profile 或自动换号处理
|
||||
|
||||
---
|
||||
|
||||
## 6. 结果落点(按画像裁剪)
|
||||
|
||||
- [ ] A6-1 **P0** 宣称可追溯时:跑完后任务记录中有对应条目(成功/失败均有)
|
||||
- [ ] A6-2 **P0** 宣称写入业务数据或可在数据管理查看时:成功跑完后能看到新业务数据;仅有任务摘要、业务侧仍空 → **FAIL**
|
||||
- [ ] A6-3 **P1** 重复执行同一业务键时,结果符合宣称(更新/去重/追加),无莫名脏数据或静默丢数据
|
||||
- [ ] A6-4 **P1** 「零结果成功」与「失败」可区分
|
||||
- [ ] A6-5 **P1** 若产出文件:落在约定数据位置且可找到,不丢在不明临时路径
|
||||
|
||||
---
|
||||
|
||||
## 7. 可观测与安全观感(所有技能)
|
||||
|
||||
- [ ] A7-1 **P0** 失败提示或任务结论不泄露密码、完整 Cookie、明文 token 等敏感信息
|
||||
- [ ] A7-2 **P1** 关键步骤有据可查(能对应到开始/关键节点/结束)
|
||||
- [ ] A7-3 **P1** 若宣称支持录屏或截图存证:打开开关后关键步骤或失败能留下可查看产物;宣称支持却完全无产物 → **FAIL**
|
||||
- [ ] A7-4 **P2** 用户可见文案偏业务语言,而非大段开发黑话
|
||||
|
||||
---
|
||||
|
||||
## 8. AI + RPA 合规与工业级风控(外部系统技能适用)
|
||||
|
||||
- [ ] A8-1 **P0** 已确认账号、数据、设备和业务操作均在用户或企业授权范围内;边界不明不能以“默认允许”通过
|
||||
- [ ] A8-2 **P0** 代码与运行路径不包含破解、反编译、逆向、未授权私有协议、抓包拼隐藏接口、Hook / 注入、验证码破解、设备指纹伪造、撞库、养号、自动换号或代理池规避限制
|
||||
- [ ] A8-3 **P0** 验证码、人脸、指纹、短信、U盾、频控、账号风险和权限不足能转人工或安全停止,不自动硬闯
|
||||
- [ ] A8-4 **P0** 付款、删除、审批、发布、发送等不可逆动作执行前有明确对象、范围和用户确认
|
||||
- [ ] A8-5 **P1** 页面 / 控件加载、保存和业务成功由真实状态判断,不以固定等待代替成功判定
|
||||
- [ ] A8-6 **P1** 真实 RPA 两个正常业务动作之间采用约 `1~3` 秒随机节奏;输入、鼠标和滚动符合正常人工操作方式
|
||||
- [ ] A8-7 **P1** 同一账号、Profile 或设备默认串行;批量、频率和运行时长有上限
|
||||
- [ ] A8-8 **P1** 只对明确可重试错误做有限重试与递增退避;风险、封禁、权限和结果不明确时不盲目重试
|
||||
- [ ] A8-9 **P1** 流程结束先核验业务结果并完成日志 / 截图 / 可选视频,再保留合理结束缓冲后关闭会话
|
||||
- [ ] A8-10 **P1** Agent 能区分成功、部分成功、失败、等待人工、等待确认和主动停止,并用业务语言解释结果
|
||||
|
||||
---
|
||||
|
||||
## 9. 质量门禁与交活底线(所有技能)
|
||||
|
||||
- [ ] A9-1 **P0** 仓库默认测试套件通过(执行技能约定的一键测试命令,如 `python tests/run_tests.py -v`)
|
||||
- [ ] A9-2 **P0** 不以「仅模拟档通过」作为交活依据(有真实档要求时必须真实跑通或书面阶段性豁免)
|
||||
- [ ] A9-3 **P0** **NEED_MANUAL** 已声明的宿主入口(详情/Agent/工具栏/定时等)按声明抽测可通过
|
||||
- [ ] A9-4 **P1** 对外版本一致:详情、version、更新日志、发布标签能对上
|
||||
- [ ] A9-5 **P1** 开发者按平台规则能安装并测到该技能(若平台有开发者可见性要求)
|
||||
|
||||
---
|
||||
|
||||
## 10. 报告结尾必填
|
||||
|
||||
报告末尾固定三块:
|
||||
|
||||
1. **结论**:通过 / 不通过(任意 P0 为 FAIL 或不通过的 NEED_MANUAL → 不通过)
|
||||
2. **统计**:PASS / FAIL / N/A / NEED_MANUAL 数量;P0 FAIL 列表
|
||||
3. **给技术人员的修改清单**:仅未通过项,带条目 ID 与期望现象
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
```ini
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
OPENCLAW_TEST_TARGET=real_rpa # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认真实浏览器;本地单测/CI 请用 mock
|
||||
|
||||
# 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(必须,便于介入与排查);1=后台静默(勿作日常用法)
|
||||
@@ -58,7 +58,7 @@ OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(必须,便于介入与排查
|
||||
|
||||
```ini
|
||||
# ── 运行模式 / adapter 档位(见 development/ADAPTER.md)──
|
||||
OPENCLAW_TEST_TARGET=real_rpa # 生产默认真实 RPA;单测/CI 可改为 mock
|
||||
OPENCLAW_TEST_TARGET=mock # 见 development/ADAPTER.md;单测用 mock
|
||||
|
||||
# ── 好看视频 / 百度账号(须与 account-manager 平台 key 一致)──
|
||||
TARGET_PLATFORM=baidu
|
||||
@@ -73,7 +73,7 @@ HAOKAN_VIDEO_SELECTOR=video.art-video
|
||||
|
||||
```ini
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
OPENCLAW_TEST_TARGET=real_rpa # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认真实浏览器;本地单测/CI 请用 mock
|
||||
|
||||
# 目标网站地址:技能要访问的网站或服务地址
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
|
||||
@@ -87,8 +87,8 @@ DEFAULT_ACCOUNT_ID= # 留空=自动;只填纯数字;手
|
||||
# 是否显示浏览器窗口:网页自动化时是否弹出可见浏览器
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(必须,便于介入与排查);1=后台静默(勿作日常用法)
|
||||
|
||||
# 浏览器防检测:降低被网站识别为自动化工具的概率
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启(推荐),0=关闭;一般保持默认
|
||||
# 浏览器兼容配置:仅在项目授权与平台规则允许时使用共享兼容能力
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=使用共享兼容配置,0=关闭;不得用于伪造身份或绕过平台限制
|
||||
|
||||
# 是否录制操作过程:用于留证、复现问题或向他人演示
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并生成带说明的视频文件;需要留证时再改为 1
|
||||
@@ -96,11 +96,11 @@ OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并
|
||||
# 失败时是否截图:任务出错时自动保存现场截图,便于排查
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图(推荐),0=不截图
|
||||
|
||||
# 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
|
||||
# 步骤最短等待:两个正常业务动作之间随机停顿的下限
|
||||
STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
|
||||
|
||||
# 步骤最长等待:两步操作之间随机停顿的上限
|
||||
STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
|
||||
STEP_DELAY_MAX=3.0 # 单位:秒;默认与最短等待组成 1~3 秒节奏;状态等待须另行判断
|
||||
|
||||
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
|
||||
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
|
||||
@@ -169,9 +169,10 @@ config.get_float("STEP_DELAY_MIN", 1.0)
|
||||
- **留空即未配置**:`KEY=` 或 `KEY= # 说明` 必须读成空字符串,再走技能自己的「自动 / ensure」逻辑;**禁止**把示例说明、`# 可填…` 当成真实配置值。
|
||||
- **需要 platform-kit ≥ 1.2.2**:旧版解析曾把「空值 + 行尾 `#`」误当成 `# 可填…` 这种假值。技能若依赖正确空值语义,须在 `SKILL.md` 声明 `platform_kit_min_version: "1.2.2"`(或更高)。
|
||||
- **网页账号配置语义**(有则用):
|
||||
- `DEFAULT_ACCOUNT_ID`:仅**纯数字**视为账号主键;非数字非空可当 login_id(不推荐,优先用 `DEFAULT_LOGIN_ID`)
|
||||
- `DEFAULT_LOGIN_ID`:手机号 / 用户名等;留空则按平台 `ensure-web`
|
||||
- `DEFAULT_ACCOUNT_ID`:仅**纯数字**视为账号主键;校验指定记录后仍必须取得该账号租约,不能只 `account get`
|
||||
- `DEFAULT_LOGIN_ID`:手机号 / 用户名等;留空则按平台 `ensure-web`;非数字 `DEFAULT_ACCOUNT_ID` 可兼容为 login_id,但不推荐
|
||||
- 占位串(`#…`、`none`、`n/a`、`default-account`)一律当未配置
|
||||
- **用户隔离必须一致**:调用 account-manager 时继承宿主注入的 `JIANGCHANG_DATA_ROOT` / `JIANGCHANG_USER_ID`;不得覆盖为开发者自己的固定路径,否则账号会写到另一位用户的数据目录。
|
||||
- **不要**在 `.env.example` 里塞「看起来像真值」的演示账号(如假手机号),以免首次落盘后被当成用户配置。
|
||||
|
||||
## health / config-path
|
||||
@@ -189,6 +190,7 @@ config.get_float("STEP_DELAY_MIN", 1.0)
|
||||
## 相关文档
|
||||
|
||||
- `RPA.md` — 三端 RPA 标准与各开关含义(开发向;勿写入 `.env` 注释)
|
||||
- `ACCOUNT_MANAGER.md` — 账号选择、Profile、租约、数据位置与验收
|
||||
- `ADAPTER.md` — `OPENCLAW_TEST_TARGET` 四档模式(开发向;勿写入 `.env` 注释)
|
||||
- `RUNTIME.md` — `JIANGCHANG_DATA_ROOT` 与数据根约定
|
||||
- `DATA_PATHS.md` — 下载/导入/导出等子目录与 `resolve_data_path` 规范
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 技能开发教程
|
||||
|
||||
这份文档是给**技术人员**看的,目标不是解释概念,而是让你拿到 `skill-template` 后,可以**一步一步开发出一个新的 skill**。
|
||||
这份文档是给**技术人员**看的,目标不是解释概念,而是让你拿到**业务技能仓库**后,可以**一步一步开发出一个新的 skill**。
|
||||
|
||||
本文默认你开发的是当前最常见的一类业务 skill:
|
||||
|
||||
@@ -13,77 +13,40 @@
|
||||
|
||||
## 推荐 AI 开发工具
|
||||
|
||||
当前 skill 开发建议尽量配合 AI 编程工具使用。这样做不是为了替代技术人员,而是为了提升以下环节的效率:
|
||||
建议用 AI 编程工具辅助搭目录、补样板、写测试与排错。团队宜统一 1~2 个主力,避免协作习惯发散。
|
||||
|
||||
- 搭建标准目录结构
|
||||
- 生成样板代码
|
||||
- 理解旧项目代码
|
||||
- 批量补文档、注释和测试
|
||||
- 辅助排查报错与重构代码
|
||||
| 场景 | 建议 |
|
||||
|------|------|
|
||||
| 一体化 IDE | Cursor 或 Windsurf |
|
||||
| 终端深度代理 | Claude Code 或 Aider |
|
||||
| 国内生态 | Trae / 通义灵码 |
|
||||
| VS Code 插件 | GitHub Copilot 或 Cline |
|
||||
|
||||
建议团队统一选择 1 到 2 个主力工具长期使用,避免每个人工具链差异太大,导致协作方式不一致。
|
||||
官方下载与文档以各产品站点为准;技能开发步骤以本文与同目录规范为准,不依赖某一家工具的专有流程。
|
||||
|
||||
下面先列国外主流工具,再列国内主流工具。链接优先使用官方站点、官方文档或官方安装入口。
|
||||
|
||||
### 国外主流工具
|
||||
|
||||
| 工具 | 类型 | 适合场景 | 官方入口 |
|
||||
|------|------|----------|----------|
|
||||
| Cursor | 独立 AI IDE | 代码编辑、Agent 开发、整仓理解 | <a href="https://www.cursor.com/" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://www.cursor.com/downloads" target="_blank" rel="noopener noreferrer">下载</a> |
|
||||
| Windsurf | 独立 AI IDE | Agent 编程、项目生成、连续开发流 | <a href="https://docs.codeium.com/windsurf" target="_blank" rel="noopener noreferrer">文档</a> / <a href="https://windsurf.com/download" target="_blank" rel="noopener noreferrer">下载</a> |
|
||||
| GitHub Copilot | IDE 插件 / 编程助手 | 日常补全、解释代码、生成函数、配合 VS Code 或 JetBrains 使用 | <a href="https://github.com/copilot" target="_blank" rel="noopener noreferrer">官网</a> |
|
||||
| Claude Code | 终端 / IDE 编程代理 | 命令行开发、代码库分析、自动改代码、运行命令 | <a href="https://www.anthropic.com/claude-code" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://docs.anthropic.com/en/docs/claude-code/" target="_blank" rel="noopener noreferrer">文档</a> |
|
||||
| Codex | 终端 / IDE / Web 编程代理 | OpenAI 官方编码代理,适合代码生成、理解、调试、评审 | <a href="https://developers.openai.com/codex/" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://developers.openai.com/codex/quickstart" target="_blank" rel="noopener noreferrer">快速开始</a> |
|
||||
| Aider | 终端 AI 编程工具 | 已有代码仓库的增量开发、终端协作、快速提交 | <a href="https://www.aider.chat/" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://aider.chat/docs/" target="_blank" rel="noopener noreferrer">文档</a> |
|
||||
| Cline | VS Code / JetBrains 插件 | 编辑器内 Agent 开发、命令执行、浏览器联动调试 | <a href="https://cline.bot/" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://docs.cline.bot/introduction/welcome" target="_blank" rel="noopener noreferrer">文档</a> / <a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev" target="_blank" rel="noopener noreferrer">VS Code 插件</a> |
|
||||
|
||||
### 国内主流工具
|
||||
|
||||
| 工具 | 类型 | 适合场景 | 官方入口 |
|
||||
|------|------|----------|----------|
|
||||
| Trae | 独立 AI IDE | AI 辅助写代码、项目搭建、对话式开发 | <a href="https://www.trae.ai/home" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://www.trae.ai/download" target="_blank" rel="noopener noreferrer">下载</a> |
|
||||
| 通义灵码 | 独立 IDE / IDE 插件 | 国内团队日常编码、问答、补全、代码生成 | <a href="https://tongyi.aliyun.com/lingma/?channel=yy_AiBot" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://tongyi.aliyun.com/lingma/download" target="_blank" rel="noopener noreferrer">下载</a> |
|
||||
| CodeGeeX | IDE 插件 / 开源助手 | 代码补全、生成、注释、跨语言辅助 | <a href="https://github.com/zai-org/CodeGeeX" target="_blank" rel="noopener noreferrer">GitHub</a> / <a href="https://marketplace.visualstudio.com/items?itemName=aminer.codegeex" target="_blank" rel="noopener noreferrer">VS Code 插件</a> |
|
||||
| 腾讯 CodeBuddy | IDE 插件 | 代码补全、测试生成、智能问答、腾讯云开发体系协作 | <a href="https://www.codebuddy.ai/" target="_blank" rel="noopener noreferrer">官网</a> / <a href="https://www.tencentcloud.com/document/product/1256" target="_blank" rel="noopener noreferrer">文档</a> / <a href="https://marketplace.visualstudio.com/items?itemName=Tencent-Cloud.coding-copilot" target="_blank" rel="noopener noreferrer">VS Code 插件</a> |
|
||||
| 百度文心快码(Baidu Comate) | IDE 插件 | 国内研发团队辅助编码、解释、测试、优化 | <a href="https://comate.baidu.com/zh" target="_blank" rel="noopener noreferrer">官网</a> |
|
||||
|
||||
### 选型建议
|
||||
|
||||
如果你们团队主要做这类 Python skill 开发,我建议这样选:
|
||||
|
||||
- 想要一体化最强体验:优先试 `Cursor` 或 `Windsurf`
|
||||
- 想要命令行深度协作:优先试 `Claude Code` 或 `Aider`
|
||||
- 想继续基于 VS Code 插件体系:优先试 `GitHub Copilot`、`Cline`、`通义灵码`、`CodeBuddy`
|
||||
- 想优先使用国内生态与中文支持:优先试 `Trae`、`通义灵码`、`CodeGeeX`、`CodeBuddy`、`文心快码`
|
||||
|
||||
### 团队落地建议
|
||||
|
||||
为了减少培训成本,建议内部至少统一一套主工具方案:
|
||||
|
||||
- 国外方案:`Cursor` + `Claude Code`
|
||||
- 国内方案:`Trae` + `通义灵码`
|
||||
- VS Code 插件方案:`GitHub Copilot` + `Cline`
|
||||
|
||||
不建议每位技术人员完全自由发挥,否则后续在:
|
||||
|
||||
- 提示词写法
|
||||
- 代码修改习惯
|
||||
- 调试方式
|
||||
- 提交节奏
|
||||
|
||||
这些方面会越来越不统一。
|
||||
开发中常用的**公开公共仓**(skill-template / account-manager / jiangchang-platform-kit 等,可 `git clone` 学习,目录可扩展)见 [`SHARED_REPOS.md`](SHARED_REPOS.md)。
|
||||
|
||||
## 1. 先理解模板的定位
|
||||
|
||||
`skill-template` 不是业务 skill,它只是一个**新 skill 仓库模板**。
|
||||
|
||||
你不应该直接在这个仓库里开发业务,而应该:
|
||||
开发任何外部系统技能前,**先读 [`AI_RPA.md`](AI_RPA.md)**。本模板的统一定位是:Agent 负责理解、确认与编排,RPA 负责通过正常界面确定性执行;只做授权范围内的正规业务自动化,禁止破解、逆向、私有协议、验证码绕过或其他对抗第三方安全机制的实现。
|
||||
|
||||
0. 按 [`NAMING.md`](NAMING.md) 确定 slug(`{verb}-{noun-phrase}-{platform}`)
|
||||
1. **优先**用 [`tools/scaffold_skill.ps1`](../tools/scaffold_skill.ps1) 创建新目录(见 [`tools/README.md`](../tools/README.md))
|
||||
2. 在新目录内 `git init` 并绑定**本技能**远端(**不得**保留模板 `.git`)
|
||||
3. 把占位内容替换掉
|
||||
4. 再开始写业务逻辑
|
||||
你不应该直接在这个仓库里开发业务。新技能仓库有**两种合法来源**(详见 §4「第一步」):
|
||||
|
||||
| 来源 | 适用情况 | 怎么做 |
|
||||
|------|----------|--------|
|
||||
| **A. Gitea 克隆** | 项目经理已在自建 Gitea([https://git.jc2009.com/](https://git.jc2009.com/),**不是** Gitea 官网)开好业务仓并给你权限 | `git clone` 到本地后按 [`REQUIREMENTS.md`](REQUIREMENTS.md) 开发 |
|
||||
| **B. 本地模板复制** | 你本机已有 `skill-template` 源码,要新建尚未灌仓的技能目录 | **优先** [`tools/scaffold_skill.ps1`](../tools/scaffold_skill.ps1)(见 [`tools/README.md`](../tools/README.md));也可手工复制但必须清掉模板 `.git` |
|
||||
|
||||
拿到仓库后的推荐顺序:
|
||||
|
||||
0. 按 [`NAMING.md`](NAMING.md) 确定 / 核对 slug(`{verb}-{noun-phrase}-{platform}`)
|
||||
1. **先填写**本仓 [`REQUIREMENTS.md`](REQUIREMENTS.md)(范围与验收),再写业务代码
|
||||
2. 来源 B:在新目录内 `git init` 并绑定**本技能**远端(**不得**保留模板 `.git`)
|
||||
3. 把占位内容替换掉;**尽早**配置 `developer_ids`(见 §6,开发期宿主自测必做)
|
||||
4. 再实现 `scripts/service/` 等业务逻辑
|
||||
5. 本地测试通过 → `release.ps1` → Gitea CI → 匠厂多入口验收(§15)
|
||||
|
||||
> **Git 红线**:禁止资源管理器整文件夹复制后保留模板 `.git`;`git remote -v` 必须指向新技能仓库,不能仍是 skill-template。
|
||||
|
||||
@@ -170,7 +133,7 @@ scripts/
|
||||
|
||||
若 skill 需要浏览器/桌面/手机自动化,按以下顺序落地:
|
||||
|
||||
1. **先读三份标准**:`RPA.md`(含 §0.2:Profile 强制 / 有头 / 登录分平台)、`CONFIG.md`(`.env` 落盘与读取)、`ADAPTER.md`(四档 adapter)。
|
||||
1. **先读总纲与四份标准**:`AI_RPA.md`(职责、合规、风控与完整闭环)、`ACCOUNT_MANAGER.md`(账号 / Profile / 租约 / 多账号 / 查看排障)、`RPA.md`(含 §0.2:Profile 强制 / 有头 / 登录分平台)、`CONFIG.md`(`.env` 落盘与读取)、`ADAPTER.md`(四档 adapter)。
|
||||
2. **从 examples 选择性复制**(不要整包照搬 `examples/<mode>/`,见各 example README 的 copy map):
|
||||
- 若是 **真实浏览器 RPA**(真实网站、登录态、验证码、滚动采集),**必须先读** `examples/real_browser_rpa/README.md`,再按需复制到 `scripts/service/`:
|
||||
- `examples/real_browser_rpa/scripts/service/browser_session.py`
|
||||
@@ -185,16 +148,18 @@ scripts/
|
||||
- `examples/simulator_browser_rpa/scripts/service/task_service.py`(async 编排)
|
||||
- `sandbox/demo_app.html` 仅留在 examples,不进入生产 skill
|
||||
- **禁止**:`rpa_helpers`、sync Playwright 用于完整技能 RPA 主路径、`task_service` 散落 account-manager subprocess
|
||||
- **网页硬规则**:未拿到 `profile_dir` 禁止开浏览器;生产 `OPENCLAW_BROWSER_HEADLESS=0`;站点是否登录写进 REQUIREMENTS / 用户教程,**不要**写成「可不接 account-manager」
|
||||
- **网页硬规则**:未拿到非空 `profile_dir + lease_token` 禁止开浏览器;一账号一 Profile;生产 `OPENCLAW_BROWSER_HEADLESS=0`;站点是否登录写进 REQUIREMENTS / 用户教程,**不要**写成「可不接 account-manager」
|
||||
3. **只用共享库,不在 skill 里重写反反爬**:
|
||||
```python
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.rpa import launch_persistent_browser, anti_detect, wait_for_captcha_pass
|
||||
```
|
||||
上述 import 来自宿主共享 runtime 安装的 `jiangchang-platform-kit`,不是技能目录副本。
|
||||
4. **mock 档必须离线可跑**(`OPENCLAW_TEST_TARGET=mock`);sim_rpa / real_* 按需单独测。
|
||||
4. **单测/CI 须 mock 离线可过**;业务 `.env` 默认 `real_rpa`,须真实跑通。**不得只 mock 通就交活**;须 release 到匠厂宿主验收(见 §15,含用户体验)。多账号或矩阵能力还须按 `ACCOUNT_MANAGER.md` 覆盖至少两个账号、租约冲突、关闭重开保持登录和异常释放,不能以单账号一两次成功代替规模验证。
|
||||
5. **桌面/手机**:本期标准见 RPA.md 第 2/3 节,复用 `jiangchang_desktop_sdk` / `screencast`,**不要在新 skill 里重复造包**(尚待实战验证)。
|
||||
|
||||
**技术路线红线:**正常界面或获得授权的官方 API 无法完成闭环时,停止并向负责人说明;禁止 AI 或开发者擅自改用破解、反编译、逆向、私有协议、Hook / 注入、验证码破解、设备指纹伪造、自动换号或无限重试。
|
||||
|
||||
---
|
||||
|
||||
### 3.1 `requirements.txt` 依赖规范
|
||||
@@ -256,13 +221,26 @@ release workflow 会对 `scripts/` 下的 Python 源码做加密/打包。当前
|
||||
|
||||
下面这套顺序建议严格按步骤做,不要一上来就直接写 `service`。
|
||||
|
||||
### 第一步:复制模板并改目录名
|
||||
### 第一步:拿到新技能仓库(两种来源)
|
||||
|
||||
例如你要开发 `disburse-payroll-icbc` 一类领域 skill(目录名 = slug):
|
||||
例如你要开发 `disburse-payroll-icbc` 一类领域 skill(目录名 = slug)。下列两种来源**同等合法**,按你实际拿到仓库的方式选一条。
|
||||
|
||||
#### 推荐方式(首选)
|
||||
#### 来源 A:从 Gitea 克隆(项目经理已开仓)
|
||||
|
||||
在 **skill-template 仓库根目录**执行:
|
||||
1. 确认项目经理已在自建 Gitea([https://git.jc2009.com/](https://git.jc2009.com/),公司基于开源 Gitea 自建的代码仓库,**不要**访问 Gitea 官网当作业务仓地址)创建**本技能**仓库,并给你拉取/推送权限。
|
||||
2. 克隆到本地(示例):
|
||||
|
||||
```powershell
|
||||
git clone https://git.jc2009.com/<org>/<your-skill-slug>.git
|
||||
cd <your-skill-slug>
|
||||
git remote -v # 确认 origin 指向本技能仓,不是 skill-template
|
||||
```
|
||||
|
||||
3. 若仓内已是从本模板 scaffold 好的结构,直接进入「第二步」与 [`REQUIREMENTS.md`](REQUIREMENTS.md);**不要**再整仓复制 `skill-template` 覆盖,以免冲掉已有提交或串 Git 历史。
|
||||
|
||||
#### 来源 B:本地已有 skill-template 时复制 / scaffold
|
||||
|
||||
在 **skill-template 仓库根目录**执行(推荐):
|
||||
|
||||
```powershell
|
||||
.\tools\scaffold_skill.ps1 -Slug disburse-payroll-icbc -Destination D:\OpenClaw\client-gdcm\disburse-payroll-icbc
|
||||
@@ -276,7 +254,7 @@ git remote -v # 确认 origin 不是 skill-template
|
||||
|
||||
脚手架会排除 `.git`、缓存与 `.env`,并删除 `.openclaw-skill-template` 标记;**不会**自动 `git init`。
|
||||
|
||||
#### 禁止方式
|
||||
##### 禁止方式(来源 B)
|
||||
|
||||
| 做法 | 后果 |
|
||||
|------|------|
|
||||
@@ -284,7 +262,7 @@ git remote -v # 确认 origin 不是 skill-template
|
||||
| ❌ 保留模板 `.git` 只改 `remote url` | 历史、分支、对象库仍属 template |
|
||||
| ❌ 未删 `.git` 就 `git init` | 嵌套/混乱仓库,难以排查 |
|
||||
|
||||
#### 若已手工复制(补救)
|
||||
##### 若已手工复制(补救)
|
||||
|
||||
```powershell
|
||||
cd <新技能目录>
|
||||
@@ -296,7 +274,7 @@ git remote add origin <新技能仓库 URL>
|
||||
git remote -v
|
||||
```
|
||||
|
||||
#### AI / 编程代理复制红线
|
||||
##### AI / 编程代理复制红线
|
||||
|
||||
| 禁止 | 说明 |
|
||||
|------|------|
|
||||
@@ -307,9 +285,9 @@ git remote -v
|
||||
|
||||
目录名要和 skill slug 对齐,后面很多地方都依赖这个命名。
|
||||
|
||||
### 第二步:先改 4 个最关键的标识
|
||||
### 第二步:先改关键标识与占位
|
||||
|
||||
复制后优先改下面这些地方:
|
||||
拿到仓库并填好 / 更新 [`REQUIREMENTS.md`](REQUIREMENTS.md) 后,优先改下面这些地方:
|
||||
|
||||
1. `SKILL.md`
|
||||
2. 根目录市场四 Tab:`README.md` / `TUTORIAL.md` / `DEMO.md` / `CHANGELOG.md`
|
||||
@@ -325,7 +303,7 @@ git remote -v
|
||||
- 平台内部键
|
||||
- 日志 logger 名
|
||||
|
||||
此外,如果该技能发布后默认不公开(`access_scope = 0`),建议一开始就把 `SKILL.md` 中的 `metadata.openclaw.developer_ids` 配好。这样后续发布到平台时,开发者本人仍能在技能市场中看到并验证该技能。
|
||||
**开发 / 联调阶段务必尽早**配置 `SKILL.md` 中的 `metadata.openclaw.developer_ids`(完整目的与取 ID 步骤见 §6「关于 developer_ids」)。开发期技能在匠厂常为不公开;不加本人用户 ID,**技术人员自己也无法在技能市场看到并安装自测**。
|
||||
|
||||
## 5. 哪些占位内容必须替换
|
||||
|
||||
@@ -385,14 +363,28 @@ git remote -v
|
||||
- `references/`(CLI / SCHEMA)
|
||||
- 代码注释与 `service/` 实现
|
||||
|
||||
### 关于 `metadata.openclaw.developer_ids`
|
||||
### 关于 `metadata.openclaw.developer_ids`(开发自测必做)
|
||||
|
||||
这是一个平台发布元数据字段,用于解决下面这个问题:
|
||||
#### 目的(先理解再填)
|
||||
|
||||
- 技能发布后若平台记录中的 `access_scope = 0`,技能默认不公开
|
||||
- 如果不额外授权,连开发者自己也可能在技能市场里看不到这个技能
|
||||
开发 / 测试阶段,技能 `release` 到匠厂后,平台侧**默认不公开**(`access_scope = 0`):
|
||||
|
||||
因此可以在 `SKILL.md` 中声明:
|
||||
- 技能市场里对普通人**不可见**
|
||||
- **若不额外授权,连开发该技能的技术人员自己也看不见、装不了**
|
||||
- 看不见 → 无法安装 → 无法在宿主做 §15 的多入口验收(新建任务、数据管理、定时任务等)
|
||||
|
||||
因此 `developer_ids` 不是可有可无的装饰字段,而是**开发期自测通行证**:把本人(及需要一起验收的同事)的匠厂**用户 ID** 写进 `SKILL.md`,发布时平台将这些用户补进可见 / 可访问范围,使开发者能在「对其他人仍不可见」的前提下完成安装与测试。
|
||||
|
||||
#### 如何获取匠厂用户 ID(完整步骤)
|
||||
|
||||
1. **下载并安装**匠厂客户端:[https://jc2009.com/product.html](https://jc2009.com/product.html)
|
||||
2. **注册并登录**(使用将用于开发自测的账号)
|
||||
3. 打开客户端左下角头像旁的**设置**(齿轮)
|
||||
4. 在 **用户信息** 中查看 **用户 ID**(正整数),点击旁边的 **复制**
|
||||
5. 将该 ID 写入本技能 `SKILL.md` 的 `metadata.openclaw.developer_ids`(见下例)
|
||||
6. **必须替换**模板里的示例 ID(如 `10032`、`12428` 等占位),不要原样留着模板作者的 ID 却指望自己账号能看见技能
|
||||
|
||||
#### 在 `SKILL.md` 中声明
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
@@ -400,17 +392,17 @@ metadata:
|
||||
slug: your-skill-slug
|
||||
category: 通用
|
||||
developer_ids:
|
||||
- 1032
|
||||
- 12428
|
||||
- 12345 # 换成「你的用户ID」(匠厂「设置 → 用户信息」复制的正整数)
|
||||
```
|
||||
|
||||
约定如下:
|
||||
约定如下(原有规则保留):
|
||||
|
||||
- 只允许填写正整数用户 ID
|
||||
- 只允许填写正整数用户 ID(来自匠厂宿主,不是 Gitea / Git 账号名)
|
||||
- 推荐使用数组,即使当前只有 1 个开发者
|
||||
- 发布时平台会把这些用户自动补写到 `skill_user_access`
|
||||
- 第一个 ID 会同步到 `skills.developer_id`
|
||||
- 一期只做“补授权”,不会因为你 later 修改数组而自动撤销旧授权
|
||||
- **首次正式 release 前**就应配好;配错或漏配时,CI 可能成功,但你在技能市场仍找不到技能
|
||||
|
||||
## 7. 文档目录分工
|
||||
|
||||
@@ -744,7 +736,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
.\release.ps1
|
||||
```
|
||||
|
||||
如果你的技能使用了 `metadata.openclaw.developer_ids`,那么这一步触发的发布工作流除了同步 `skills` / `skill_versions` 外,还会在平台侧自动补开发者可见权限。测试非公开技能时,建议重点验证这部分是否生效。
|
||||
发布前 **`developer_ids` 应已配置**(见 §6)。本步触发的发布工作流除了同步 `skills` / `skill_versions` 外,还会在平台侧自动补开发者可见权限;发布后应重点确认:用本人账号能在技能市场看到并安装该技能。
|
||||
|
||||
这一步会自动完成标准发布动作,包括:
|
||||
|
||||
@@ -789,9 +781,11 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- 工作流文件是否存在
|
||||
- 发布包结构是否符合模板规范
|
||||
|
||||
### 第四步:进入匠厂平台下载安装包
|
||||
### 第四步:进入匠厂客户端(用于安装验收)
|
||||
|
||||
当工作流成功后,就可以进入匠厂平台验证最终安装效果。
|
||||
当工作流成功后,就可以进入匠厂客户端验证最终安装效果。
|
||||
|
||||
若你已按 §6 为 `developer_ids` 安装并登录过匠厂,**直接使用同一客户端、同一账号**即可,无需重复下载。若尚未安装:
|
||||
|
||||
匠厂产品下载地址:
|
||||
|
||||
@@ -803,11 +797,13 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
|
||||
匠厂产品页可从这里进入:[产品下载 - 匠厂](https://jc2009.com/product.html)
|
||||
|
||||
### 第五步:安装匠厂后,在技能市场检查最新 skill
|
||||
> 取用户 ID 写入 `developer_ids` 的完整步骤见 §6;此处侧重发布后的安装验收。
|
||||
|
||||
安装并启动匠厂后,进入左侧“技能市场”,搜索或查找刚刚发布的 skill,确认以下内容:
|
||||
### 第五步:在技能市场检查最新 skill
|
||||
|
||||
- 技能可以被正常检索到
|
||||
使用已写入 `developer_ids` 的账号登录并启动匠厂后,进入左侧“技能市场”,搜索或查找刚刚发布的 skill,确认以下内容:
|
||||
|
||||
- 技能可以被正常检索到(开发期不公开时,**仅** `developer_ids` 内账号可见)
|
||||
- 技能名称、说明、版本信息正确
|
||||
- 最新版本已经同步出来
|
||||
- 可以正常安装或更新
|
||||
@@ -826,11 +822,35 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- 安装后状态正常
|
||||
- 不会出现缺文件、缺入口或安装失败的问题
|
||||
|
||||
### 第七步:在“新建任务”中实际使用该 skill
|
||||
### 第七步:按声明做宿主多入口验收(已声明的入口都要测;须看用户体验)
|
||||
|
||||
安装完成后,不要只停留在“已安装”状态,还需要进入“新建任务”页面,真正调用一次该 skill,完成最终验证。
|
||||
安装完成后,不要只停留在“已安装”状态,也**不要**只在本地 mock 通就认为完成。宿主侧栏有多条与技能相关的入口;**按本技能 `assets/actions.json` 的 `placements` / `executionProfile` 声明逐项自测**(契约见 [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md)、[`../references/ACTIONS.md`](../references/ACTIONS.md))。未声明的入口可以跳过;**已声明却测不到,视为验收失败**。不要只测对话、忽略其它入口。
|
||||
|
||||
建议至少验证:
|
||||
**用户体验(与功能入口并列,后台通了不算完):**
|
||||
|
||||
- 失败时用户能否看懂原因(对话 / 任务中心 / 提示),不是只有技术日志
|
||||
- 长任务(async)是否有可见进度,能否取消或等待说明
|
||||
- 网页 RPA 是否有头运行;需登录/验证码时是否停下等人,而不是静默失败
|
||||
- 数据管理:表名/字段中文、按钮文案是否清楚;结果是否写回用户看得见的地方
|
||||
- 市场说明 / 教程是否与真实行为一致
|
||||
|
||||
| 宿主入口(侧栏 / 界面) | 技能侧如何挂上 | 建议验收什么 |
|
||||
|-------------------------|----------------|--------------|
|
||||
| **新建任务**(对话 Agent) | `placements` 含 `agent`;或短查询走共享 Python CLI | 自然语言能触发主流程;长任务 / RPA 须走 `run_skill_action`,禁止 bash 干等 |
|
||||
| **数据管理 · 表顶栏** | `placements` 含 `toolbar`(须合法非空 `bind.tables`) | 左侧能看到本技能库表;表顶栏出现按钮;点按行为符合预期 |
|
||||
| **数据管理 · 行内** | `placements` 含 `row`(建议 `bind.tables` + `bind.inputMapping` 预填主键) | 行内操作链接可见(需有主键列);点按后入参/行为正确 |
|
||||
| **数据管理 · 批量** | `placements` 含 `batch` | 勾选行后批量按钮可用;无勾选时禁用;批量行为正确 |
|
||||
| **定时任务** | `placements` 含 `cron`(创建时选「技能直调」) | 能选到本技能 Action、保存并触发;到点或「立即运行」行为正确 |
|
||||
| **任务中心** | 不是 placement;由 `executionProfile: "async"` 决定是否进 Job | 长任务出现进度 / 可取消;来源标签与触发入口一致 |
|
||||
| **技能市场 → 技能详情** | `placements` 含 `skill-detail`(详情直调) | **必验**安装与四 Tab。详情页「技能直调」按钮随宿主版本上线;**已声明则须在具备该 UI 的宿主上完成按钮自测**(与其它入口同等对待,不要只靠 Agent) |
|
||||
|
||||
补充说明:
|
||||
|
||||
- **进不进任务中心只看 `executionProfile`**,与从哪个入口触发无关(既有正交规则不变)。
|
||||
- 数据管理还依赖库表元数据(`_jiangchang_*` 等,见 [`../references/SCHEMA.md`](../references/SCHEMA.md));仅有 Action、没有可展示库表时,侧栏可能看不到表。
|
||||
- `row` / `batch` / `toolbar` / `cron` / `agent` / `skill-detail` 均以**当前匠厂宿主已实现能力**为准;字段与示例见 [`../references/ACTIONS.md`](../references/ACTIONS.md)。
|
||||
|
||||
**新建任务(对话)最小检查:**
|
||||
|
||||
- 新任务中可以正常选择或触发该 skill
|
||||
- skill 能被正确唤起
|
||||
@@ -841,6 +861,8 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
|
||||

|
||||
|
||||
若第五步在技能市场**搜不到**本技能:先核对登录账号的用户 ID 是否已写入 `developer_ids` 并随本次 release 发布(见 §6);不要只反复重装客户端。
|
||||
|
||||
## 16. 发布前检查清单
|
||||
|
||||
每个新 skill 发布前,建议技术人员逐条确认:
|
||||
@@ -849,6 +871,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- [ ] slug 符合 [`NAMING.md`](NAMING.md)(verb-noun-platform)
|
||||
- [ ] 目录名、`SKILL.md` slug、`constants.SKILL_SLUG` 三者一致
|
||||
- [ ] `SKILL.md` 中 slug、名称、描述都已替换
|
||||
- [ ] `SKILL.md` 的 `developer_ids` 已换成**本人**匠厂用户 ID(设置 → 用户信息 → 复制),不是模板示例 ID
|
||||
- [ ] `scripts/util/constants.py` 已修改
|
||||
- [ ] `../references/CLI.md` 示例命令已改成真实命令
|
||||
- [ ] `service` 下的核心业务文件(如 `task_service.py`)已按领域改名并实现
|
||||
@@ -868,7 +891,9 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- [ ] 如有 integration 测试需求,已写在 `tests/integration/` 下并保持 `.sample` 后缀
|
||||
- [ ] 本仓库**不是** skill-template 的误复制(根目录**无** `.openclaw-skill-template`)
|
||||
- [ ] `git remote -v` 指向**本技能**远端,URL 不含 skill-template 仓库名
|
||||
- [ ] `git log` 首条提交属于本技能(非模板历史)
|
||||
- [ ] `git log` 首条提交属于本技能(非模板历史)(来源 A 从 Gitea 克隆的已有业务仓,以该仓历史为准)
|
||||
- [ ] 业务 `.env` / `.env.example` 默认 `OPENCLAW_TEST_TARGET=real_rpa`(单测/CI 仍用 mock);**未只靠 mock 交活**
|
||||
- [ ] 发布后计划在宿主按 §15 第七步验收:已声明入口 + **用户体验**(进度、失败提示、有头 RPA、文案一致等)
|
||||
- [ ] 网页 RPA:`ensure-web` 取得 `profile_dir` 再开浏览器;`.env` 默认有头;REQUIREMENTS 写明登录策略(required/optional/not_needed,会话依赖型勿抄 optional);用户 README/TUTORIAL 区分「登记账号」与「站点登录」(`RPA.md` §0.2 / `POLICY-RPA-004`);依赖 kit 空值解析等修复时上调 `platform_kit_min_version`
|
||||
|
||||
## 17. 常见错误
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
| 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.3;development/LOGGING.md;development/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 再开浏览器;实现上应走 ensure-web);登录不全局强制,但不得跳过 account-manager | development/RPA.md §0.2;development/ADAPTER.md §兄弟依赖 | hard | 解析 `.env.example` + 扫描 `scripts/**/*.py` | `tests/test_development_policy_guard.py::TestPolicyRpa004` |
|
||||
| POLICY-RPA-005 | 必须保留 AI + RPA 总纲,明确 Agent / RPA 职责、授权范围、禁止破解/逆向/私有协议/安全验证绕过、人工介入、状态等待;真实业务默认动作节奏为 1~3 秒 | development/AI_RPA.md §4–§6;development/RPA.md §0.0/§0.3 | hard(文档与默认配置) / soft(真实行为) | 检查总纲关键边界;解析 `.env.example` 的 `STEP_DELAY_MIN=1.0` / `STEP_DELAY_MAX=3.0` | `tests/test_development_policy_guard.py::TestPolicyRpa005` |
|
||||
| POLICY-RPA-006 | 浏览器 RPA 必须保留 account-manager 生命周期标准;所有样例账号选择路径都须返回非空 `profile_dir + lease_token`,指定数字账号 ID 不得退化为无租约 `account get` | development/ACCOUNT_MANAGER.md;development/ADAPTER.md §兄弟依赖;development/RPA.md §0.2 | hard | 检查标准文档关键标记;扫描真实/仿真 `account_client.py` 的指定 ID 带租约实现并禁止空租约赋值 | `tests/test_development_policy_guard.py::TestPolicyRpa006` |
|
||||
| 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.4;development/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` |
|
||||
@@ -35,7 +37,7 @@
|
||||
| POLICY-SKILL-ACTION-001 | 存在 `development/SKILL_ACTION_RUNTIME.md`;schema 含 `executionProfile`;业务技能(非模板占位 slug)若 service 含 `RpaVideoSession` 则 `actions.json` 须有 `executionProfile=async` 且 placements 含 `agent` 的 action | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md | hard | 文档/schema/manifest 检查 | `tests/test_development_policy_guard.py::TestPolicySkillAction001` |
|
||||
| POLICY-SKILL-ACTION-002 | 每个 Action 必须显式声明 `executionProfile`,且只能是 `sync` 或 `async` | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | schema required + manifest 扫描 | `tests/test_development_policy_guard.py::TestPolicySkillAction002`;`tests/test_actions_manifest.py` |
|
||||
| POLICY-SKILL-ACTION-003 | toolbar Action 必须声明合法非空 `bind.tables`(snake_case、唯一) | references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | schema if/then + manifest 扫描 | `tests/test_development_policy_guard.py::TestPolicySkillAction003`;`tests/test_actions_manifest.py` |
|
||||
| POLICY-SKILL-ACTION-004 | `placements` 与 `executionProfile` 正交;文档不得把数据管理入口与异步执行方式硬绑定;Schema 须接受 toolbar/cron/agent/skill-detail × sync/async 全部组合 | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | Schema 4×2 矩阵校验 + 文档禁止短语扫描 | `tests/test_actions_manifest.py::test_schema_allows_all_placement_execution_profile_combinations`;`tests/test_development_policy_guard.py::TestPolicySkillAction004` |
|
||||
| POLICY-SKILL-ACTION-004 | `placements` 与 `executionProfile` 正交;文档不得把数据管理入口与异步执行方式硬绑定;Schema 须接受 toolbar/row/batch/cron/agent/skill-detail × sync/async 全部组合 | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | Schema 6×2 矩阵校验 + 文档禁止短语扫描 | `tests/test_actions_manifest.py::test_schema_allows_all_placement_execution_profile_combinations`;`tests/test_development_policy_guard.py::TestPolicySkillAction004` |
|
||||
| POLICY-ARCH-CORE-001 | 同一业务能力的 Action、CLI、Agent、cron、数据管理入口复用同一业务内核(文档要求已声明) | development/DEVELOPMENT.md;development/SKILL_ACTION_RUNTIME.md | hard(文档标记) / soft(语义) | 文档关键表述存在性;真实复用靠 sample/review | `tests/test_development_policy_guard.py::TestPolicyArchCore001`;`tests/samples/test_action_core_contract.py.sample` |
|
||||
| POLICY-DATA-SYNC-001 | 同步型业务须遵循幂等、事务、完整性与空结果保护(文档已声明) | references/SCHEMA.md;development/SKILL_ACTION_RUNTIME.md | hard(文档标记) / soft(语义) | 文档关键表述存在性;完整语义靠 sample/review | `tests/test_development_policy_guard.py::TestPolicyDataSync001`;`tests/samples/test_sync_contract.py.sample` |
|
||||
|
||||
@@ -56,7 +58,7 @@
|
||||
| slug 命名语义(verb-noun-platform) | development/NAMING.md | 部分覆盖于 `tests/test_slug_naming.py`,非本矩阵 hard policy |
|
||||
| 复制模板不得保留 `.git` / template marker | development/DEVELOPMENT.md §4 | 由 `tests/test_scaffold_guard.py` 守护,属脚手架场景 |
|
||||
| 共享 runtime 导入来源非技能目录 | development/RUNTIME.md | 运行时依赖已安装包,见 `tests/test_platform_import.py` |
|
||||
| RPA 拟人操作、选择器纪律、HITL 超时 | development/RPA.md §0–§1 | 行为与 DOM 质量,无法静态扫描 |
|
||||
| RPA 真实轨迹、选择器纪律、状态等待、HITL 超时与有限重试是否正确实现 | development/AI_RPA.md;development/RPA.md §0–§1 | 文档和默认节奏已守护,真实行为与 DOM 质量仍需人工验收 |
|
||||
| adapter 四档契约测试覆盖 timeout/unauthorized 等 | development/ADAPTER.md §contract tests | 需业务实现后人工补测 |
|
||||
| `SKILL.md` / `constants.SKILL_SLUG` 一致性 | development/DEVELOPMENT.md §16 | 已有 `tests/test_skill_metadata.py` |
|
||||
| platform_kit_min_version >= 1.2.2 | development/RUNTIME.md | 已有 `tests/test_platform_import.py` |
|
||||
|
||||
@@ -1,20 +1,45 @@
|
||||
# 开发资料入口
|
||||
|
||||
本目录面向**人类开发者**与 **AI 编程代理**。开始定制 skill 前,建议按以下顺序阅读:
|
||||
本目录面向**人类开发者**与 **AI 编程代理**。技术人员与编程 AI **以本目录为主读路径**;`references/` 是 CLI / Action / Schema 契约细节,根目录市场四 Tab 面向最终用户。
|
||||
|
||||
1. [`REQUIREMENTS.md`](REQUIREMENTS.md) — 需求文档模板与验收标准
|
||||
2. [`NAMING.md`](NAMING.md) — slug / 仓库名命名规范(复制模板前必读)
|
||||
3. [`DEVELOPMENT.md`](DEVELOPMENT.md) — 完整开发步骤与目录规范
|
||||
4. [`TESTING.md`](TESTING.md) — 测试分层、隔离数据根与档位开关
|
||||
5. [`LOGGING.md`](LOGGING.md) — 日志分层、必打节点、敏感信息红线(**涉及长任务、RPA、外部系统时必读**)
|
||||
6. [`ADAPTER.md`](ADAPTER.md) — 涉及外部系统对接时
|
||||
7. [`RPA.md`](RPA.md) — 涉及浏览器 / 桌面 / 手机自动化时
|
||||
8. [`CONFIG.md`](CONFIG.md) — `.env` 规范与 bootstrap 机制
|
||||
9. [`DATA_PATHS.md`](DATA_PATHS.md) — 下载/导入/导出等本地文件路径标准(涉及文件读写时必读)
|
||||
10. [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md) — Skill Action sync/async、任务中心、Agent 禁令、队列 pick(涉及 RPA / 长任务 / 数据管理按钮 / Cron / Agent 时必读)
|
||||
11. [`RUNTIME.md`](RUNTIME.md) — 共享 runtime、数据路径、发布打包与编码约定
|
||||
## 新技能全流程(先看这张表)
|
||||
|
||||
脚手架与 Git 防串库:[`../tools/README.md`](../tools/README.md)(`scaffold_skill.ps1`)。
|
||||
按顺序做;细节与约束见 [`DEVELOPMENT.md`](DEVELOPMENT.md) 对应章节(**不替代**下文深度规范,只作导航)。
|
||||
|
||||
| 步骤 | 做什么 | 详见 |
|
||||
|------|--------|------|
|
||||
| 1 | **拿到仓库**:① 项目经理在 [自建 Gitea](https://git.jc2009.com/)(`git.jc2009.com`,非 Gitea 官网)开仓并授权后 `git clone`;或 ② 本地已有 `skill-template` 时用 scaffold / 复制出新目录(遵守 Git 红线) | [`DEVELOPMENT.md`](DEVELOPMENT.md) §1、§4 |
|
||||
| 2 | **先读 AI + RPA 总纲**;浏览器技能再读账号 / Profile / 租约标准,理解真实多账号场景 | [`AI_RPA.md`](AI_RPA.md)、[`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md) |
|
||||
| 3 | 按 [`NAMING.md`](NAMING.md) 确认 slug;**先填** [`REQUIREMENTS.md`](REQUIREMENTS.md),再写业务代码 | [`REQUIREMENTS.md`](REQUIREMENTS.md)、[`NAMING.md`](NAMING.md) |
|
||||
| 4 | 替换标识与占位,按四象限 / examples 实现 `scripts/service/` | [`DEVELOPMENT.md`](DEVELOPMENT.md) §4~§14 |
|
||||
| 5 | **配置 `developer_ids`(开发自测必做)**:开发期技能在匠厂默认不公开;不加则开发者自己也装测不了。从宿主设置复制用户 ID 写入 `SKILL.md` | [`DEVELOPMENT.md`](DEVELOPMENT.md) §6「关于 developer_ids」 |
|
||||
| 6 | 本地单测(mock)通过后,按业务默认 **`real_rpa`** 真实跑通;再 `release.ps1`;看 Gitea CI。**mock 通 ≠ 完成** | [`ADAPTER.md`](ADAPTER.md);[`DEVELOPMENT.md`](DEVELOPMENT.md) §15 |
|
||||
| 7 | 匠厂安装后,按已声明入口自测,并看**用户体验**(进度、失败提示、有头 RPA 等) | [`DEVELOPMENT.md`](DEVELOPMENT.md) §15;[`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md) |
|
||||
| 8 | **交活验收(第三方)**:对照 [`CHECKLIST.md`](CHECKLIST.md) 验收;报告写入仓库根目录 [`acceptance-reports/`](../acceptance-reports/) | [`CHECKLIST.md`](CHECKLIST.md) |
|
||||
|
||||
脚手架与 Git 防串库:[`../tools/README.md`](../tools/README.md)(`scaffold_skill.ps1`)。业务仓也可直接从自建 Gitea clone,见 [`DEVELOPMENT.md`](DEVELOPMENT.md) §4 来源 A。
|
||||
|
||||
**公开公共仓(可 clone 学习,可扩展登记):** [`SHARED_REPOS.md`](SHARED_REPOS.md) — 当前含 skill-template、account-manager、jiangchang-platform-kit。
|
||||
|
||||
## 深度规范阅读顺序
|
||||
|
||||
开始定制 skill 前,建议按以下顺序阅读:
|
||||
|
||||
1. [`AI_RPA.md`](AI_RPA.md) — **AI Agent + RPA 总纲**:职责边界、合规红线、风控、操作节奏与完整闭环
|
||||
2. [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md) — **浏览器账号标准**:一账号一 Profile、租约、多账号、数据查看、验收与排障
|
||||
3. [`SHARED_REPOS.md`](SHARED_REPOS.md) — **公共仓库目录**(模板 / 账号管理 / platform-kit 等,可扩展)
|
||||
4. [`REQUIREMENTS.md`](REQUIREMENTS.md) — 需求文档模板与验收标准
|
||||
5. [`NAMING.md`](NAMING.md) — slug / 仓库名命名规范(复制模板前必读)
|
||||
6. [`DEVELOPMENT.md`](DEVELOPMENT.md) — 完整开发步骤与目录规范
|
||||
7. [`TESTING.md`](TESTING.md) — 测试分层、隔离数据根与档位开关
|
||||
8. [`LOGGING.md`](LOGGING.md) — 日志分层、必打节点、敏感信息红线(**涉及长任务、RPA、外部系统时必读**)
|
||||
9. [`ADAPTER.md`](ADAPTER.md) — 涉及外部系统对接时
|
||||
10. [`RPA.md`](RPA.md) — 涉及浏览器 / 桌面 / 手机自动化时
|
||||
11. [`CONFIG.md`](CONFIG.md) — `.env` 规范与 bootstrap 机制
|
||||
12. [`DATA_PATHS.md`](DATA_PATHS.md) — 下载/导入/导出等本地文件路径标准(涉及文件读写时必读)
|
||||
13. [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md) — Skill Action sync/async、任务中心、Agent 禁令、队列 pick(涉及 RPA / 长任务 / 数据管理按钮 / Cron / Agent 时必读)
|
||||
14. [`RUNTIME.md`](RUNTIME.md) — 共享 runtime、数据路径、发布打包与编码约定
|
||||
14. [`CHECKLIST.md`](CHECKLIST.md) — **交活验收清单(第三方视角)**;报告输出到 [`../acceptance-reports/`](../acceptance-reports/)
|
||||
|
||||
Agent 调用契约见 [`../references/CLI.md`](../references/CLI.md)、[`../references/SCHEMA.md`](../references/SCHEMA.md)、[`../references/ACTIONS.md`](../references/ACTIONS.md)。
|
||||
用户市场四 Tab 见根目录 [`README.md`](../README.md) / [`TUTORIAL.md`](../TUTORIAL.md) / [`DEMO.md`](../DEMO.md) / [`CHANGELOG.md`](../CHANGELOG.md),不要写进本目录。
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- 一个 skill 对应一份主需求文档
|
||||
- 需求先写清楚,再进入开发
|
||||
- 涉及外部系统时先读 `AI_RPA.md`,明确 Agent / RPA 边界、授权、风险与人工介入
|
||||
- 文档描述“要做什么”和“做到什么程度”,不要在这里堆实现细节
|
||||
- 实现方式、代码结构、发布流程,分别放到 `DEVELOPMENT.md`、`references/`、`development/` 下的技术规范
|
||||
|
||||
@@ -30,6 +31,39 @@
|
||||
- **登记 ≠ 登录**:ensure/add-web 只建账号与 Profile;站点登录由本技能在有头浏览器中完成
|
||||
- **反模式**:把「需要登录态才能采到数据」的需求写成 `optional`,再省略 `ensure_logged_in`
|
||||
|
||||
## 0.1 AI + RPA 协作与授权边界(涉及外部系统时必填)
|
||||
|
||||
### Agent 设计
|
||||
|
||||
- 用户会怎样描述本任务:
|
||||
- 支持哪些用户意图;分别映射到哪个 Action:
|
||||
- 每种意图必须提取哪些参数:
|
||||
- 缺少哪些参数时必须追问:
|
||||
- 哪些动作必须先展示对象、数量和影响,再取得明确确认:
|
||||
- Agent 最终如何解释成功、部分成功、失败、等待人工和停止:
|
||||
|
||||
### 授权与平台边界
|
||||
|
||||
- 目标浏览器 / 桌面软件 / 手机 App:
|
||||
- 账号、设备、数据和业务权限由谁授权:
|
||||
- 是否有获得授权的官方 API;若不用,原因是什么:
|
||||
- 目标平台规则、合同或客户约定中的自动化边界:
|
||||
- 明确禁止的实现路线:破解 / 反编译 / 逆向 / 私有协议 / 抓包拼隐藏接口 / Hook 或注入 / 验证码破解 / 设备指纹伪造 / 自动换号:
|
||||
- 正常界面无法闭环时的上报与停止条件:
|
||||
|
||||
### RPA 风控设计
|
||||
|
||||
- 执行端:浏览器 / 桌面 / Android / iOS:
|
||||
- 会话、Profile、窗口或设备如何准备与互斥:
|
||||
- 单批上限、单位时间操作量和最长运行时间:
|
||||
- 普通动作节奏(真实环境默认 `1~3` 秒):
|
||||
- 页面 / 控件真实状态如何判断;禁止用固定睡眠代替的节点:
|
||||
- 哪些错误允许有限重试;退避策略:
|
||||
- 哪些信号必须立即停止(频控、封禁、权限不足、结构变化等):
|
||||
- 哪些步骤进入人工处理(验证码、人脸、指纹、短信、U盾等):
|
||||
- 如何防止重复提交并支持必要的断点续跑:
|
||||
- 需要保存哪些日志、截图、视频和业务结果:
|
||||
|
||||
## 1. 文档目标
|
||||
|
||||
说明这份需求文档的作用,以及它解决什么问题。
|
||||
@@ -109,6 +143,8 @@
|
||||
- 输出格式应尽量机读友好;错误前缀统一为 `ERROR:`
|
||||
- Windows 环境下需保证 UTF-8 输出兼容
|
||||
- 必须具备基本日志能力;敏感字段脱敏
|
||||
- 只做授权范围内的正常界面自动化;禁止破解、逆向、未授权私有协议、安全验证绕过、进程注入、凭据窃取、自动换号和无限重试
|
||||
- RPA 的页面加载与成功判断必须由真实状态驱动;正常业务动作之间默认随机等待 `1~3` 秒
|
||||
- RPA 类 skill:Playwright 由宿主共享 runtime 提供;技能侧不要 `playwright install`;URL 不要放进 `launch_persistent_context` 的 `args`
|
||||
- 网页 RPA:启动前必须经 ensure-web 拿到 `profile_dir`;生产有头;站点登录策略见 §0(required / optional / not_needed);`required` 时每轮须登录门禁
|
||||
|
||||
@@ -164,21 +200,26 @@
|
||||
|
||||
- 代码结构符合模板规范;`SKILL.md` slug 与 `constants.SKILL_SLUG` 一致
|
||||
- `health`、`version`、`init-db` 命令执行正常
|
||||
- 主命令(如 `run`)在 mock / simulator 档位可重复验证
|
||||
- `python tests/run_tests.py -v` 必跑测试全部通过
|
||||
- `python tests/run_tests.py -v` 必跑测试全部通过(mock / 离线门禁;**mock 通 ≠ 完成**)
|
||||
- 主命令在业务默认档(模板为 `real_rpa`)真实跑通;不得只 mock 通就交活
|
||||
- `task_logs` 写入和查询符合 `references/SCHEMA.md`(含 `created_at` / `updated_at` Unix 秒级规范)
|
||||
- `init_db()` 已写入 `_jiangchang_tables` / `_jiangchang_columns`;用户可见表/字段具备中文 `display_name`
|
||||
- 字段展示顺序与 `PRAGMA table_info(task_logs)` 的 cid 一致;不依赖 `display_order`
|
||||
- `tests/test_display_metadata.py` 通过
|
||||
- 真实联调(如有)放在 `tests/integration/`,且默认套件不包含真实外联
|
||||
- 发布后 Gitea 工作流成功;匠厂技能市场可见最新版本;安装后可在“新建任务”中调用
|
||||
- 真实联调样例仍放 `tests/integration/`,默认套件不包含真实外联(与业务 `.env` 默认 `real_rpa` 不冲突)
|
||||
- `SKILL.md` 的 `developer_ids` 已配置为开发者本人匠厂用户 ID(开发期不公开时,否则本人无法在市场安装自测;取 ID 步骤见 `development/DEVELOPMENT.md` §6)
|
||||
- 发布后 Gitea 工作流成功;匠厂技能市场对开发者账号可见最新版本并可安装
|
||||
- 安装后按本技能 `actions.json` 声明完成宿主多入口验收(见 `development/DEVELOPMENT.md` §15);**须看用户体验**(进度可见、失败可读、RPA 有头、文案与真实行为一致等),后台通了不算完
|
||||
- 已人工核验授权与技术路线:无破解、逆向、私有协议、验证码绕过、Hook / 注入、自动换号或其他规避平台限制的实现
|
||||
- RPA 真实主路径已验证状态等待、`1~3` 秒正常操作节奏、有限重试、账号 / 设备串行、人工介入、结果核验和结束存证
|
||||
|
||||
## 10. 开发注意事项
|
||||
|
||||
- 只修改当前 skill 仓库,不要改动无关兄弟项目
|
||||
- 正常界面或获得授权的官方 API 无法完成闭环时,停止并上报;不得擅自改走破解、逆向或绕过安全机制的路线
|
||||
- 先判断四象限类型(`real_browser_rpa` / `real_api` / `simulator_browser_rpa` / `simulator_api`),再读对应 `examples/*/README.md`
|
||||
- `cli` 只做参数解析;核心逻辑在 `service`;兄弟 skill 调用集中封装(见 `ADAPTER.md`)
|
||||
- 发布前完成本地验证、工作流验证和正式环境安装验证
|
||||
- 发布前完成本地验证、工作流验证和正式环境安装验证(含 `developer_ids` 与宿主多入口)
|
||||
|
||||
## 11. 变更记录
|
||||
|
||||
@@ -190,10 +231,11 @@
|
||||
|
||||
## 建议使用方式
|
||||
|
||||
1. 先写 `REQUIREMENTS.md`
|
||||
2. 再按 `DEVELOPMENT.md` 进入开发
|
||||
3. 开发过程中补充 `references/CLI.md`、`references/SCHEMA.md` 及 `development/` 技术规范
|
||||
4. 发布前对照第 9 节验收标准逐项检查
|
||||
1. 拿到业务技能仓库(Gitea clone,或本地从 `skill-template` scaffold / 复制;见 `DEVELOPMENT.md` §4)
|
||||
2. 先写 / 填全本文件 `REQUIREMENTS.md`
|
||||
3. 再按 `DEVELOPMENT.md` 进入开发(含尽早配置 `developer_ids`)
|
||||
4. 开发过程中补充 `references/CLI.md`、`references/SCHEMA.md` 及 `development/` 技术规范
|
||||
5. 发布前对照第 9 节验收标准逐项检查;release 后按 `DEVELOPMENT.md` §15 做宿主多入口验收
|
||||
|
||||
## 最小模板示例
|
||||
|
||||
@@ -254,8 +296,8 @@
|
||||
|
||||
- 命令可运行;tests/run_tests.py -v 通过
|
||||
- task_logs 符合 SCHEMA
|
||||
- 正式环境安装验证通过
|
||||
|
||||
- `developer_ids` 已配置为「你的用户ID」(匠厂设置中复制的正整数)
|
||||
- 正式环境安装验证通过;已声明的宿主入口(新建任务 / 数据管理 / 定时任务 / 任务中心 / 技能详情等)按 DEVELOPMENT §15 自测
|
||||
## 10. 开发注意事项
|
||||
|
||||
- 不修改无关项目;不引入旧模板结构
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
> 本文是团队 RPA 开发的**统一标准**。任何需要"自动操作软件界面"的 skill,都应先读这份文档,按这里的选型和范式落地,不要每个项目重新踩坑。
|
||||
|
||||
先读 [`AI_RPA.md`](AI_RPA.md):Agent 负责理解、确认与编排,RPA 只负责授权范围内的确定性界面执行。RPA 是代替人完成正常业务操作的工具,**不是**破解、逆向、私有协议调用或对抗第三方安全机制的手段。
|
||||
|
||||
网页账号 / Profile 依赖公开兄弟技能 **account-manager**;浏览器 RPA 原语来自 **jiangchang-platform-kit**。账号设计、查看、租约、多账号和验收标准先读 [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md);二者 Gitea 地址、clone 方式与红线见 [`SHARED_REPOS.md`](SHARED_REPOS.md)。
|
||||
|
||||
我们开发的各类 skill,本质上都是在替人操作三类界面:**浏览器、桌面软件、手机软件**。三类的底层技术不同,但**工程范式相同**:可控会话(Profile)、有头运行、拟人操作、失败存证、人工兜底。
|
||||
|
||||
---
|
||||
@@ -14,8 +18,8 @@
|
||||
|------|------|
|
||||
| **可控会话 / Profile** | 网页版见下方 **§0.2**(必须走 account-manager 的 `profile_dir`);桌面/手机用对应持久会话,不硬编码密码 |
|
||||
| **有头运行** | 生产 RPA **必须有头**(见 §0.2);禁止把无头当交付/联调常态 |
|
||||
| **拟人操作** | 真实事件(isTrusted=true),逐字输入、随机延迟、贝塞尔鼠标轨迹;严禁 JS 直接设值/JS 点击/JS 跳转 |
|
||||
| **步骤间随机等待** | 每两步操作之间 `random_delay(min,max)`,区间由 `.env` 配置(默认 1~5s) |
|
||||
| **正常人工节奏** | 真实点击、逐字输入、自然鼠标轨迹、分段滚动;严禁 JS 直接设值/JS 点击/JS 跳转绕过页面流程 |
|
||||
| **步骤间随机等待** | 每两个正常业务动作之间 `random_delay(min,max)`,真实环境默认 **1~3 秒**;状态等待另算,不得用固定睡眠代替 |
|
||||
| **人工兜底(HITL)** | 滑块 / 短信验证码 / 人脸 / U盾 / 动态口令 → **停下来轮询等人工**,超时报 `ERROR:XXX_NEED_HUMAN`,绝不自动硬闯 |
|
||||
| **失败存证** | 失败必截图,合规场景全程录屏,统一存 `{数据目录}/rpa-artifacts/{batch_id}/{tag}_{ts}.png` |
|
||||
| **选择器纪律** | 语义选择器优先(id/name/text/aria);**F12 确认后再写,严禁凭记忆猜 DOM** |
|
||||
@@ -25,6 +29,14 @@
|
||||
|
||||
> 三端各自实现一个会话抽象 `RpaSession`(launch / login / act / screenshot / close),上层 skill 不感知是浏览器还是手机。
|
||||
|
||||
### 0.0 合规红线
|
||||
|
||||
- 只使用用户或企业合法拥有、明确授权的账号、数据、设备和业务权限。
|
||||
- 优先正常界面或获得授权的官方 API;只有破解、逆向或绕过安全机制才能实现时,停止并上报。
|
||||
- 禁止反编译、私有协议、抓包拼装隐藏接口、Hook / 注入、客户端篡改、验证码破解、设备指纹伪造、撞库、养号、自动换号或代理池规避限制。
|
||||
- 验证码、人脸、指纹、短信、U盾、动态口令必须交给人工;权限不足、账号风险、频控或封禁必须安全停止。
|
||||
- 合规与完整职责边界以 [`AI_RPA.md`](AI_RPA.md) 为准。
|
||||
|
||||
### 0.1 步骤控制与进度(SRCP,platform-kit >= 1.2.0)
|
||||
|
||||
- **禁止** `scripts/util/progress.py` 或 `print({"type":"progress",...})`;进度只走 `emit` / Run Journal。
|
||||
@@ -58,6 +70,9 @@ async def open_login(page):
|
||||
| **登记 ≠ 登录** | `ensure-web` / `add-web` 只保证有账号记录与 Profile 目录;**不等于**目标站已登录。站点登录仍由本技能 `ensure_logged_in`(或等价)在有头浏览器中完成。 |
|
||||
| **登录 ≠ Profile** | 「可不登录」**绝不等于**「可以不接 account-manager」。无论目标站要不要登录,网页操作都依赖账号管理下发的 **Profile**。 |
|
||||
| **登录策略 = required 时** | 每轮业务主路径在 `goto` 后必须过登录门禁 + 验证码/人工等待(`HUMAN_WAIT_TIMEOUT`);**禁止**把会话依赖型采集默认抄成 `optional`。 |
|
||||
| **账号必须可见可查** | 创建后须能在宿主数据管理、`account list/get` 和实际 `profile_dir` 中核验;不得把“命令没报错”当作创建与登录验收。 |
|
||||
| **一账号一 Profile** | 不同账号必须使用不同且稳定的 `profile_dir`;不得共用、临时创建或指向本机日常浏览器目录。 |
|
||||
| **租约贯穿任务** | 所有账号选择路径(包括指定数字 ID)都必须取得非空 `lease_token`;成功、失败、取消和异常均在 `finally` 释放。 |
|
||||
|
||||
**反模式(禁止):**
|
||||
|
||||
@@ -70,6 +85,42 @@ async def open_login(page):
|
||||
|
||||
**参考实现:** `examples/real_browser_rpa/`、`examples/simulator_browser_rpa/`(`pick_web_account` → ensure-web → RPA → `finally release_lease`)。
|
||||
|
||||
账号完整生命周期、数据目录、数据管理查看方式、矩阵场景和排障证据见 [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md)。不得只测试单账号一两次就宣称支持批量或多账号。
|
||||
|
||||
---
|
||||
|
||||
### 0.3 工业级风控、等待与收尾
|
||||
|
||||
**状态等待优先:**页面加载、控件可用、弹窗消失、保存成功、下载完成和业务结果出现,必须等待真实条件并设置超时;禁止用固定 `sleep(3)` 假设成功。
|
||||
|
||||
**操作节奏:**
|
||||
|
||||
- 两个正常业务动作之间默认随机等待 `1~3` 秒。
|
||||
- 输入采用逐字符随机延迟;鼠标使用自然轨迹;滚动采用分段操作。
|
||||
- 同一账号、Profile、桌面会话或手机设备默认串行。
|
||||
- 单批数量、单位时间操作量和运行时长必须有上限。
|
||||
- 只对明确可重试错误做有限重试;普通网络错误可递增退避,风险提示和权限错误不得自动重试。
|
||||
|
||||
**前后缓冲:**
|
||||
|
||||
- 启动浏览器、桌面软件或手机会话后,先等待目标页面 / 窗口 / 控件真实就绪,再额外缓冲约 `1.5~2.5` 秒。
|
||||
- 页面导航后,关键控件就绪再缓冲约 `1~2` 秒。
|
||||
- 流程结束时,先确认业务结果、写日志并完成存证,再缓冲约 `2~3` 秒关闭会话。
|
||||
- 付款、审批、发布等重要动作确认结果并截图后,可缓冲约 `3~5` 秒。
|
||||
|
||||
缓冲用于稳定界面和改善用户体验,**不能作为成功判断**。RPA 主路径使用 `interruptible_sleep` 等可控制等待,确保暂停与停止能够生效。
|
||||
|
||||
**风险信号:**
|
||||
|
||||
| 信号 | 标准处理 |
|
||||
|------|----------|
|
||||
| 验证码 / 人脸 / 指纹 / U盾 | 进入人工处理 |
|
||||
| 频率限制 | 停止当前批次或长时间退避 |
|
||||
| 账号风险 / 封禁 / 安全警告 | 立即停止,禁止换号继续 |
|
||||
| 权限不足 | 明确失败,不尝试绕过 |
|
||||
| 页面 / 控件结构变化 | 截图并停止,重新人工验证定位 |
|
||||
| 提交结果不明确 | 查询业务状态,确认前禁止再次提交 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 浏览器(标准已成熟)
|
||||
@@ -85,12 +136,12 @@ async def open_login(page):
|
||||
| CDP | **仅作诊断 / 桌面宿主类场景**;**不要**作为强风控站点的默认生产路径 |
|
||||
| 行为 | 模拟真实用户:真实点击、键盘、鼠标、地址栏输入;**不要**拼接搜索结果 URL、DOM 注入、`el.value=`、JS 跳转 |
|
||||
| 模式 | **有头** `OPENCLAW_BROWSER_HEADLESS=0`(生产强制,见 §0.2) |
|
||||
| 反检测 | stealth 默认开 `OPENCLAW_PLAYWRIGHT_STEALTH=1`(见 1.1) |
|
||||
| 浏览器兼容配置 | `OPENCLAW_PLAYWRIGHT_STEALTH` 仅用于经评估的兼容配置;不得用于伪造身份或绕过平台限制 |
|
||||
|
||||
### 1.1 Playwright 启动标准
|
||||
|
||||
1. **有头**:`OPENCLAW_BROWSER_HEADLESS=0`(`.env.example` 默认;网页 RPA 生产路径禁止改成无头常态,见 §0.2)。
|
||||
2. **stealth 默认开**:`OPENCLAW_PLAYWRIGHT_STEALTH=1`;通过 `add_init_script` 注入指纹淡化脚本。
|
||||
2. **stealth 须受控**:`OPENCLAW_PLAYWRIGHT_STEALTH` 仅在目标平台授权、规则与项目评估允许时启用;不得把它理解为规避风控或伪造设备身份。
|
||||
3. **不要在技能里自行安装 playwright**;由宿主共享 runtime 提供。
|
||||
4. **不要默认传 `--no-sandbox`**(除非特定容器环境且已评估风险)。
|
||||
5. **不要默认传 `--disable-blink-features=AutomationControlled`**;platform-kit stealth 已覆盖,额外 flag 可能适得其反。
|
||||
@@ -98,7 +149,7 @@ async def open_login(page):
|
||||
7. **强风控平台**:优先真实点击、键盘、鼠标、地址栏、account-manager 持久 profile;**不要**直接拼接搜索结果 URL 或 DOM 注入。
|
||||
8. **先 Profile 再开浏览器**:`user_data_dir` 必须来自 ensure/pick 到的 `profile_dir`;未拿到则不得进入本节后续步骤。
|
||||
|
||||
指纹淡化(stealth)典型项:`navigator.webdriver=undefined`、`chrome.runtime`、`permissions.query`、`plugins`、`languages` 等。共享实现见 `jiangchang_skill_core.rpa`(platform-kit **>= 1.2.0**)。
|
||||
浏览器兼容配置由共享 `jiangchang_skill_core.rpa` 提供(platform-kit **>= 1.2.0**)。业务技能不得自行扩展指纹伪造、代理轮换或规避安全检测的代码;遇到明确风控信号时转人工或停止。
|
||||
|
||||
**拟人操作**(必做):
|
||||
|
||||
@@ -106,7 +157,7 @@ async def open_login(page):
|
||||
- 鼠标:贝塞尔曲线轨迹 + 微抖动;进场随机晃动。
|
||||
- 导航:用真实点击触发,**不要 `window.location.href=` / JS 点击跳转**。
|
||||
- 翻页:真实点击翻页控件,注意排除禁用态。
|
||||
- 延迟:每步之间 `random_delay`(`.env` 中 `STEP_DELAY_MIN/MAX`)。
|
||||
- 延迟:两个正常业务动作之间 `random_delay`(`.env` 中 `STEP_DELAY_MIN/MAX`,默认 1~3 秒);页面状态仍须显式等待。
|
||||
|
||||
### 1.2 页面启动标准
|
||||
|
||||
@@ -241,6 +292,7 @@ from jiangchang_skill_core.rpa.stealth import stealth_enabled, STEALTH_INIT_SCRI
|
||||
- **DPI/缩放**:图像识别方案必须固定显示缩放比例;UIA 方案不受影响(优先用 UIA 即是为此)。
|
||||
- **存证**:同样要失败截图(截目标窗口/全屏),存到 `rpa-artifacts`。
|
||||
- **人工兜底**:U盾插拔、动态口令、人脸 → 停下等人工,超时 `ERROR:XXX_NEED_HUMAN`。
|
||||
- **禁止侵入**:不注入或 Hook 第三方进程,不修改客户端文件,不反编译或调用未授权私有协议。
|
||||
|
||||
```bash
|
||||
pip install pywinauto # UIA 自动化
|
||||
@@ -269,6 +321,7 @@ pip install pywinauto # UIA 自动化
|
||||
- **登录态**:靠 App 自身保持登录,必要时引导人工首登一次。
|
||||
- **人工兜底**:短信验证码、人脸、指纹 → scrcpy 投屏让人工完成,程序轮询等待。
|
||||
- **存证**:失败时 `adb screencap` / Appium 截图存 `rpa-artifacts`。
|
||||
- **设备边界**:使用正常授权设备与官方 App;不 Root / 越狱,不 Hook,不修改 APK / IPA,不伪造设备身份或绕过签名、绑定与安全校验。
|
||||
|
||||
```bash
|
||||
# Android(首选)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
技能根目录 `requirements.txt` **只声明技能特有依赖**;**不要**重复声明 `jiangchang-platform-kit` 或 `playwright`。`SKILL.md` 的 `platform_kit_min_version` 是运行契约,**不是** pip 依赖声明。
|
||||
|
||||
kit **源码仓**(可 clone 学习,勿 vendor)与其它公开公共仓登记见 [`SHARED_REPOS.md`](SHARED_REPOS.md)。
|
||||
|
||||
### 何时上调 `platform_kit_min_version`
|
||||
|
||||
| 情况 | 做法 |
|
||||
|
||||
131
development/SHARED_REPOS.md
Normal file
131
development/SHARED_REPOS.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 公共仓库目录(可扩展)
|
||||
|
||||
本页是技能开发用到的**公开公共仓唯一登记处**,面向技术人员与 AI 编程代理。
|
||||
|
||||
- 仓均在公司**自建 Gitea**:[https://git.jc2009.com/](https://git.jc2009.com/)(基于开源 Gitea 自建,**不是** Gitea 官网)。
|
||||
- 技术人员一般**可直接访问、随时 `git clone` 到本地**,用于研究、学习、对照与跟进更新。
|
||||
- **当前登记 3 项**;以后新增公开公共仓时,**只在本页按类型加行**,不必改散落长文。
|
||||
|
||||
深度规范仍在 [`RPA.md`](RPA.md)、[`RUNTIME.md`](RUNTIME.md)、[`ADAPTER.md`](ADAPTER.md)、[`DEVELOPMENT.md`](DEVELOPMENT.md);本页只做**指路 + 边界**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 类型(可扩展)
|
||||
|
||||
| 类型 id | 含义 | 典型消费方式 |
|
||||
|---------|------|--------------|
|
||||
| `skill-template` | 业务技能作者脚手架与规范源 | clone 学习;`scaffold` / 复制出新业务仓;**不要**在模板仓写业务 |
|
||||
| `sibling-skill` | 与业务技能并列安装的公开基础设施技能 | clone 学契约;运行时经 **CLI / 封装客户端** 调用 |
|
||||
| `python-sdk` | 共享 Python 库(源码仓 + 宿主共享 venv 安装) | clone 读实现 / 贡献;运行时 **`import`**,**禁止 vendor** 进技能仓 |
|
||||
| `asset-bundle` | (预留)共享媒体/资源包等 | 按该仓说明拉取或由 kit/宿主解析 |
|
||||
| `tooling` | (预留)发布脚本、脚手架、CI 辅助等 | clone 使用或引用其 workflow |
|
||||
|
||||
新增类型时:先在本表加一行类型说明,再在 §2 登记表增加实例。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前登记表
|
||||
|
||||
| 短名 | 类型 | Gitea(唯一入口) | 一句话职责 |
|
||||
|------|------|-------------------|------------|
|
||||
| **skill-template** | `skill-template` | [https://git.jc2009.com/client-commons/skill-template](https://git.jc2009.com/client-commons/skill-template) | 新业务技能的开发模板、目录规范与发布约定 |
|
||||
| **account-manager** | `sibling-skill` | [https://git.jc2009.com/client-commons/account-manager](https://git.jc2009.com/client-commons/account-manager) | 账号 / 凭据 / 浏览器 Profile / 租约的统一入口 |
|
||||
| **jiangchang-platform-kit** | `python-sdk` | [https://git.jc2009.com/client-jiangchang/jiangchang-platform-kit](https://git.jc2009.com/client-jiangchang/jiangchang-platform-kit) | 共享 SDK(`jiangchang_skill_core` 等);技能发布可复用 CI 也在此仓 |
|
||||
|
||||
本地常见路径(仅作对照,以你机器为准):
|
||||
|
||||
- `D:\OpenClaw\client-commons\skill-template`
|
||||
- `D:\OpenClaw\client-commons\account-manager`
|
||||
- `D:\OpenClaw\client-jiangchang\jiangchang-platform-kit`
|
||||
|
||||
### 2.1 skill-template
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| **Clone 为了什么** | 读 `development/`、跟模板版本、用 `tools/scaffold_skill` 开新技能 |
|
||||
| **怎么用** | 来源 B:scaffold;或学完后在业务仓开发。规范以本仓为准 |
|
||||
| **红线** | 不要在模板仓写业务;业务仓 `origin` 不得指向本仓;不得保留 `.openclaw-skill-template` |
|
||||
| **深入** | 本目录 [`README.md`](README.md)、[`DEVELOPMENT.md`](DEVELOPMENT.md) |
|
||||
|
||||
```powershell
|
||||
git clone https://git.jc2009.com/client-commons/skill-template.git
|
||||
```
|
||||
|
||||
### 2.2 account-manager
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| **Clone 为了什么** | 读 CLI / `references/INTEGRATION.md`、平台表、租约与错误码;排 `NO_ACCOUNT` / `LEASE_CONFLICT` 等 |
|
||||
| **为什么必须用** | 支撑真实业务的一账号一 Profile、登录状态持久化、多账号矩阵和并发互斥;禁止浏览器“裸奔” |
|
||||
| **怎么用(业务技能)** | 经 `scripts/service/account_client.py`(或等价)**subprocess 调 CLI**;网页 RPA 须先拿到 `profile_dir + lease_token`(`ensure-web` / `pick-web`) |
|
||||
| **红线** | **禁止**业务技能 `import` 其内部 `rpa_helpers` / 散落存密码(见 `POLICY-RPA-001`);对方文档若写「同进程 rpa_helpers」,**以本模板约束为准** |
|
||||
| **创建后查看** | 宿主数据管理;CLI `account list/get`、`lease list`;本地用户数据目录中的 `account-manager.db` 与 `profiles/` |
|
||||
| **深入** | 对方仓 `references/INTEGRATION.md`、`CLI.md`、`SCHEMA.md`;本模板 [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md)、[`RPA.md`](RPA.md) §0.2、[`ADAPTER.md`](ADAPTER.md) |
|
||||
|
||||
```powershell
|
||||
git clone https://git.jc2009.com/client-commons/account-manager.git
|
||||
```
|
||||
|
||||
### 2.3 jiangchang-platform-kit
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| **Clone 为了什么** | 读 `jiangchang_skill_core`(config / logging / activity·SRCP / rpa / sibling_bridge 等)、跟版本、贡献共享 API |
|
||||
| **怎么用(业务技能)** | 运行时由**宿主共享 venv** 提供;技能内 `import jiangchang_skill_core...`。`SKILL.md` 的 `platform_kit_min_version` 是兼容声明 |
|
||||
| **红线** | **不要**把 `jiangchang-platform-kit` / `playwright` 写入技能 `requirements.txt`;**不要**在技能仓 vendor `jiangchang_skill_core/`(`POLICY-RUNTIME-002`) |
|
||||
| **深入** | 对方仓 `README.md`;本模板 [`RUNTIME.md`](RUNTIME.md)、[`LOGGING.md`](LOGGING.md) |
|
||||
|
||||
```powershell
|
||||
git clone https://git.jc2009.com/client-jiangchang/jiangchang-platform-kit.git
|
||||
```
|
||||
|
||||
私有 PyPI 索引(维护/排障用,日常技能开发通常不必手装):
|
||||
`https://git.jc2009.com/api/packages/client-jiangchang/pypi/simple/`
|
||||
|
||||
---
|
||||
|
||||
## 3. 三者关系(别当成三个无关收藏夹)
|
||||
|
||||
```text
|
||||
规范 / 脚手架 ──────────► skill-template(本仓)
|
||||
│
|
||||
业务技能 scripts/ ──import──► 宿主共享 venv 中的 platform-kit
|
||||
│
|
||||
└──CLI──► account-manager(兄弟技能,要先拿到 profile_dir 等)
|
||||
```
|
||||
|
||||
- **写规范、开新仓** → 打开 **skill-template**
|
||||
- **少写公共 Python、进度/RPA 原语** → 用 **kit**(运行靠宿主;源码可 clone)
|
||||
- **账号与浏览器 Profile** → 用 **account-manager**(运行靠 CLI;源码可 clone)
|
||||
|
||||
---
|
||||
|
||||
## 4. 何时该打开哪个仓
|
||||
|
||||
| 场景 | 优先打开 |
|
||||
|------|----------|
|
||||
| 不知道新技能目录/发布/developer_ids 怎么做 | **skill-template** `development/` |
|
||||
| 定 `platform_key`、ensure-web、租约、凭证存储 | **account-manager** + 本模板 RPA/ADAPTER |
|
||||
| 账号创建后不知道去哪看、登录态不保持、多账号互相污染 | 本模板 **ACCOUNT_MANAGER.md** + account-manager 数据管理 / CLI |
|
||||
| 看 `emit` / `finish` / `@rpa_step` / `launch_persistent_browser` 实现 | **jiangchang-platform-kit** |
|
||||
| 技能发布 CI / reusable workflow 从哪来 | **jiangchang-platform-kit** `.github/workflows/` |
|
||||
| 「公共东西到底在哪」 | **本页登记表** |
|
||||
|
||||
---
|
||||
|
||||
## 5. 以后如何新增一条公共仓(维护约定)
|
||||
|
||||
1. 确认公开可访问,URL 使用 `https://git.jc2009.com/...`(勿写 Gitea 官网)。
|
||||
2. 选定或新增 §1 **类型**。
|
||||
3. 在 §2 **登记表**加一行;必要时增加 `§2.x` 短节(消费方式 + 红线 + clone 示例)。
|
||||
4. 若改变业务技能硬约束,同步 [`POLICY_MATRIX.md`](POLICY_MATRIX.md) / 相关深度文档。
|
||||
5. **不要**只在某个业务技能 README 里私藏链接而不登记本页。
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关链接
|
||||
|
||||
- 开发入口:[`README.md`](README.md)
|
||||
- 运行时:[`RUNTIME.md`](RUNTIME.md)
|
||||
- 网页 RPA / 账号:[`RPA.md`](RPA.md)、[`ADAPTER.md`](ADAPTER.md)
|
||||
- 脚手架:[`../tools/README.md`](../tools/README.md)
|
||||
@@ -52,7 +52,7 @@
|
||||
| `health` / `version` / `config-path` / 纯统计 / 秒级查询 | `sync` |
|
||||
| 任意 placement(含 toolbar) | 允许 sync 或 async;以 manifest 为准 |
|
||||
|
||||
合法:`sync|async` × `toolbar|cron|agent|skill-detail`。
|
||||
合法:`sync|async` × `toolbar|row|batch|cron|agent|skill-detail`。
|
||||
|
||||
历史说明:旧文档曾错误地把数据管理按钮入口与异步执行方式绑死。该约束**已废止**;模板测试与 Schema **不得**再建立「入口位置决定 sync/async」一类硬耦合。旧版宿主曾「省略 → async」;**当前宿主为「省略 → sync」**。新技能仍须显式声明,不得依赖省略行为。
|
||||
|
||||
@@ -62,14 +62,18 @@
|
||||
|
||||
多个入口统一走 `POST /api/skill-actions/run`,可能带 `source.kind`:
|
||||
|
||||
| 入口 | 常见 `source.kind` | 说明 |
|
||||
|------|-------------------|------|
|
||||
| **Agent** | `agent` | `run_skill_action`;按 manifest 的 executionProfile 执行 |
|
||||
| **数据管理** | `data-management` | toolbar 按钮;须 `bind.tables` |
|
||||
| **定时任务** | `cron` | Cron 配置参数后调用同一 Action |
|
||||
| **技能详情** | (依宿主) | 与上同一 CLI / 同一业务内核 |
|
||||
| 入口(匠厂侧栏 / 界面) | 常见 `source.kind` | 技能侧 | 开发者怎么测 |
|
||||
|-------------------------|-------------------|--------|--------------|
|
||||
| **新建任务**(对话 Agent) | `agent` | `placements` 含 `agent`;`run_skill_action` | 侧栏「新建任务」→ 对话触发 |
|
||||
| **数据管理 · 顶栏** | `data-management` | `toolbar` + 必填 `bind.tables` | 侧栏「数据管理」→ 表顶栏按钮 |
|
||||
| **数据管理 · 行内** | `data-management` | `row`(建议 `bind.tables` + `inputMapping`) | 同行操作链接(需主键) |
|
||||
| **数据管理 · 批量** | `data-management` | `batch` | 勾选行后点批量按钮 |
|
||||
| **定时任务** | `cron` | `placements` 含 `cron` | 侧栏「定时任务」→「技能直调」 |
|
||||
| **任务中心** | (各入口触发的 Job) | `executionProfile: "async"` | 侧栏「任务中心」查看进度 / 取消;**不是** placement |
|
||||
| **技能详情** | `skill-detail` | `placements` 含 `skill-detail` | 市场详情安装/四 Tab;详情直调按钮随宿主上线后**同等自测** |
|
||||
|
||||
技能 **不要** 在 Python 里按 `source` / placement 写业务分叉;宿主读 manifest 分流展示与等待策略。
|
||||
技能 **不要** 在 Python 里按 `source` / placement 写业务分叉;宿主读 manifest 分流展示与等待策略。
|
||||
**已声明的入口都要自测**,不要只测对话。操作清单见 [`DEVELOPMENT.md`](DEVELOPMENT.md) §15 第七步。
|
||||
|
||||
---
|
||||
|
||||
@@ -81,7 +85,8 @@
|
||||
|-------------|-------------------------|-----------------|----------------|
|
||||
| 运行检查 / 版本 / 配置路径 | `sync` | `skill-detail`, `agent` | 无 |
|
||||
| 库 / 队列统计 | `sync` | `skill-detail`, `agent` | 无 |
|
||||
| 外源 → 本地库同步 | `sync` 或 `async` | 可含 `toolbar`(须 `bind.tables`)、`cron`、`agent` | 可选 |
|
||||
| 外源 → 本地库同步 | `sync` 或 `async` | 可含 `toolbar`(须 `bind.tables`)、`cron`、`agent`、`skill-detail` | 可选 |
|
||||
| 单行 / 批量数据管理动作 | `sync` 或 `async` | `row` / `batch`(建议 `bind.tables`;行内常用 `inputMapping`) | 可选 |
|
||||
| 数据导入 / 失败重置 | `sync` 或 `async` | `skill-detail`, `agent` | 可选 |
|
||||
| **单条**副作用 RPA | `async`(建议) | `agent`, `skill-detail` | **建议** |
|
||||
| **批量顺序** RPA(pick) | `async`(建议) | `toolbar`, `cron`, `agent` | **建议** |
|
||||
|
||||
@@ -81,7 +81,7 @@ def test_whatever():
|
||||
|
||||
未设置环境变量 ⇒ 等价 `unit`。
|
||||
|
||||
默认策略摘要:**不要在 unittest 必跑路径误设 `OPENCLAW_TEST_TARGET=real_*`**。
|
||||
默认策略摘要:**不要在 unittest 必跑路径误设 `OPENCLAW_TEST_TARGET=real_*`**(CI 仍离线)。业务 `.env` 模板默认是 `real_rpa`,与「单测用 mock」不冲突;**mock 通 ≠ 交活**。
|
||||
|
||||
档位读取与业务代码一致:经 `jiangchang_skill_core.config.get("OPENCLAW_TEST_TARGET")`(见 `CONFIG.md`)。
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
**`examples/` 不是完整 skill 仓库。** 不要整包照搬 `examples/<mode>/` 到新技能根目录。应按各 example README 的 **copy map**,**选择性复制**到新技能的 `scripts/`(及按需的 `tests/`)。
|
||||
|
||||
复制任何案例前先读 [`../development/AI_RPA.md`](../development/AI_RPA.md):案例只示范授权范围内的正常界面自动化,禁止把它扩展为破解、逆向、私有协议、验证码绕过、Hook / 注入、自动换号或规避平台限制的实现。
|
||||
|
||||
根 `README.md`、`SKILL.md`、`references/`、`development/` 仍按模板主目录规则编写,**不要**从 `examples/` 复制。
|
||||
|
||||
## 四类外部交互方式
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- 本示例是通用模板,不绑定具体平台。
|
||||
- 示例抽象自已跑通的真实浏览器 RPA skill(抖音视频链接采集),但代码已改成中性示例命名。
|
||||
- 新 skill 开发时可以复制本目录,再替换目标站点、选择器、解析逻辑和业务字段。
|
||||
- 只用于授权范围内的正常界面操作;禁止破解、逆向、私有协议、验证码绕过、自动换号或其他规避平台限制的扩展。
|
||||
|
||||
## 适用场景
|
||||
|
||||
@@ -142,6 +143,7 @@ real_browser_rpa/
|
||||
|
||||
- **不 import account-manager 内部模块** — 只通过 CLI/subprocess 调用
|
||||
- **不自动破解验证码** — 滑块/短信只检测 + 等待人工完成
|
||||
- **不对抗平台风控** — 频控、账号风险、封禁或权限不足时停止并交由人工处理
|
||||
- **日志脱敏** — 不输出完整手机号/账号
|
||||
- **租约释放** — `ensure-web` / `pick-web --lease` 后必须在 `finally` 释放
|
||||
- **失败留痕** — 真实 skill 应在关键失败点截图(示例中已留注释位)
|
||||
|
||||
@@ -159,6 +159,8 @@ def _normalize_error_code(raw: str) -> str:
|
||||
|
||||
|
||||
def _validate_pick_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
if not isinstance(payload, dict):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回格式异常。")
|
||||
if payload.get("success") is False:
|
||||
err = payload.get("error") if isinstance(payload.get("error"), dict) else {}
|
||||
code = _normalize_error_code(str(err.get("code") or ""))
|
||||
@@ -174,10 +176,10 @@ def _validate_pick_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
raise AccountManagerError("NO_ACCOUNT", message or "没有可用账号。")
|
||||
raise AccountManagerError(code or "ENSURE_WEB_FAILED", message or "ensure-web 失败。")
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回格式异常。")
|
||||
if not payload.get("profile_dir"):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回的账号缺少 profile_dir。")
|
||||
if not payload.get("lease_token"):
|
||||
raise AccountManagerError("LEASE_REQUIRED", "account-manager 未返回 lease_token,禁止打开浏览器。")
|
||||
return payload
|
||||
|
||||
|
||||
@@ -226,7 +228,44 @@ def _ensure_web_with_lease(
|
||||
return _validate_pick_payload(payload)
|
||||
|
||||
|
||||
def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
def _pick_existing_with_lease(
|
||||
platform: str,
|
||||
*,
|
||||
login_id: Optional[str] = None,
|
||||
label: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
argv = [
|
||||
"account",
|
||||
"pick-web",
|
||||
"--platform",
|
||||
platform,
|
||||
"--lease",
|
||||
"--holder",
|
||||
LEASE_HOLDER,
|
||||
"--ttl-sec",
|
||||
LEASE_TTL_SEC,
|
||||
"--purpose",
|
||||
"rpa",
|
||||
]
|
||||
if login_id:
|
||||
argv.extend(["--login-id", login_id])
|
||||
elif label:
|
||||
argv.extend(["--label", label])
|
||||
else:
|
||||
raise AccountManagerError("AMBIGUOUS_ACCOUNT", "指定账号缺少可用于带租约选择的登录标识或名称。")
|
||||
|
||||
proc = _run_argv(argv)
|
||||
out = proc.stdout or ""
|
||||
if proc.returncode != 0 and not out.strip():
|
||||
raise AccountManagerError(
|
||||
"PICK_WEB_FAILED",
|
||||
(proc.stderr or "").strip() or "pick-web 子进程失败",
|
||||
)
|
||||
payload = _parse_last_json(out)
|
||||
return _validate_pick_payload(payload)
|
||||
|
||||
|
||||
def _pick_by_id_with_lease(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
proc = _run_argv(["account", "get", str(account_id)])
|
||||
out = proc.stdout or ""
|
||||
if proc.returncode != 0 or out.startswith("ERROR:"):
|
||||
@@ -239,6 +278,8 @@ def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
else:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "account get 返回非对象 JSON。")
|
||||
|
||||
if data.get("success") is False:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"指定账号 {account_id} 不存在或获取失败。")
|
||||
platform_key = str(data.get("platform_key") or "").lower()
|
||||
if platform_key != platform.lower():
|
||||
raise AccountManagerError(
|
||||
@@ -248,9 +289,26 @@ def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
profile_dir = str(data.get("profile_dir") or "").strip()
|
||||
if not profile_dir:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"账号 {account_id} 缺少 profile_dir。")
|
||||
data["profile_dir"] = profile_dir
|
||||
data["lease_token"] = ""
|
||||
return data
|
||||
if str(data.get("status") or "active").lower() != "active":
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"账号 {account_id} 当前不是 active 状态。")
|
||||
|
||||
try:
|
||||
selected = _pick_existing_with_lease(
|
||||
platform,
|
||||
login_id=(str(data.get("login_id") or "").strip() or None),
|
||||
label=(str(data.get("account_label") or "").strip() or None),
|
||||
)
|
||||
except AccountManagerError as exc:
|
||||
if exc.code in ("NO_ACCOUNT", "ACCOUNT_NOT_FOUND"):
|
||||
raise AccountManagerError("LEASE_CONFLICT", LEASE_BUSY_MESSAGE) from exc
|
||||
raise
|
||||
if str(selected.get("id")) != str(account_id):
|
||||
release_lease(str(selected.get("lease_token") or ""))
|
||||
raise AccountManagerError(
|
||||
"AMBIGUOUS_ACCOUNT",
|
||||
f"无法唯一取得指定账号 {account_id} 的租约,请检查重复的登录标识或账号名称。",
|
||||
)
|
||||
return selected
|
||||
|
||||
|
||||
def pick_web_account(
|
||||
@@ -271,7 +329,7 @@ def pick_web_account(
|
||||
login_id = aid_raw
|
||||
aid_raw = ""
|
||||
else:
|
||||
return _pick_by_id(platform_key, int(aid_raw))
|
||||
return _pick_by_id_with_lease(platform_key, int(aid_raw))
|
||||
|
||||
lid = (login_id or "").strip() or None
|
||||
if lid and _is_placeholder_config(lid):
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from jiangchang_skill_core import config
|
||||
from jiangchang_skill_core.activity import interruptible_sleep
|
||||
|
||||
from service.browser_session import close_browser_context, get_start_url, start_browser_session
|
||||
@@ -101,9 +102,9 @@ def _step_cb(cb: Optional[StepCallback], text: str) -> None:
|
||||
|
||||
|
||||
async def _random_delay() -> None:
|
||||
lo = int(os.getenv("RPA_STEP_DELAY_MIN_MS") or "900")
|
||||
hi = int(os.getenv("RPA_STEP_DELAY_MAX_MS") or "2600")
|
||||
await interruptible_sleep(random.uniform(lo / 1000.0, hi / 1000.0))
|
||||
lo = max(0.0, config.get_float("STEP_DELAY_MIN", 1.0))
|
||||
hi = max(lo, config.get_float("STEP_DELAY_MAX", 3.0))
|
||||
await interruptible_sleep(random.uniform(lo, hi))
|
||||
|
||||
|
||||
async def _scroll_wait() -> None:
|
||||
|
||||
63
examples/real_browser_rpa/tests/test_account_client.py
Normal file
63
examples/real_browser_rpa/tests/test_account_client.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from service.account_client import AccountManagerError, pick_web_account
|
||||
|
||||
|
||||
def _completed(payload: dict) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.CompletedProcess(
|
||||
args=["python", "account-manager"],
|
||||
returncode=0,
|
||||
stdout=json.dumps(payload, ensure_ascii=False),
|
||||
stderr="",
|
||||
)
|
||||
|
||||
|
||||
class TestExplicitAccountLease(unittest.TestCase):
|
||||
@patch("service.account_client._run_argv")
|
||||
def test_explicit_account_id_is_reselected_with_lease(self, run_argv) -> None:
|
||||
account = {
|
||||
"id": 42,
|
||||
"platform_key": "target-platform",
|
||||
"account_label": "主账号",
|
||||
"login_id": "user@example.com",
|
||||
"profile_dir": "D:/data/profile-42",
|
||||
"status": "active",
|
||||
}
|
||||
leased = {**account, "lease_token": "lease-42"}
|
||||
run_argv.side_effect = [_completed(account), _completed(leased)]
|
||||
|
||||
result = pick_web_account("target-platform", account_id="42")
|
||||
|
||||
self.assertEqual(result["id"], 42)
|
||||
self.assertEqual(result["lease_token"], "lease-42")
|
||||
self.assertEqual(run_argv.call_args_list[0].args[0], ["account", "get", "42"])
|
||||
pick_args = run_argv.call_args_list[1].args[0]
|
||||
self.assertIn("pick-web", pick_args)
|
||||
self.assertIn("--lease", pick_args)
|
||||
self.assertIn("user@example.com", pick_args)
|
||||
|
||||
@patch("service.account_client._run_argv")
|
||||
def test_missing_lease_token_is_rejected(self, run_argv) -> None:
|
||||
account = {
|
||||
"id": 42,
|
||||
"platform_key": "target-platform",
|
||||
"account_label": "主账号",
|
||||
"login_id": "user@example.com",
|
||||
"profile_dir": "D:/data/profile-42",
|
||||
"status": "active",
|
||||
}
|
||||
run_argv.side_effect = [_completed(account), _completed(account)]
|
||||
|
||||
with self.assertRaises(AccountManagerError) as raised:
|
||||
pick_web_account("target-platform", account_id="42")
|
||||
|
||||
self.assertEqual(raised.exception.code, "LEASE_REQUIRED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
本示例抽象自已跑通的「工行批量提交类」浏览器自动化 skill 的工程范式,但已**去业务化**——不包含真实银行/真实资金语义,也**不是** Amazon 或任何生产技能的复制。
|
||||
|
||||
本示例只教学授权范围内的正常界面自动化;禁止把 sandbox 做法扩展为破解、逆向、私有协议、验证码绕过、Hook / 注入、自动换号或规避真实平台限制的实现。
|
||||
|
||||
## 适合什么场景
|
||||
|
||||
- 自有 **行业仿真系统** / `sandbox.jc2009.com` / 本地 `sandbox/demo_app.html`
|
||||
|
||||
@@ -159,6 +159,8 @@ def _normalize_error_code(raw: str) -> str:
|
||||
|
||||
|
||||
def _validate_pick_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
if not isinstance(payload, dict):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回格式异常。")
|
||||
if payload.get("success") is False:
|
||||
err = payload.get("error") if isinstance(payload.get("error"), dict) else {}
|
||||
code = _normalize_error_code(str(err.get("code") or ""))
|
||||
@@ -174,10 +176,10 @@ def _validate_pick_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
raise AccountManagerError("NO_ACCOUNT", message or "没有可用账号。")
|
||||
raise AccountManagerError(code or "ENSURE_WEB_FAILED", message or "ensure-web 失败。")
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回格式异常。")
|
||||
if not payload.get("profile_dir"):
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "ensure-web 返回的账号缺少 profile_dir。")
|
||||
if not payload.get("lease_token"):
|
||||
raise AccountManagerError("LEASE_REQUIRED", "account-manager 未返回 lease_token,禁止打开浏览器。")
|
||||
return payload
|
||||
|
||||
|
||||
@@ -226,7 +228,44 @@ def _ensure_web_with_lease(
|
||||
return _validate_pick_payload(payload)
|
||||
|
||||
|
||||
def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
def _pick_existing_with_lease(
|
||||
platform: str,
|
||||
*,
|
||||
login_id: Optional[str] = None,
|
||||
label: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
argv = [
|
||||
"account",
|
||||
"pick-web",
|
||||
"--platform",
|
||||
platform,
|
||||
"--lease",
|
||||
"--holder",
|
||||
LEASE_HOLDER,
|
||||
"--ttl-sec",
|
||||
LEASE_TTL_SEC,
|
||||
"--purpose",
|
||||
"rpa",
|
||||
]
|
||||
if login_id:
|
||||
argv.extend(["--login-id", login_id])
|
||||
elif label:
|
||||
argv.extend(["--label", label])
|
||||
else:
|
||||
raise AccountManagerError("AMBIGUOUS_ACCOUNT", "指定账号缺少可用于带租约选择的登录标识或名称。")
|
||||
|
||||
proc = _run_argv(argv)
|
||||
out = proc.stdout or ""
|
||||
if proc.returncode != 0 and not out.strip():
|
||||
raise AccountManagerError(
|
||||
"PICK_WEB_FAILED",
|
||||
(proc.stderr or "").strip() or "pick-web 子进程失败",
|
||||
)
|
||||
payload = _parse_last_json(out)
|
||||
return _validate_pick_payload(payload)
|
||||
|
||||
|
||||
def _pick_by_id_with_lease(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
proc = _run_argv(["account", "get", str(account_id)])
|
||||
out = proc.stdout or ""
|
||||
if proc.returncode != 0 or out.startswith("ERROR:"):
|
||||
@@ -239,6 +278,8 @@ def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
else:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", "account get 返回非对象 JSON。")
|
||||
|
||||
if data.get("success") is False:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"指定账号 {account_id} 不存在或获取失败。")
|
||||
platform_key = str(data.get("platform_key") or "").lower()
|
||||
if platform_key != platform.lower():
|
||||
raise AccountManagerError(
|
||||
@@ -248,9 +289,26 @@ def _pick_by_id(platform: str, account_id: int) -> Dict[str, Any]:
|
||||
profile_dir = str(data.get("profile_dir") or "").strip()
|
||||
if not profile_dir:
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"账号 {account_id} 缺少 profile_dir。")
|
||||
data["profile_dir"] = profile_dir
|
||||
data["lease_token"] = ""
|
||||
return data
|
||||
if str(data.get("status") or "active").lower() != "active":
|
||||
raise AccountManagerError("ACCOUNT_NOT_FOUND", f"账号 {account_id} 当前不是 active 状态。")
|
||||
|
||||
try:
|
||||
selected = _pick_existing_with_lease(
|
||||
platform,
|
||||
login_id=(str(data.get("login_id") or "").strip() or None),
|
||||
label=(str(data.get("account_label") or "").strip() or None),
|
||||
)
|
||||
except AccountManagerError as exc:
|
||||
if exc.code in ("NO_ACCOUNT", "ACCOUNT_NOT_FOUND"):
|
||||
raise AccountManagerError("LEASE_CONFLICT", LEASE_BUSY_MESSAGE) from exc
|
||||
raise
|
||||
if str(selected.get("id")) != str(account_id):
|
||||
release_lease(str(selected.get("lease_token") or ""))
|
||||
raise AccountManagerError(
|
||||
"AMBIGUOUS_ACCOUNT",
|
||||
f"无法唯一取得指定账号 {account_id} 的租约,请检查重复的登录标识或账号名称。",
|
||||
)
|
||||
return selected
|
||||
|
||||
|
||||
def pick_web_account(
|
||||
@@ -271,7 +329,7 @@ def pick_web_account(
|
||||
login_id = aid_raw
|
||||
aid_raw = ""
|
||||
else:
|
||||
return _pick_by_id(platform_key, int(aid_raw))
|
||||
return _pick_by_id_with_lease(platform_key, int(aid_raw))
|
||||
|
||||
lid = (login_id or "").strip() or None
|
||||
if lid and _is_placeholder_config(lid):
|
||||
|
||||
63
examples/simulator_browser_rpa/tests/test_account_client.py
Normal file
63
examples/simulator_browser_rpa/tests/test_account_client.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from service.account_client import AccountManagerError, pick_web_account
|
||||
|
||||
|
||||
def _completed(payload: dict) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.CompletedProcess(
|
||||
args=["python", "account-manager"],
|
||||
returncode=0,
|
||||
stdout=json.dumps(payload, ensure_ascii=False),
|
||||
stderr="",
|
||||
)
|
||||
|
||||
|
||||
class TestExplicitAccountLease(unittest.TestCase):
|
||||
@patch("service.account_client._run_argv")
|
||||
def test_explicit_account_id_is_reselected_with_lease(self, run_argv) -> None:
|
||||
account = {
|
||||
"id": 42,
|
||||
"platform_key": "target-platform",
|
||||
"account_label": "主账号",
|
||||
"login_id": "user@example.com",
|
||||
"profile_dir": "D:/data/profile-42",
|
||||
"status": "active",
|
||||
}
|
||||
leased = {**account, "lease_token": "lease-42"}
|
||||
run_argv.side_effect = [_completed(account), _completed(leased)]
|
||||
|
||||
result = pick_web_account("target-platform", account_id="42")
|
||||
|
||||
self.assertEqual(result["id"], 42)
|
||||
self.assertEqual(result["lease_token"], "lease-42")
|
||||
self.assertEqual(run_argv.call_args_list[0].args[0], ["account", "get", "42"])
|
||||
pick_args = run_argv.call_args_list[1].args[0]
|
||||
self.assertIn("pick-web", pick_args)
|
||||
self.assertIn("--lease", pick_args)
|
||||
self.assertIn("user@example.com", pick_args)
|
||||
|
||||
@patch("service.account_client._run_argv")
|
||||
def test_missing_lease_token_is_rejected(self, run_argv) -> None:
|
||||
account = {
|
||||
"id": 42,
|
||||
"platform_key": "target-platform",
|
||||
"account_label": "主账号",
|
||||
"login_id": "user@example.com",
|
||||
"profile_dir": "D:/data/profile-42",
|
||||
"status": "active",
|
||||
}
|
||||
run_argv.side_effect = [_completed(account), _completed(account)]
|
||||
|
||||
with self.assertRaises(AccountManagerError) as raised:
|
||||
pick_web_account("target-platform", account_id="42")
|
||||
|
||||
self.assertEqual(raised.exception.code, "LEASE_REQUIRED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -80,7 +80,7 @@ Action 是**可选能力**:
|
||||
|
||||
| 字段 / 层 | 只决定什么 | 不决定什么 |
|
||||
|-----------|------------|------------|
|
||||
| **`placements`** | Action **出现在哪里**(toolbar / cron / agent / skill-detail …) | 不决定 sync/async |
|
||||
| **`placements`** | Action **出现在哪里**(toolbar / row / batch / cron / agent / skill-detail) | 不决定 sync/async |
|
||||
| **`bind.tables`** | 数据管理里出现在**哪些业务表** | 不决定业务实现 |
|
||||
| **`executionProfile`** | 宿主是**同步等待**还是**异步 Job** | 不限制可展示入口 |
|
||||
| **`entrypoint`** | 实际执行哪个 CLI 命令与参数 | 不写业务逻辑 |
|
||||
@@ -95,9 +95,9 @@ Action 是**可选能力**:
|
||||
| 顶层 | `schemaVersion`、`skill`、`actions` |
|
||||
| action | `id`、`label`、`description`、`placements`、`entrypoint`、**`executionProfile`(必填)**;可选 `inputSchema`、`confirmation`、**`bind`** |
|
||||
| entrypoint | `type = cli`、`command`、`args`(**必填** JSON 数组,项为 `string` / `number` / `boolean`) |
|
||||
| placements | `toolbar`、`skill-detail`、`agent`、`cron` |
|
||||
| **bind** | `{ "tables": ["business_table"] }`(见下节) |
|
||||
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive` |
|
||||
| placements | `toolbar`、`row`、`batch`、`skill-detail`、`agent`、`cron` |
|
||||
| **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`(行内/批量预填,见下节) |
|
||||
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly` |
|
||||
| confirmation | `{ "message": "..." }` |
|
||||
| **executionProfile** | `"sync"` \| `"async"`(**必须显式写出**,禁止依赖缺省) |
|
||||
|
||||
@@ -113,14 +113,16 @@ Action 是**可选能力**:
|
||||
|
||||
规则:
|
||||
|
||||
- `bind` 为 object,`additionalProperties: false`;当前只允许 `tables`。
|
||||
- `bind` 为 object,`additionalProperties: false`;当前允许 `tables`(必填键当存在 bind 时)与可选 `inputMapping`。
|
||||
- `tables` 非空数组、元素唯一、英文 **snake_case**、不得为空字符串。
|
||||
- **`placements` 含 `toolbar` 时必须显式声明非空 `bind.tables`**(模板 Schema `if/then` + 自动测试强制)。
|
||||
- `row` / `batch` **建议**同样声明 `bind.tables`,避免在无关表上露出按钮;未声明时宿主可能按兼容逻辑在更多表上展示。
|
||||
- 表名须真实存在于技能 SQLite 与 `_jiangchang_tables`。
|
||||
- 希望出现在多张表时,显式列出所有表;**不要**依赖「无 bind 则全表展示」——那是宿主对**旧技能**的兼容,不是新技能标准。
|
||||
- 本阶段**不**扩展 `selection` / `inputMapping` / `columns` / row binding / concurrency / locks。
|
||||
- **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段;可用 `inputSchema.properties.*.readOnly: true` 在表单中只读展示。
|
||||
- `concurrency` / `locks` 仍未稳定,**不要**写入新技能 manifest。
|
||||
|
||||
默认模板 `actions.json` 仅含 `health` / `version` / `config-path`,不放在 toolbar,故无需 `bind` 示例污染默认 manifest。业务示例:
|
||||
默认模板 `actions.json` 仅含 `health` / `version` / `config-path`,不放在 toolbar/row/batch,故无需 `bind` 示例污染默认 manifest。业务示例:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -134,16 +136,42 @@ Action 是**可选能力**:
|
||||
}
|
||||
```
|
||||
|
||||
行内示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "retry-one",
|
||||
"label": "重试本行",
|
||||
"description": "对当前行执行一次业务重试。",
|
||||
"placements": ["row"],
|
||||
"executionProfile": "async",
|
||||
"bind": {
|
||||
"tables": ["business_records"],
|
||||
"inputMapping": { "recordId": "$row.$pk" }
|
||||
},
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["recordId"],
|
||||
"properties": {
|
||||
"recordId": { "type": "string", "title": "记录 ID", "readOnly": true }
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "cli",
|
||||
"command": "retry",
|
||||
"args": ["--id", "{{recordId}}"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 预留能力(当前不可当作已实现能力)
|
||||
|
||||
| 类别 | 预留项 | 说明 |
|
||||
|------|--------|------|
|
||||
| placements | `row`、`batch` | 行内 / 批量入口,未来扩展;新技能示例不得使用 |
|
||||
| action 字段 | `concurrency`、`locks` | 并发与锁控制,未稳定开放 |
|
||||
| inputSchema | `array`、nested `object`、`oneOf` 等 | 复杂表单,不属于当前推荐范围 |
|
||||
|
||||
`bind` **不是**保留字段;已作为 Phase 1 表级绑定正式能力。
|
||||
|
||||
`bind`、`row`、`batch` **不是**保留能力;已按匠厂宿主正式支持写入本规范。
|
||||
## 执行模型
|
||||
|
||||
宿主将 action 转为 argv,再用**共享 Python**(用户数据目录下 `python-runtime/.venv`)执行:
|
||||
@@ -168,7 +196,7 @@ python {skill_root}/scripts/main.py <command> <args...>
|
||||
| `sync`(或省略时的宿主缺省) | 调用方等待结束,直接返回结构化结果或结构化错误;**不**创建后台 Job | 不进 |
|
||||
| `async`(须显式声明) | 宿主创建后台 Job,立即返回 `jobId`;用 `emit` / `checkpoint` / `finish` 报告进度与终态 | **进入** |
|
||||
|
||||
合法组合(全部允许):`sync|async` × `toolbar|cron|agent|skill-detail`。
|
||||
合法组合(全部允许):`sync|async` × `toolbar|row|batch|cron|agent|skill-detail`。
|
||||
|
||||
经验建议(**不是** placement 硬限制):耗时不可预测、需要暂停/停止/进度展示的任务,用 `async`;其余默认写 `sync`。
|
||||
|
||||
@@ -199,26 +227,27 @@ HINT: <next step>
|
||||
|
||||
| 值 | 含义 |
|
||||
|----|------|
|
||||
| `toolbar` | 数据管理第一排技能按钮(须配合 `bind.tables`) |
|
||||
| `skill-detail` | 技能详情或技能市场详情页 |
|
||||
| `toolbar` | 数据管理表顶栏技能按钮(须配合 `bind.tables`) |
|
||||
| `row` | 数据管理行内操作(建议 `bind.tables` + `inputMapping`;表需有主键) |
|
||||
| `batch` | 数据管理批量操作(需勾选行;无勾选时宿主禁用按钮) |
|
||||
| `skill-detail` | 技能详情页直调(与安装/四 Tab 同属技能市场详情;已声明则须自测) |
|
||||
| `agent` | Agent 可直接调用(`run_skill_action`) |
|
||||
| `cron` | 定时任务可直接调用 |
|
||||
|
||||
**预留(模板示例请勿使用):** `row`、`batch`。
|
||||
| `cron` | 定时任务「技能直调」 |
|
||||
|
||||
### Action 类型 × placements(常见示例,不是限制)
|
||||
|
||||
下表只表示**常见用法示例**。实际 `placements` 以 manifest 配置为准;**`sync` / `async` 不影响展示入口**。
|
||||
下表只表示**常见用法示例**。实际 `placements` 以 manifest 配置为准;**`sync` / `async` 不影响展示入口**。已声明的入口都要在宿主自测,不要只测其中一条。
|
||||
|
||||
| Action 类型 | 常见 executionProfile | 常见 placements | confirmation |
|
||||
|-------------|----------------------|-----------------|--------------|
|
||||
| health / version / config-path | sync | skill-detail, agent | 无 |
|
||||
| 统计 / 只读查询 | sync | skill-detail, agent | 无 |
|
||||
| 数据同步(外源 → 本地库) | sync 或 async | toolbar + cron + agent + skill-detail(按需) | 可选 |
|
||||
| 单行重试 / 行内处理 | sync 或 async | row(可加 agent) | 可选 |
|
||||
| 批量处理勾选行 | sync 或 async | batch(可加 toolbar/cron) | 可选 |
|
||||
| 导入 / 重置 | sync 或 async | skill-detail, agent | 可选 |
|
||||
| 单条副作用 RPA | async(建议) | agent, skill-detail | **建议** |
|
||||
| 批量顺序 pick RPA | async(建议) | toolbar, cron, agent | **建议** |
|
||||
|
||||
## 同步数据 vs 导出当前表 vs 特殊业务报告
|
||||
|
||||
不要混用「导出」一词:
|
||||
@@ -236,7 +265,7 @@ HINT: <next step>
|
||||
当前宿主稳定支持:
|
||||
|
||||
- `object` / `string` / `number` / `boolean`
|
||||
- `enum` / `default` / `required` / `sensitive`
|
||||
- `enum` / `default` / `required` / `sensitive` / `readOnly`
|
||||
- 简单模板替换(`{{fieldName}}`)
|
||||
|
||||
示例:
|
||||
@@ -281,6 +310,8 @@ HINT: <next step>
|
||||
- `enum` 在宿主中渲染为选择框
|
||||
- `required` 字段在提交前校验
|
||||
- `sensitive` 字段不得出现在低信任入口
|
||||
- `readOnly: true` 时数据管理表单只读展示该字段(仍随提交传入);适合行内/勾选预填的主键、编号列表
|
||||
- `placements` 含 `batch` 的动作在数据管理表顶常驻,无勾选时禁用(与表删除一致)
|
||||
- 需要确认的副作用操作使用 `confirmation.message`
|
||||
- 模板参数使用 `{{fieldName}}`,且必须在 `inputSchema.properties` 中声明
|
||||
- `entrypoint.args` 项当前只允许 `string`、`number`、`boolean`;不允许对象或 `null`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
|
||||
|
||||
SKILL_SLUG = "your-skill-slug"
|
||||
SKILL_VERSION = "1.0.51"
|
||||
SKILL_VERSION = "1.0.58"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.2"
|
||||
|
||||
@@ -15,7 +15,6 @@ from util.constants import SKILL_SLUG
|
||||
|
||||
_TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
|
||||
_ALLOWED_PLACEMENTS = frozenset({"toolbar", "row", "batch", "cron", "agent", "skill-detail"})
|
||||
_RESERVED_PLACEMENTS = frozenset({"row", "batch"})
|
||||
_RESERVED_ACTION_FIELDS = frozenset({"concurrency", "locks"})
|
||||
_ALLOWED_EXECUTION_PROFILES = frozenset({"sync", "async"})
|
||||
_REQUIRED_MANIFEST_KEYS = frozenset({"schemaVersion", "skill", "actions"})
|
||||
@@ -275,13 +274,13 @@ class TestActionsManifest(unittest.TestCase):
|
||||
self.assertEqual(missing, [], msg="\n".join(missing))
|
||||
|
||||
def test_schema_allows_all_placement_execution_profile_combinations(self) -> None:
|
||||
"""POLICY-SKILL-ACTION-004:4 placements × 2 profiles 必须全部通过 Schema。"""
|
||||
"""POLICY-SKILL-ACTION-004:6 placements × 2 profiles 必须全部通过 Schema。"""
|
||||
try:
|
||||
import jsonschema
|
||||
except ImportError:
|
||||
self.fail("jsonschema is required to validate placement×executionProfile orthogonality")
|
||||
schema = _load_actions_schema()
|
||||
placements = ("toolbar", "cron", "agent", "skill-detail")
|
||||
placements = ("toolbar", "row", "batch", "cron", "agent", "skill-detail")
|
||||
profiles = ("sync", "async")
|
||||
for placement in placements:
|
||||
for profile in profiles:
|
||||
@@ -346,16 +345,6 @@ class TestActionsManifest(unittest.TestCase):
|
||||
for snippet in forbidden_snippets:
|
||||
self.assertNotIn(snippet, text)
|
||||
|
||||
def test_template_example_does_not_use_reserved_placements(self) -> None:
|
||||
manifest = _load_actions_manifest()
|
||||
for action in manifest["actions"]:
|
||||
placements = set(action.get("placements") or [])
|
||||
overlap = placements & _RESERVED_PLACEMENTS
|
||||
self.assertFalse(
|
||||
overlap,
|
||||
msg=f"{action['id']} uses reserved placements not yet supported in template: {overlap}",
|
||||
)
|
||||
|
||||
def test_template_variables_have_input_schema(self) -> None:
|
||||
manifest = _load_actions_manifest()
|
||||
for action in manifest["actions"]:
|
||||
|
||||
@@ -84,8 +84,8 @@ class TestConfigBootstrap(unittest.TestCase):
|
||||
config.reset_cache()
|
||||
with open(example, encoding="utf-8") as f:
|
||||
example_text = f.read()
|
||||
self.assertIn(_env_line(_TEST_TARGET_KEY, "mock").strip(), example_text)
|
||||
self.assertEqual(config.get(_TEST_TARGET_KEY), "mock")
|
||||
self.assertIn(_env_line(_TEST_TARGET_KEY, "real_" + "rpa").strip(), example_text)
|
||||
self.assertEqual(config.get(_TEST_TARGET_KEY), "real_" + "rpa")
|
||||
|
||||
def test_config_path_outputs_json(self) -> None:
|
||||
with IsolatedDataRoot(user_id="_cfg_path"):
|
||||
|
||||
@@ -36,6 +36,8 @@ POLICY_IDS = (
|
||||
"POLICY-RPA-002",
|
||||
"POLICY-RPA-003",
|
||||
"POLICY-RPA-004",
|
||||
"POLICY-RPA-005",
|
||||
"POLICY-RPA-006",
|
||||
"POLICY-PACKAGING-001",
|
||||
"POLICY-PACKAGING-002",
|
||||
"POLICY-DOCS-001",
|
||||
@@ -155,6 +157,45 @@ RPA_004_LAUNCH_MARKERS = (
|
||||
)
|
||||
RPA_004_PICK_MARKER = "pick_web_account"
|
||||
|
||||
RPA_005_SOURCE = (
|
||||
"development/AI_RPA.md §4–§6; development/RPA.md §0.0/§0.3; "
|
||||
"development/POLICY_MATRIX.md"
|
||||
)
|
||||
RPA_005_REQUIRED_DOC_MARKERS = (
|
||||
"AI Agent 是用户入口与业务编排者",
|
||||
"RPA 是确定性执行器",
|
||||
"破解",
|
||||
"逆向",
|
||||
"私有协议",
|
||||
"验证码",
|
||||
"人工处理",
|
||||
"1~3 秒",
|
||||
"状态等待",
|
||||
)
|
||||
RPA_005_STEP_DELAY_KEY_RE = re.compile(
|
||||
r"(?m)^\s*(STEP_DELAY_MIN|STEP_DELAY_MAX)\s*=\s*([^\s#]+)"
|
||||
)
|
||||
|
||||
RPA_006_SOURCE = (
|
||||
"development/ACCOUNT_MANAGER.md; development/ADAPTER.md §兄弟依赖; "
|
||||
"development/RPA.md §0.2; development/POLICY_MATRIX.md"
|
||||
)
|
||||
RPA_006_REQUIRED_DOC_MARKERS = (
|
||||
"一账号一",
|
||||
"profile_dir",
|
||||
"lease_token",
|
||||
"数据管理页面",
|
||||
"account-manager.db",
|
||||
"account list",
|
||||
"lease list",
|
||||
"关闭浏览器并释放租约",
|
||||
"两个以上账号",
|
||||
)
|
||||
RPA_006_EXAMPLE_CLIENTS = (
|
||||
"examples/real_browser_rpa/scripts/service/account_client.py",
|
||||
"examples/simulator_browser_rpa/scripts/service/account_client.py",
|
||||
)
|
||||
|
||||
LOGGING_001_SOURCE = "development/LOGGING.md; development/RUNTIME.md"
|
||||
LOGGING_002_SOURCE = "development/LOGGING.md; development/DEVELOPMENT.md"
|
||||
LOGGING_003_SOURCE = "development/LOGGING.md"
|
||||
@@ -669,6 +710,87 @@ class TestPolicyRpa004(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestPolicyRpa005(unittest.TestCase):
|
||||
def test_ai_rpa_governance_doc_has_required_boundaries(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
path = os.path.join(skill_root, "development", "AI_RPA.md")
|
||||
self.assertTrue(
|
||||
os.path.isfile(path),
|
||||
msg=_policy_msg("POLICY-RPA-005", RPA_005_SOURCE, "development/AI_RPA.md missing"),
|
||||
)
|
||||
text = _read_text(path)
|
||||
missing = [marker for marker in RPA_005_REQUIRED_DOC_MARKERS if marker not in text]
|
||||
self.assertEqual(
|
||||
missing,
|
||||
[],
|
||||
msg=_policy_msg(
|
||||
"POLICY-RPA-005",
|
||||
RPA_005_SOURCE,
|
||||
"AI_RPA.md missing markers: " + ", ".join(missing),
|
||||
),
|
||||
)
|
||||
|
||||
def test_real_rpa_default_step_delay_is_one_to_three_seconds(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
env_path = os.path.join(skill_root, ".env.example")
|
||||
values = {
|
||||
key: value.strip().strip("\"'")
|
||||
for key, value in RPA_005_STEP_DELAY_KEY_RE.findall(_read_text(env_path))
|
||||
}
|
||||
self.assertEqual(
|
||||
values.get("STEP_DELAY_MIN"),
|
||||
"1.0",
|
||||
msg=_policy_msg(
|
||||
"POLICY-RPA-005",
|
||||
RPA_005_SOURCE,
|
||||
f"STEP_DELAY_MIN must default to 1.0, got {values.get('STEP_DELAY_MIN')!r}",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
values.get("STEP_DELAY_MAX"),
|
||||
"3.0",
|
||||
msg=_policy_msg(
|
||||
"POLICY-RPA-005",
|
||||
RPA_005_SOURCE,
|
||||
f"STEP_DELAY_MAX must default to 3.0, got {values.get('STEP_DELAY_MAX')!r}",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestPolicyRpa006(unittest.TestCase):
|
||||
def test_account_manager_lifecycle_doc_has_required_markers(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
path = os.path.join(skill_root, "development", "ACCOUNT_MANAGER.md")
|
||||
self.assertTrue(
|
||||
os.path.isfile(path),
|
||||
msg=_policy_msg("POLICY-RPA-006", RPA_006_SOURCE, "development/ACCOUNT_MANAGER.md missing"),
|
||||
)
|
||||
text = _read_text(path)
|
||||
missing = [marker for marker in RPA_006_REQUIRED_DOC_MARKERS if marker not in text]
|
||||
self.assertEqual(
|
||||
missing,
|
||||
[],
|
||||
msg=_policy_msg(
|
||||
"POLICY-RPA-006",
|
||||
RPA_006_SOURCE,
|
||||
"ACCOUNT_MANAGER.md missing markers: " + ", ".join(missing),
|
||||
),
|
||||
)
|
||||
|
||||
def test_example_account_id_selection_keeps_lease(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
for rel in RPA_006_EXAMPLE_CLIENTS:
|
||||
text = _read_text(os.path.join(skill_root, *rel.split("/")))
|
||||
self.assertIn("_pick_by_id_with_lease", text, msg=f"{rel} must lease explicit account ids")
|
||||
self.assertIn('"account",\n "pick-web"', text, msg=f"{rel} must use pick-web for lease")
|
||||
self.assertIn("lease_token", text, msg=f"{rel} must validate lease_token")
|
||||
self.assertNotIn(
|
||||
'data["lease_token"] = ""',
|
||||
text,
|
||||
msg=f"{rel} must not return an explicit account without a lease",
|
||||
)
|
||||
|
||||
|
||||
def _scan_sensitive_logging_assignments(skill_root: str) -> list[str]:
|
||||
offenders: list[str] = []
|
||||
for path in _walk_files(skill_root, "scripts", suffix=".py"):
|
||||
@@ -1211,7 +1333,7 @@ class TestPolicySkillAction004(unittest.TestCase):
|
||||
msg=_policy_msg(
|
||||
"POLICY-SKILL-ACTION-004",
|
||||
"tests/test_actions_manifest.py",
|
||||
"missing Schema 4×2 orthogonality matrix test",
|
||||
"missing Schema 6×2 orthogonality matrix test",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -249,6 +249,21 @@ class TestDocsStandards(unittest.TestCase):
|
||||
msg="RPA.md §0.2 must separate site-login from Profile requirement",
|
||||
)
|
||||
|
||||
def test_account_manager_standard_is_discoverable(self) -> None:
|
||||
account_text = self._read("development/ACCOUNT_MANAGER.md")
|
||||
for marker in (
|
||||
"一账号一",
|
||||
"profile_dir",
|
||||
"lease_token",
|
||||
"数据管理",
|
||||
"account-manager.db",
|
||||
"关闭浏览器并释放租约",
|
||||
"两个以上账号",
|
||||
):
|
||||
self.assertIn(marker, account_text)
|
||||
for rel in ("SKILL.md", "development/README.md", "development/RPA.md"):
|
||||
self.assertIn("ACCOUNT_MANAGER.md", self._read(rel), msg=f"{rel} must link account standard")
|
||||
|
||||
def test_simulator_example_readme_covers_async_and_account_manager(self) -> None:
|
||||
text = self._read("examples/simulator_browser_rpa/README.md")
|
||||
self.assertIn("async", text.lower())
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestEnvExampleVideoDefaults(unittest.TestCase):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
for key in (
|
||||
"OPENCLAW_" + "TEST_TARGET=mock",
|
||||
"OPENCLAW_" + "TEST_TARGET=real_" + "rpa",
|
||||
"OPENCLAW_RECORD_VIDEO=0",
|
||||
"OPENCLAW_ARTIFACTS_ON_FAILURE=1",
|
||||
"OPENCLAW_BROWSER_HEADLESS=0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# skill-template 工具
|
||||
|
||||
## 业务仓也可以直接 clone(来源 A)
|
||||
|
||||
若项目经理已在**公司自建 Gitea**([https://git.jc2009.com/](https://git.jc2009.com/),基于开源 Gitea 自建,**不是** Gitea 官网)开好业务技能仓库并授权,技术人员应直接:
|
||||
|
||||
```powershell
|
||||
git clone https://git.jc2009.com/<org>/<your-skill-slug>.git
|
||||
```
|
||||
|
||||
完整说明见 [`../development/DEVELOPMENT.md`](../development/DEVELOPMENT.md) §4「来源 A」。公开公共仓(含本模板、account-manager、platform-kit)登记见 [`../development/SHARED_REPOS.md`](../development/SHARED_REPOS.md)。下面脚手架用于**本地已有 skill-template、要新建尚未灌仓目录**的场景(来源 B)。
|
||||
|
||||
## 推荐:脚手架创建新技能
|
||||
|
||||
**不要**用资源管理器整文件夹复制 `skill-template`(会带走隐藏 `.git`,导致 push 串库)。
|
||||
|
||||
Reference in New Issue
Block a user