AI 控制台下新增子分栏「🧭 AI 建任务」:描述要做什么(例:建一个跑 2 小时的任务、自动刷
某 App、随机点赞),AI 用 de_* 工具自己在设备上探索(看屏/读元素树/点按验证),把走通的
路径写成一条任务草稿,经服务端校验后交人在步骤编辑器里核对/手改/试跑,保存才入库。
链路:POST /api/agent/run{mode:"designer", settings}
→ Agent 自探 → 本地工具 submit_task(draft)
→ core/task_draft 校验(失败把 errors 回灌模型让它改)
→ 只暂存(运行态 + app_meta.agent_task_draft,**不落库**)
→ SSE done{mode,draft,warnings} → 页面草稿预览 → openTaskModal(null, prefill) 预填
关键实现
- core/task_draft.py(新):把执行器的"静默跳过点"(未知 type/空 selector/空 children/
嵌套>5/节点>60/cron 非法/必填缺失)前移成显式 error——POST /api/jobs 对 params 是盲存的,
执行器又静默跳过错误步骤,没有这道闸门就是"任务建好了、跑起来什么都没做"。
归一化兜底任务名/target/schedule/retry/时长;页面填的设置以 overrides 优先于模型。
故意**不比执行器更严**:loop_mode 近义值归一(count→rounds)、缺 max_iterations 补默认 10
(执行器本来就默认)——实测卡太死会把一轮探索耗在改字段上。
有副作用的步骤(评论/发送/购买/删除…)只警告并把触发概率压到 30%(编辑器可改回)。
- mcp_agent/agent.py:双系统提示词(CHAT/DESIGNER)+ 平台级本地工具
(LOCAL_TOOL_SPECS,不进 MCP)+ 每工具调用上限 40 + designer 输出上限 8192 +
**json.loads 容错**(草稿被截断时给模型可读错误,而不是整轮失败)。
- web/agent_api.py:mode/settings 透传、submit_task 处理器(app_context 内校验+暂存)、
done 带 draft、GET /api/agent/task_draft{,+POST,/clear}(草稿走 app_meta,不新建表)。
- 前端:static/admin/taskgen.js + #agent-sub-taskgen 子面板(showSubTab 机制);
tasks.js 的 openTaskModal(jobId, prefill) + 信封归一化 + 唯一 draftKey;
agent.js 按 mode 门控(一个 run 只有一个事件队列,两个 EventSource 会互相瓜分事件)。
顺带修掉一个 chat 也踩的协议 bug:一轮里同时调 de_screenshot 与别的工具时,截图图像会被
插在两条 tool 消息之间 → 模型侧判"工具回应不足"直接 400。改为本轮 tool 消息发完再附图像,
_repair_tool_messages 也改成只数**连续**的 tool 消息。
真机实测(Redmi 22120RN86C,设置页):8 步探索(含 tap_text 验证)→ submit_task 一次通过 →
草稿 8 个顶层步骤(screen_on/open_app/wait_el/click/wait/key_event…)、max_duration 1800、
无 click_xy、3 条 evidence;页面恢复草稿 + 预填编辑器 + 提示块渲染均正常,无 JS 报错。
自测数据已清理(草稿已丢弃、未创建任何任务)。
文档:AI_TASK_GEN.md 状态改「P0 已实现」+ §10 实现记录(差异/护栏/未做项)、AI_CONSOLE.md
(子分栏、designer 分支、SSE done 负载、app_meta 键)、API.md、DATA_MODEL.md、ARCHITECTURE.md、
DEVELOPMENT.md(自测入口)、README.md 索引、backlog 勾掉 P0。
14 KiB
开发手册(DEVELOPMENT)
适用读者:所有参与
auto_control开发的人。动手前先读 §2 技术红线。 相关文档:ARCHITECTURE.md(架构,先懂再改)、DATA_MODEL.md(表结构)、doc/README.md(文档索引与维护约定)。
1. 开发流程
1.1 分支策略
| 分支 | 用途 |
|---|---|
dev |
开发分支,所有新功能/修复从它切出、合并回它 |
main |
生产分支,只放已确认的稳定版本 |
fix/xxx · feat/xxx · chore/xxx |
单个改动的临时分支(从 dev 切出) |
生产环境 = 部署机 192.168.20.220 的 /mnt/data/openstf/auto_control(python-app 容器)。
1.2 git 铁律
所有 git 操作都必须先经项目负责人明确确认,包括但不限于 commit / push(即使 push 到 dev 也要确认)/ merge / rebase / reset / branch -D。
标准流程:
① 本机新建分支 fix/xxx 或 feat/xxx(从 dev 切出)
② 分支上开发 + 自测 跑通本机(服务/接口/页面),能自测就别只靠"看代码没问题"
③ 交负责人确认 ★ 未经确认不合 dev
④ 合并到 dev 确认通过后(保持线性:rebase 后 ff-merge)
⑤ dev 整体就绪 功能齐全、验证完毕
⑥ 合并到 main ★ 负责人确认后
⑦ 生产 220 部署 git pull → docker restart python-app → 验证(见 DEPLOY.md §3)
每个改动单独分支 + 单独 commit,主题单一,便于评审与回退。不允许"开发完顺手 commit/push"。
2. 技术红线(违反会打断共享 adb transport 或造成生产事故)
| # | 红线 | 为什么 | 代码里的体现 |
|---|---|---|---|
| 1 | 绝不 adb kill-server |
会断掉所有设备的 adb transport,运行中任务全废 | core/adb_helper.py 只 connect 不 kill;Web 层硬拦截 kill-server/disconnect 字符串 |
| 2 | 绝不对 IP:5555 设备 adb disconnect |
该地址的 adb transport 是共享的 | adb_disconnect 全项目零调用方;STFDevice.release() 空实现 |
| 3 | 空闲设备扫描不主动 connect/disconnect | 避免扰动共享连接 | 前台扫描对空闲设备直接返回"空闲";设备发现用 socket 探测 |
| 4 | adb key 保持历史 key 不变 | 设备信任该 key,换 key 全部 unauthorized |
部署沿用 ~/.android/adbkey |
| 5 | 生产(220)默认只读 | 生产事故成本高 | 任何写操作(pull/重启/改文件)都需负责人确认 |
| 6 | 新增持久化表必须登记备份覆盖清单 | 漏登记 = 等于没备份 | core/system_backup.py 的 SUMMARY_TABLES + TABLE_LABELS,详见 DEPLOY.md §5.2 |
| 7 | 功能/配置/接口改动必须同步文档 | 文档落后会误导开发与运维 | 见 §6;索引 doc/README.md |
其它开发约束
web_server.py以debug=False运行:改core/、tasks/、templates/后必须重启;改前端 JS 后强刷浏览器- 任务参数放各自
tasks/<app>/顶部,不放config.py - 运行时数据不提交 git:
data/、logs/全是运行时产物 - 不要移除分页与错峰:监控/列表页已分页(100 台设备只渲染 10 行/页);任务批量触发已错峰(
_START_STAGGER_SEC) - 不要手工改库结构:走
SCHEMA_MIGRATIONS(模型表)或幂等原生建表
3. 本地开发
3.1 安装
python -m venv .venv
# Windows: .venv\Scripts\activate Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
确认 adb key(沿用既有 key,否则设备连不上):ls -la ~/.android/adbkey。
3.2 启动
python web_server.py
# 访问 http://localhost:18050/ admin / admin123
需要 AI 控制台 / MCP 时,另起一个进程:
MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \
python -m mcp_server.mcp_server # 默认 http://127.0.0.1:8033/mcp
(容器里由 scripts/start.sh 自动拉起,不需要手动。)
3.3 调试
| 目的 | 做法 |
|---|---|
| 看日志 | logs/core.log(adb/调度)、task.log(任务)、web.log(Web/AI)、action.log;或「日志」Tab |
| 看设备/任务状态 | 监控页;或 GET /api/status、GET /api/health |
| 后端逻辑验证 | 直接 python -c "…" 调用 core/、tasks/ 的函数(如构造 worker 检查参数合并) |
| 前端验证 | 无 npm/构建,改完强刷;浏览器控制台看报错 |
| 接口 500 巡检 | python scripts/regression_test.py(⚠️ 当前在 Windows 上会因 signal.alarm 报错,Linux/macOS 可用) |
| 停止设备/清异常 | 监控页「停止全部 / 停止选中 / 清除全部异常」 |
| AI 建任务(designer)验证 | AI 控制台 →「AI 建任务」选空闲设备跑一轮;脚本方式见 AI_TASK_GEN.md §10.5。草稿只落 app_meta.agent_task_draft(不是任务),验证完 POST /api/agent/task_draft/clear 清掉。注意它会真在设备上点按(导航类动作),且与聊天共用同一个运行槽 |
改了数据库/配置想复原:删
data/users.db*会丢数据,别这么干;用「系统 → 备份/导入」或先手工复制一份data/。
3.4 写测试的约定(重要)
本仓库没有单元测试框架,验证靠"跑起来 + 真实调用"。写验证脚本时必须:
- 不要碰真实数据:需要会话/任务/分组时自建(如
POST /api/agent/conversations建专属会话),绝不要依赖"当前选中项",也不要删自己没建的东西 - 改配置前后都要回读校验:改前 GET 存原值,收尾写回后再 GET 比对,不一致要显式报错
- 临时数据用完即删,并核对"集合已复原"
教训:曾用浏览器脚本跑 AI 控制台冒烟,脚本清空
localStorage后前端自动选中了用户最近的会话,收尾的"删除测试会话"把用户真实会话删了;同一脚本还把 AI 配置改成了假值。恢复手段见 DATA_MODEL.md §7 与 git 历史。
4. 配置速查
4.1 配置文件与优先级
config.py:程序级常量(端口、路径、USB 远程 adb 等),改它要重启.env(项目根,不入 git):密钥与可覆盖配置,逐行解析 +os.environ.setdefault(真实环境变量优先);数据库目标(DEPLOY_ENV+DB_*)也在这里app_meta(数据库 KV):运行时可改的配置(AI 配置、设备发现参数),在界面上改
4.2 config.py 常量
| 常量 | 默认值 | 来源 | 说明 |
|---|---|---|---|
ADB_PATH |
bin/adb/adb(.exe) |
按平台自动 | 不可用 env 覆盖 |
WEB_HOST / WEB_PORT |
0.0.0.0 / 18050 |
硬编码 | 18050 避开 Windows 动态端口段 |
DATA_DIR / APK_DIR |
data/ / data/apks/ |
代码计算 | |
BACKUP_DIR / RESTORE_STAGING_DIR / RESTORE_PENDING_DIR |
data/backups / data/restore_staging / data/restore_pending |
代码计算 | 备份相关 |
DEPLOY_ENV |
dev |
.env 可覆盖 |
声明这套配置连哪个环境的库;与库名绑定(dev→auto_control_dev,prod→auto_control),不符拒绝启动 |
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME |
空 / 3306 / 空 / 空 / 空 |
.env 可覆盖 |
MySQL 目标;DB_HOST 为空则回退 SQLite(生产禁止静默回退,需 DB_ALLOW_SQLITE_FALLBACK=1) |
DB_CHARSET / DB_COLLATION |
utf8mb4 / utf8mb4_bin |
.env 可覆盖 |
排序规则必须用 _bin(逐码点比较,等价 SQLite 的大小写敏感语义) |
DATABASE_URL |
空 | .env 可覆盖 |
完整连接串,优先级最高;脚本临时指向别的库时用 |
DB_ALLOW_ENV_MISMATCH / DB_ALLOW_SQLITE_FALLBACK |
0 |
.env |
逃生阀,见 DEPLOY.md |
USB_ADB_HOST / USB_ADB_PORT |
100.100.10.1 / 5037 |
.env 可覆盖 |
USB 设备所在部署机的远程 adb server |
TAILSCALE_API_KEY / TAILSCALE_TAILNET |
空 | .env |
Tailscale 管理功能 |
DISCOVERY_PORT / DISCOVERY_INTERVAL |
5555 / 60 |
.env 可覆盖 |
设备发现(网段在工具页配置,存 app_meta) |
DISCOVERY_SUBNETS |
局域网 + Tailscale 网段 | 硬编码列表(当前无 env 支持) | 默认扫描网段 |
STF_* |
空 | .env |
已废弃,仅历史保留,代码不再使用 |
4.3 进程读取的环境变量(不在 config.py)
| 变量 | 默认 | 谁读 | 说明 |
|---|---|---|---|
WEB_SECRET_KEY |
未配置则随机 | web_server.py |
会话密钥,生产必须固定 |
DISABLE_SCHEDULER |
未设 | core/task_manager.py |
设任意值则不启动 cron 调度器(测试用) |
DATA_BACKUP_DIR / DATA_RESTORE_STAGING_DIR / DATA_RESTORE_PENDING_DIR |
data/backups 等 |
config.py |
备份/恢复目录改到别处;自动化测试必须指到临时目录(否则测试造的待生效恢复任务会在下次重启被当成用户操作消费) |
MCP_ENABLED |
1 |
scripts/start.sh |
=0 则不后台拉起 MCP |
MCP_ALLOW_WRITE |
0(脚本内强制 1) |
mcp_server/config.py |
写操作总开关 |
MCP_PLATFORM_URL / _USER / _PASS |
http://127.0.0.1:18050 / admin / 空 |
mcp_server/config.py |
登录平台的凭据(改过 admin 密码要同步) |
MCP_ALLOWED_SERIALS |
空=不限 | 同上 | 逗号分隔白名单(语义缺口见 backlog) |
MCP_HTTP_HOST / _PORT |
0.0.0.0 / 8033 |
同上 | |
MCP_SCREENSHOT_WIDTH / MCP_JPEG_QUALITY |
540 / 70 |
同上 | 返回给模型的截图层参数 |
MCP_AUDIT_FILE |
/var/log/mcp/audit.log(脚本兜底 /tmp/mcp_audit.log) |
同上 | 审计日志路径 |
AGENT_API_BASE / AGENT_MODEL / AGENT_API_KEY / DEEPSEEK_API_KEY |
DeepSeek 默认地址 / 默认模型 / 空 | mcp_agent/config.py |
仅 CLI 用;Web 端 AI 配置优先读数据库 app_meta |
AGENT_MCP_URL / AGENT_MAX_STEPS / AGENT_TIMEOUT / AGENT_LANG |
http://127.0.0.1:8033/mcp / 40 / 120 / zh |
同上 | |
ANDROID_ADB_SERVER_ADDRESS / _HOST / _PORT |
未设 | adb 客户端自身(非本项目代码) | 把 adb 调用指向远程 server;两套变量名都要设 |
mcp_server/config.py与mcp_agent/config.py不读.env(只读进程环境变量),与根config.py的行为不同。
5. 常见开发任务
每一项的"改哪里"清单也见 ARCHITECTURE.md §10。
5.1 新增 HTTP 接口
- 在对应功能域的
web/xxx_api.py加@bp.route(...)+ 鉴权装饰器(@login_required/@perm_required(PERM_X)/@admin_required) - 新蓝图需在
web/__init__.py的register_blueprints里注册 - 更新 API.md(路由索引表 + 详细小节)
5.2 新增数据库字段/表
- 模型改
core/models.py;新表create_all()会建 - 老库要在
SCHEMA_MIGRATIONS里加迁移(版本号递增 + SQL) - 新增表:登记进
core/system_backup.py的SUMMARY_TABLES+TABLE_LABELS(红线) - 更新 DATA_MODEL.md 与 DEPLOY.md §5.2
5.3 修改前端
| 改什么 | 文件 |
|---|---|
| 页面结构 / 样式 / 引入脚本 | templates/admin/monitor.html |
| 公共工具(API/权限/Toast/Tab) | static/admin/base.js |
| 列表分页排序 | static/admin/list.js |
| 各功能域逻辑 | static/admin/{monitor,editor,tasks,tools,apps,admin,agent,system,markdown}.js |
新增 JS 模块:建文件 → 在 monitor.html 里按依赖顺序加 <script src> → 更新 ARCHITECTURE.md §6.3 与本文 §5.3。
5.4 新增步骤类型 / 任务类型
见 TASK_DEV.md(步骤类型要同时改后端 STEP_TYPES 与前端 STEP_LIB)。
5.5 新增 MCP 工具
在 mcp_server/mcp_server.py 加 @mcp.tool() 函数;写操作必须挂门控(_check_write → _check_serial → _ensure_device_free);更新 MCP.md。
5.6 新增常驻线程
参考 core/device_discovery.py 的 init_app(app) / shutdown() 模式;更新 ARCHITECTURE.md §3。
6. 文档同步(红线)
任何功能 / 配置 / 接口 / 表结构的增删改,都要在同一个 commit 里更新对应文档。
| 改动类型 | 必须更新 |
|---|---|
| HTTP 接口 | API.md |
| 表结构 / 迁移 | DATA_MODEL.md(+ 备份覆盖清单) |
config.py / .env 键 |
本文 §4 + DEPLOY.md + .env.example |
| 页面 Tab / 子分栏 / 前端模块 | ARCHITECTURE.md §6 + 本文 §5.3 |
| 任务类型 / 步骤 schema | TASK_DEV.md |
| 常驻线程 / 装配顺序 | ARCHITECTURE.md §2-3 |
| MCP 工具 | MCP.md + MCP_DESIGN.md |
| 对外接入约定 | staffdeck/KNOWLEDGE_BASE.md |
| 暂缓项 / 已知问题 | backlog/TODO.md |
新增文档时:登记进 doc/README.md §1 与根 README 的文档索引。 历史文档(STF_REMOVAL.md)只增不改。
7. 常见坑速查
| 坑 | 说明 |
|---|---|
| 跨线程访问 DB | 必须自推 app context(with app.app_context()) |
| 改了不生效 | core/、tasks/、templates/ 改动要重启;JS 要强刷 |
| Windows adb 输出 | 不能用 text=True(非 GBK 字节会崩),要收 bytes 再解码 |
| u2 卡死 | u2.connect / d.info 可能永久 hang,必须加超时 |
| 抢占死锁 | 不能在 TaskManager._lock 内调 stop_device |
| 删除不生效 | 删任务/分组必须显式删行,否则重启会"复活" |
前端写了 data-perm 仍可见 |
它只在 loadMe() 时求值一次;改权限后需刷新 |
| 新步骤没生效 | 后端 STEP_TYPES 与前端 STEP_LIB 要同时改 |
| 备份导入后没变化 | 必须重启服务 |
.env 不生效 |
setdefault 语义:已存在的环境变量优先,检查是否被系统环境覆盖 |
更完整的"代码里写明的坑"见 ARCHITECTURE.md §9.2。