chore: 删除抖音养号任务类型,平台只保留 generic_steps
tasks/douyin/(task_type=douyin_nurture)整体删除,唯一任务类型是 generic_steps。 所有默认值/文档/接口示例同步改成 generic_steps: - tasks/:删 douyin 包;__init__ 只注册 generic;base.py/generic 注释改为照 generic 抄 - 默认值:core/models.py(列默认 + 旧 JSON 迁移默认)、core/task_manager.py TaskJob、 web/tasks_api.py 建任务默认、static/admin/tasks.js 新建任务默认 - core/task_manager.py:去掉 douyin 专属的"清理废弃 comment 参数"迁移块,改为**启动时告警** 仍残留已删类型的任务(只告警不改数据);run_job_now 对已删类型直接返回明确错误, 不再"报已触发、线程里静默失败" - 清理残留:douyin_running 状态位(无任何读取方)、core/__init__、core/actions/*、 core/logger.py 注释里的抖音示例 - 文档:README(特性/目录树/类型表/参数表/示例)、TASK_DEV(目录树/注册说明/模板引用)、 ARCHITECTURE(注册示例/action 注册表示例)、API.md(task_types 与任务 JSON 示例)、 AI_TASK_GEN(P1 去掉 douyin 预设)、DEVELOPMENT - 注:示例里"抖音"作为**App 名**(MCP 列应用、AI 建任务的需求举例)保留,与任务类型无关 自测(全部通过):类型列表只剩 generic_steps;建任务不传类型默认 generic_steps;传 douyin_nurture 被 400 拒;库里塞残留旧类型任务 → 启动日志告警 + 执行返回明确错误 + 不自动删用户数据;前端新建任务下拉 1 项且默认选中、界面建任务成功;监控页卡片两个按钮 + 覆盖设备正常。临时任务/数据验完已清理,任务集合复原。
This commit is contained in:
+4
-3
@@ -23,7 +23,8 @@
|
||||
|
||||
- **入口**:AI 控制台加「聊天 / AI 建任务」模式开关,复用会话/SSE/设备选择/停止/刷新恢复骨架。不新建顶级 Tab。
|
||||
- **产出方式**:**AI 直接撰写编辑器的步骤 JSON**(非操作轨迹翻译)。自探信息只作上下文与审计;探索中的误点/多余截图不会混入任务。
|
||||
- **任务类型**:P0 **只产 `generic_steps`**(通用步骤)。`douyin_nurture` 预设参数生成放 P1。
|
||||
- **任务类型**:**只产 `generic_steps`**(通用步骤)——平台当前唯一的任务类型
|
||||
(原 `douyin_nurture` 预设参数生成已随该类型删除,不再做)。
|
||||
- **定位原则:全部基于 UI 树**:
|
||||
- 模型定位/点击一律走 `de_ui_tree` 拿到的元素(`text`/`id`/`description`/`text_contains`),多实例用 index,或后端 `uiauto_helper` 生成的 `//*[@resource-id=..][k]` XPath;
|
||||
- `de_tap_element` 确认能命中后才写进 `click` 步骤;
|
||||
@@ -134,8 +135,8 @@
|
||||
- 尽量不写死坐标;P0 默认禁 `click_xy`,产物以树元素定位为主。
|
||||
|
||||
## 6. 里程碑
|
||||
- **P0(本设计主体)**:designer 模式 → 自探(UI 树定位)→ 直接撰写 generic_steps draft → 服务端 schema 校验 → 前端步骤编辑器预填确认保存。验收:一句话在真实设备上生成一条可调度的 generic_steps,步骤全部来自 UI 树且编辑器可打开。
|
||||
- **P1**:`douyin_nurture` 参数预设生成(照 default_params 结构);模板沉淀:把 draft+需求写入 `agent_experience`(新列存结构化 steps 或 JSON),相似需求注入参考;整链「演示试跑」(把 steps 在设备上以受控方式跑一遍并截图回报,需新增端点,复刻 `device_busy` 拒绝语义)。
|
||||
- **P0(本设计主体)**:designer 模式 → 自探(UI 树定位)→ 直接撰写 generic_steps draft → 服务端 schema 校验 → 前端步骤编辑器预填确认保存。验收:一句话在真实设备上生成一条可调度的 generic_steps,步骤全部来自 UI 树且编辑器可打开。(平台只剩 generic_steps 一种任务类型,与原规划一致)
|
||||
- **P1**:模板沉淀:把 draft+需求写入 `agent_experience`(新列存结构化 steps 或 JSON),相似需求注入参考;整链「演示试跑」(把 steps 在设备上以受控方式跑一遍并截图回报,需新增端点,复刻 `device_busy` 拒绝语义)。
|
||||
- **进展(2026-09-10)**:动作级沉淀已落地——`agent_action` 表 + 从**成功步骤**蒸馏"命名动作"(steps 用编辑器 schema、带元素定位、禁坐标)+ 执行前按名/别名召回注入(`web/agent_api.py`)。AI 建任务可直接把这些动作当作 generic_steps 的**预制件**复用。
|
||||
- **P2**:自定义动作支持(内联展开成 group,或新增 `action_ref` 节点 + 执行器/编辑器同步);多设备并行;成本与 token 控制。
|
||||
|
||||
|
||||
+7
-13
@@ -141,7 +141,7 @@
|
||||
"counts": {"total": 8, "running": 1, "done": 5, "error": 1, "failed": 1, "idle": 0},
|
||||
"errors": [
|
||||
{"serial": "192.168.1.100:5555", "model": "Pixel 6", "status": "failed",
|
||||
"last_error": "重试3次失败", "task": "抖音养号", "attempt": 3, "updated": 1700000000.0}
|
||||
"last_error": "重试3次失败", "task": "刷视频-上午", "attempt": 3, "updated": 1700000000.0}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -218,12 +218,6 @@
|
||||
{
|
||||
"ok": true,
|
||||
"task_types": [
|
||||
{
|
||||
"task_type": "douyin_nurture",
|
||||
"name": "抖音养号",
|
||||
"description": "自动观看抖音视频...",
|
||||
"default_params": {"watch_count": 80, "...": "..."}
|
||||
},
|
||||
{
|
||||
"task_type": "generic_steps",
|
||||
"name": "通用步骤",
|
||||
@@ -265,7 +259,7 @@
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"jobs": [{"id": "abc123", "name": "抖音养号", "task_type": "douyin_nurture",
|
||||
"jobs": [{"id": "abc123", "name": "刷视频-上午", "task_type": "generic_steps",
|
||||
"next_run": "2026-08-12 09:00",
|
||||
"coverage": {"mode": "all", "total": 3,
|
||||
"serials": ["192.168.20.206:5555", "..."]},
|
||||
@@ -296,10 +290,10 @@
|
||||
**请求**(JSON):
|
||||
```json
|
||||
{
|
||||
"name": "抖音养号",
|
||||
"task_type": "douyin_nurture",
|
||||
"name": "刷视频-上午",
|
||||
"task_type": "generic_steps",
|
||||
"target": {"mode": "all"},
|
||||
"params": {"watch_count": 50, "actions": {"like": {"params": {"rate": 0.5}}}},
|
||||
"params": {"max_duration": 0, "steps": [{"type": "open_app", "params": {"package": "com.ss.android.ugc.aweme"}}]},
|
||||
"schedule": {"mode": "cron", "cron": "0 */2 * * *"},
|
||||
"retry": {"max_attempts": 3, "delay": 60},
|
||||
"enabled": true
|
||||
@@ -327,7 +321,7 @@
|
||||
|
||||
**请求**(JSON):
|
||||
```json
|
||||
{"params": {"watch_count": 100}}
|
||||
{"params": {"max_duration": 1800}}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
@@ -354,7 +348,7 @@
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{"ok": true, "msg": "任务 抖音养号 已触发"}
|
||||
{"ok": true, "msg": "任务 刷视频-上午 已触发"}
|
||||
```
|
||||
|
||||
### POST /api/jobs/<job_id>/toggle
|
||||
|
||||
+7
-5
@@ -275,8 +275,7 @@ APK 上传/解析/批量安装。
|
||||
```
|
||||
tasks/__init__.py
|
||||
├── from .base import BaseTask, register_task, list_task_types, get_task_class
|
||||
├── from .douyin import task # 触发 @register_task
|
||||
└── from .generic import task # 触发 @register_task
|
||||
└── from .generic import task # 触发 @register_task(当前唯一任务类型)
|
||||
```
|
||||
|
||||
`@register_task` 装饰器将 Task 类注册到全局 `_TASK_TYPES` 字典,key 为 `task_type` 字符串。
|
||||
@@ -301,9 +300,12 @@ Job 下发时只传需要覆盖的字段,调度器做三层合并:
|
||||
每个 App 有独立的 Action 注册表(`create_action_registry()`),互不污染。
|
||||
|
||||
```
|
||||
core/actions/base.py — BaseAction 全局基类 + should_trigger + register_action
|
||||
tasks/douyin/actions/base.py — ACTIONS = create_action_registry() + list/get 函数
|
||||
tasks/douyin/actions/like.py — @register_action(ACTIONS) LikeAction
|
||||
core/actions/base.py — BaseAction 全局基类 + should_trigger + register_action
|
||||
tasks/<app>/actions/base.py — ACTIONS = create_action_registry() + list/get 函数
|
||||
tasks/<app>/actions/xxx.py — @register_action(ACTIONS) XxxAction
|
||||
|
||||
> 当前没有 App 专属任务包(只剩 `tasks/generic/`,步骤全走通用 STEP_TYPES,不建专属 action 表);
|
||||
> 新增专属任务类型时按上面三行建 `tasks/<app>/actions/`。
|
||||
```
|
||||
|
||||
**循环导入坑**:`actions/__init__.py` 必须先 `from .base import ACTIONS`,再 `from . import like`。
|
||||
|
||||
+4
-2
@@ -176,9 +176,11 @@ with sync_playwright() as p:
|
||||
|
||||
## 5. 常见开发任务
|
||||
|
||||
### 5.1 新增 App 任务
|
||||
### 5.1 新增 App 任务类型
|
||||
|
||||
参照 `tasks/douyin/` 结构,详见 **[doc/TASK_DEV.md](TASK_DEV.md)**(6 步模板)。
|
||||
参照 `tasks/generic/` 结构,详见 **[doc/TASK_DEV.md](TASK_DEV.md)**(6 步模板)。
|
||||
注意:当前平台**只保留 `generic_steps` 一种任务类型**;大多数 App 操作直接用步骤编辑器
|
||||
编排即可,不必新增任务类型。
|
||||
|
||||
```
|
||||
tasks/<app>/
|
||||
|
||||
+12
-18
@@ -62,18 +62,13 @@ platform-tools/
|
||||
│ ├── __init__.py # create_action_registry / register_action / should_trigger
|
||||
│ └── base.py # BaseAction 全局基类
|
||||
├── tasks/ # 任务定义层
|
||||
│ ├── __init__.py # 聚合导出 + import 各任务包触发注册(from .douyin/.generic import task)
|
||||
│ ├── __init__.py # 聚合导出 + import 各任务包触发注册(from .generic import task)
|
||||
│ ├── base.py # BaseTask 基类 + _TASK_TYPES + register_task/list_task_types/get_task_class
|
||||
│ ├── douyin/ # 抖音养号(task_type=douyin_nurture,示例)
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── task.py # DEFAULT_PARAMS + Worker + Task + @register_task
|
||||
│ │ └── actions/
|
||||
│ │ ├── __init__.py # 先 from .base import ACTIONS,再 from . import like
|
||||
│ │ ├── base.py # ACTIONS = create_action_registry()
|
||||
│ │ └── like.py # @register_action(ACTIONS) LikeAction
|
||||
│ └── generic/ # 通用步骤任务(task_type=generic_steps,步骤编辑器编排)
|
||||
│ └── generic/ # 通用步骤任务(task_type=generic_steps,当前唯一任务类型)
|
||||
│ ├── __init__.py
|
||||
│ └── task.py # STEP_TYPES + Worker + Task(按 steps 顺序执行)+ test_step
|
||||
│
|
||||
│ # 新增专属任务类型时:按同样结构建 <app>/(见 §6「新增任务类型」的骨架)
|
||||
├── templates/admin/
|
||||
│ ├── monitor.html # 单页应用(7 Tab + 页内子分栏,纯前端渲染)
|
||||
│ ├── login.html # 登录页
|
||||
@@ -130,9 +125,9 @@ platform-tools/
|
||||
|
||||
### 2.1 TaskType — 任务类型
|
||||
|
||||
一个 `TaskType` 描述"做什么"(例如抖音养号、通用步骤),由 `Task` 子类 + `Worker` 子类 + `DEFAULT_PARAMS` 组成。每个 `TaskType` 注册到全局 `_TASK_TYPES` 字典,key 为任务类型字符串,value 为 `Task` 类。
|
||||
一个 `TaskType` 描述"做什么"(例如通用步骤、某个 App 的专属养号流程),由 `Task` 子类 + `Worker` 子类 + `DEFAULT_PARAMS` 组成。每个 `TaskType` 注册到全局 `_TASK_TYPES` 字典,key 为任务类型字符串,value 为 `Task` 类。
|
||||
|
||||
> **注册位置**:`_TASK_TYPES` 及 `register_task` / `list_task_types` / `get_task_class` **定义在 `tasks/base.py`**;`tasks/__init__.py` 只做两件事——`from .base import ...` 聚合导出,以及 `from .douyin/.generic import task` 触发各任务包注册。**当前已注册的 task_type 仅 `douyin_nurture` 与 `generic_steps`**,前端"新建任务"下拉来自 `GET /api/task_types`。新增 task_type 需在 `tasks/` 下建子包、用 `@register_task` 装饰,并在 `tasks/__init__.py` 加 `from .xxx import task`,然后**重启 web** 生效。
|
||||
> **注册位置**:`_TASK_TYPES` 及 `register_task` / `list_task_types` / `get_task_class` **定义在 `tasks/base.py`**;`tasks/__init__.py` 只做两件事——`from .base import ...` 聚合导出,以及 `from .generic import task` 触发各任务包注册。**当前已注册的 task_type 只有 `generic_steps`(通用步骤)**,前端"新建任务"下拉来自 `GET /api/task_types`。新增 task_type 需在 `tasks/` 下建子包、用 `@register_task` 装饰,并在 `tasks/__init__.py` 加 `from .xxx import task`,然后**重启 web** 生效。
|
||||
|
||||
```python
|
||||
# tasks/base.py
|
||||
@@ -151,7 +146,7 @@ def get_task_class(task_type):
|
||||
|
||||
`TaskJob` 是"什么时候、在哪些设备上、用什么参数执行某个 TaskType"的持久化计划,存于 SQLite(`data/users.db` 的 `task_job` 表)。包含字段:
|
||||
|
||||
- `task_type` — 任务类型(对应 `_TASK_TYPES` 的 key;当前仅 `douyin_nurture` / `generic_steps`)
|
||||
- `task_type` — 任务类型(对应 `_TASK_TYPES` 的 key;当前仅 `generic_steps`,新建任务不传时默认就是它)
|
||||
- `target` — 目标设备:`{"mode": "all"|"group"|"serial", "group_name": "", "serial": ""}`,默认 `{"mode":"all"}`
|
||||
- `params` — 任务参数(与 `DEFAULT_PARAMS` 深合并)。含两个隐藏开关:
|
||||
- `skip_offline`(默认 **true**)— serial/group 模式先跳过本机 adb 不可达(离线)的设备
|
||||
@@ -198,11 +193,11 @@ Worker 通过 `self.set_progress(**fields)` 上报进度,前端统一解析展
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
# 抖音任务(有数量限制)
|
||||
# 有数量限制的任务
|
||||
self.set_progress(done=5, total=80, unit="视频",
|
||||
action_counts={"like": 3}, elapsed=120)
|
||||
|
||||
# 抖音任务(仅时长限制,无数量)
|
||||
# 仅时长限制(无数量)的任务
|
||||
self.set_progress(done=5, total=0, unit="视频",
|
||||
action_counts={"like": 3}, elapsed=120)
|
||||
|
||||
@@ -346,7 +341,7 @@ adb + u2 **只读连接**试执行单步并验证选择器,返回 `result` = *
|
||||
|
||||
> **签名提醒**:以下模板的 `Worker.__init__` / `Task.create_worker` **已去掉 `stf_client` / `stf`
|
||||
> 参数**——现行签名是 `BaseWorker.__init__(self, serial, params=None, daemon=True)`、
|
||||
> `Task.create_worker(self, serial, params)`(对照 `tasks/douyin/task.py`),新增任务照此抄,
|
||||
> `Task.create_worker(self, serial, params)`(对照 `tasks/generic/task.py`),新增任务照此抄,
|
||||
> 不要再带 stf 形参。
|
||||
|
||||
### 步骤 1:在 `tasks/` 下建 `kuaishou/` 子包
|
||||
@@ -629,7 +624,6 @@ from . import task # noqa: F401 触发 @register_task 注册
|
||||
```python
|
||||
# tasks/__init__.py
|
||||
from .base import BaseTask, register_task, list_task_types, get_task_class
|
||||
from .douyin import task # noqa: F401
|
||||
from .generic import task # noqa: F401
|
||||
from .kuaishou import task # noqa: F401 ← 新增这一行
|
||||
```
|
||||
@@ -1029,7 +1023,7 @@ from .base import ACTIONS, ... # 1. 先建注册表
|
||||
from . import like # 2. 再导入各 action,触发 @register_action
|
||||
```
|
||||
|
||||
`tasks/__init__.py` 同理:先 `from .base import BaseTask`,再 `from . import douyin`、`from . import generic`。
|
||||
`tasks/__init__.py` 同理:先 `from .base import BaseTask`,再 `from . import generic`(新增类型往下加)。
|
||||
|
||||
### 10.2 中文输入
|
||||
|
||||
@@ -1130,7 +1124,7 @@ self.set_progress(videos_watched=5, round_idx=3)
|
||||
- [ ] 长循环内周期性调用 `self.heartbeat()`
|
||||
- [ ] `tasks/<app>/actions/__init__.py` 先 `from .base import ACTIONS` 再导入各 action
|
||||
- [ ] 每个 Action 有 `action_type` / `name` / `default_params` / `execute`,返回 `True/False`
|
||||
- [ ] `Task.create_worker` 做 actions 参数级深合并(照抄抖音模板)
|
||||
- [ ] `Task.create_worker` 做 actions 参数级深合并(照抄 `tasks/generic/task.py`)
|
||||
- [ ] `tasks/__init__.py` 已 `from .<app> import task` 注册
|
||||
- [ ] 日志用 `get_logger("task.<app>")` / `get_logger("action.<app>.<name>")`,无 `print`
|
||||
- [ ] 定位元素优先 `description` / `descriptionContains`,resourceId 多候选,xpath 用相对定位
|
||||
|
||||
Reference in New Issue
Block a user