# 定时任务与 Element 投递改动说明及内网发版文档 本文档对应本次新增的「类 Hermes 任务调度」能力,包含两部分: - 修改说明:说明改了哪些位置,以及为什么这样设计。 - 发版说明:基于现有 `start_backend.0509.sh` 的离线部署方式,说明如何重新发版到内网。 ## 1. 本次功能目标 本次改动解决三个核心场景: 1. 用户可以在任意智能体聊天中创建定时任务,例如「每天 9 点生成 AI 发展日报」。 2. 定时任务执行结果统一发送到当前用户唯一的「定时任务助手」聊天框,避免和普通聊天混在一起。 3. 用户可以在任务管理页面查看、修改、暂停、恢复、删除、立即触发自己的全部定时任务,并可以填写 Element/Matrix 用户 ID,让任务结果同步投递到 Element 私聊。 ## 2. 后端修改说明 ### 2.1 新增定时任务 API 位置: - `app/gateway/routers/scheduled_tasks.py` - `app/gateway/app.py` 作用: - 新增 `/api/scheduled-tasks` API。 - 支持获取定时任务专属会话、任务列表、创建任务、修改任务、删除任务、暂停/恢复、立即触发。 - 支持 `/api/scheduled-tasks/delivery-profile` 保存当前用户的 Element 用户 ID。 为什么这样做: - 页面上的任务管理能力需要稳定的 CRUD API。 - Element 用户 ID 是用户级配置,不应该写在系统环境变量里,所以单独做成用户投递配置。 - 每个用户只保留一个定时任务会话,因此提供 `/thread` 接口让前端直接进入该会话。 ### 2.2 新增定时任务持久化 位置: - `packages/harness/deerflow/persistence/scheduled_tasks/model.py` - `packages/harness/deerflow/persistence/scheduled_tasks/base.py` - `packages/harness/deerflow/persistence/scheduled_tasks/sql.py` - `packages/harness/deerflow/persistence/scheduled_tasks/memory.py` - `packages/harness/deerflow/persistence/scheduled_tasks/__init__.py` - `packages/harness/deerflow/persistence/models/__init__.py` - `app/gateway/deps.py` 新增数据表: - `scheduler_threads`:记录每个用户唯一的定时任务聊天线程。 - `scheduled_tasks`:记录定时任务本体,包括 prompt、cron、时区、下次执行时间、执行智能体上下文。 - `scheduled_task_runs`:记录每次任务执行,避免重复执行同一计划时间。 - `scheduled_task_delivery_profiles`:记录用户自己的 Element 用户 ID。 为什么这样做: - 定时任务需要跨进程、跨重启保存,所以不能只存在内存里。 - 单独记录 run 可以判断任务是否成功、失败,也可以防止同一计划时间被重复执行。 - Element 投递目标是用户个人信息,和任务本体分开更清晰。 ### 2.3 新增调度运行服务 位置: - `packages/harness/deerflow/runtime/scheduler/service.py` - `packages/harness/deerflow/runtime/scheduler/registry.py` - `packages/harness/deerflow/runtime/scheduler/__init__.py` - `app/gateway/app.py` 作用: - 后端启动时创建 `ScheduledTaskService`。 - 服务每 30 秒扫描一次到期任务。 - 到期后把任务 prompt 投递到该用户的定时任务专属聊天线程。 - 任务运行时会恢复创建任务时的智能体上下文,例如自定义智能体名、模型、思考模式等。 - 定时任务执行期间禁用再次创建定时任务,避免任务递归创建任务。 为什么这样做: - 用户希望「可以在智能体聊天中创建任务,并复用智能体技能生成日报」,因此任务必须保存创建时的 agent/context。 - 所有结果集中到专属聊天线程,可以满足「任务结果从定时任务聊天框发给我」的交互要求。 - 当前 RunManager 对同一 thread 的并发运行有限制,所以调度服务内部加了运行锁,避免多个报告同时写入同一个定时任务会话导致冲突。 ### 2.4 新增智能体工具 `scheduled_task` 位置: - `packages/harness/deerflow/tools/builtins/scheduled_task_tool.py` - `packages/harness/deerflow/tools/builtins/__init__.py` - `packages/harness/deerflow/tools/tools.py` - `packages/harness/deerflow/agents/lead_agent/agent.py` 作用: - 智能体可以通过工具创建、查询、更新、删除、暂停、恢复、触发定时任务。 - 用户不需要进入「定时任务聊天框」也能创建任务,可以直接在普通聊天或智能体聊天中说需求。 - 定时任务执行时不会暴露 `scheduled_task` 工具,避免任务运行中再次调度自身。 为什么这样做: - 这是满足「我可能会使用智能体中的某一些技能去实现日报生成」的关键。 - 如果只在任务管理页面创建任务,就无法自然复用智能体上下文和工具能力。 ### 2.5 新增 Element/Matrix 私聊投递 位置: - `packages/harness/deerflow/runtime/scheduler/element.py` - `packages/harness/deerflow/runtime/scheduler/service.py` - `.env` 作用: - 定时任务成功后,后端会读取当前用户保存的 `element_user_id`。 - 如果系统启用了 Element 配置,则机器人会创建 Matrix DM 房间并发送文本结果。 - 支持三种机器人认证方式: - `ELEMENT_ACCESS_TOKEN`:直接使用已有 token,优先级最高。 - `ELEMENT_AUTO_REGISTER=true`:后端自动注册机器人用户,并缓存 token。 - `ELEMENT_BOT_USERNAME` + `ELEMENT_BOT_PASSWORD`:使用已有机器人账号登录。 为什么这样做: - `default_room_id` 不适合这个需求,因为你要发到每个用户自己的私聊。 - 用户在任务管理页面填写自己的 Element 用户 ID,后端机器人按用户发 DM,更符合多用户场景。 - 自动注册适合内网环境快速启用;账号密码和 token 方式适合更严格的运维管理。 ### 2.6 新增运行依赖 位置: - `pyproject.toml` 新增: ```toml croniter>=6.2.2 ``` 作用: - 用于根据 cron 表达式计算下一次执行时间。 发版注意: - 本次可以继续使用 `deerflow-backend-offline:20260505` 原镜像。 - 因为新增了 `croniter`,启动脚本会在容器启动时先检查该依赖;如果缺失,则从内网 Python 私服安装。 - 只要内网容器能访问 Python 私服,就不需要重新构建或导入新镜像。 ## 3. 前端修改说明 ### 3.1 新增定时任务 API 与 hooks 位置: - `frontend-web/src/core/scheduled-tasks/api.ts` - `frontend-web/src/core/scheduled-tasks/hooks.ts` - `frontend-web/src/core/scheduled-tasks/types.ts` - `frontend-web/src/core/scheduled-tasks/index.ts` 作用: - 封装 `/api/scheduled-tasks` 后端接口。 - 页面通过 React Query 自动加载任务列表和 Element 投递配置。 为什么这样做: - 和现有前端数据请求方式保持一致。 - 任务列表、任务操作、Element 配置保存都能统一刷新缓存。 ### 3.2 新增定时任务聊天页与任务管理页 位置: - `frontend-web/src/pages/ScheduledChatPage.tsx` - `frontend-web/src/pages/ScheduledTasksPage.tsx` - `frontend-web/src/App.tsx` 作用: - `/workspace/scheduled`:进入当前用户唯一的定时任务聊天框。 - `/workspace/scheduled/tasks`:查看、编辑、删除、暂停、恢复、立即运行任务;填写 Element 用户 ID。 为什么这样做: - 聊天结果和任务管理是两个不同工作流:一个看结果,一个管理规则。 - 两个入口都挂在 workspace 下,用户不会离开原来的工作区。 ### 3.3 左侧栏入口与普通会话过滤 位置: - `frontend-web/src/components/workspace/workspace-nav-chat-list.tsx` - `frontend-web/src/components/workspace/recent-chat-list.tsx` - `frontend-web/src/pages/ChatsPage.tsx` 作用: - 左侧栏新增「定时任务」和「任务管理」入口。 - 普通最近聊天列表中过滤 `metadata.thread_type === "scheduler"` 的系统会话。 为什么这样做: - 定时任务会话是系统专用会话,不应该和普通用户聊天列表混在一起。 - 用户仍然可以通过专门入口进入该会话查看报告。 ## 4. 配置说明 运行时配置文件: - `offline-backend-20260505/bundle/.env` 当前写入的是注释示例,默认不会启用 Element: ```env # ELEMENT_ENABLED=true # ELEMENT_HOMESERVER_URL=http://your-element-homeserver:8008 # ELEMENT_AUTO_REGISTER=true # ELEMENT_BOT_USERNAME=scheduled-bot # ELEMENT_STATE_FILE=.deer-flow/data/element-bot-token ``` 内网启用时建议: ```env ELEMENT_ENABLED=true ELEMENT_HOMESERVER_URL=http://你的内网Matrix地址:8008 ELEMENT_AUTO_REGISTER=true ELEMENT_BOT_USERNAME=scheduled-bot ELEMENT_STATE_FILE=.deer-flow/data/element-bot-token ``` 如果不允许自动注册: ```env ELEMENT_ENABLED=true ELEMENT_HOMESERVER_URL=http://你的内网Matrix地址:8008 ELEMENT_AUTO_REGISTER=false ELEMENT_BOT_USERNAME=scheduled-bot ELEMENT_BOT_PASSWORD=你的机器人密码 ELEMENT_STATE_FILE=.deer-flow/data/element-bot-token ``` 用户个人 Element ID 不写在 `.env`,由用户在任务管理页面填写,例如: ```text @zhangsan:matrix.intranet.local ``` ## 5. 内网发版方案 ### 5.1 推荐方案 推荐发布一个新的增量包目录,例如: ```text offline-backend-20260512-scheduler/ ``` 本次按你的原部署方式处理:继续使用 0509 原镜像,只复制或挂载代码 patch。新增依赖 `croniter` 由启动脚本通过内网 Python 私服安装。 推荐产物: - `frontend-vite-20260512.zip` - `start_backend.scheduler.sh` - `stop_backend.sh` - `config.yaml.example` - `.env.example` - `extensions_config.json` - `skills/` - `data/` - `docs/SCHEDULED_TASK_CHANGE_AND_RELEASE_ZH.md` - `patch_20260512_scheduler/` ### 5.2 后端发版文件清单 需要进入新包或 patch 包的后端文件: ```text backend/app/gateway/app.py backend/app/gateway/deps.py backend/app/gateway/routers/scheduled_tasks.py backend/packages/harness/deerflow/agents/lead_agent/agent.py backend/packages/harness/deerflow/persistence/models/__init__.py backend/packages/harness/deerflow/persistence/scheduled_tasks/ backend/packages/harness/deerflow/runtime/scheduler/ backend/packages/harness/deerflow/tools/builtins/__init__.py backend/packages/harness/deerflow/tools/builtins/scheduled_task_tool.py backend/packages/harness/deerflow/tools/tools.py backend/pyproject.toml backend/.env ``` 注意: - 不建议把本地 `.deer-flow/data/deerflow.db*` 作为代码发版内容,它们是运行数据。 - 内网环境自己的 `data/` 目录要保留,这是历史会话和任务数据所在位置。 ### 5.3 前端发版文件清单 需要构建并发布新的前端静态资源。 涉及源码: ```text frontend-web/src/App.tsx frontend-web/src/core/scheduled-tasks/ frontend-web/src/pages/ScheduledChatPage.tsx frontend-web/src/pages/ScheduledTasksPage.tsx frontend-web/src/components/workspace/workspace-nav-chat-list.tsx frontend-web/src/components/workspace/recent-chat-list.tsx frontend-web/src/pages/ChatsPage.tsx ``` 构建命令: ```bash cd frontend-web pnpm install --offline pnpm run build ``` 产物: ```text frontend-web/dist/ ``` 将 `dist/` 按你原来的方式打包成 `frontend-vite-20260512.zip` 或覆盖原静态资源目录。 ### 5.4 适配 `start_backend.0509.sh` 的部署方式 0509 脚本核心逻辑是: - 使用老镜像 `deerflow-backend-offline:20260505`。 - 通过 `patch_0509` bind-mount Python 文件覆盖镜像内代码。 - 通过 `--env-file .env` 注入配置。 - 通过 `-v data:/app/backend/.deer-flow` 保留运行数据。 本次继续沿用这个模式,调整两点: 1. `PATCH_BASE` 建议改成新的目录,例如 `patch_20260512_scheduler`。 2. 启动脚本在容器启动前检查并安装 `croniter`,依赖来源使用内网 Python 私服。 示例脚本差异: ```bash IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}" PATCH_BASE="${SCRIPT_DIR}/patch_20260512_scheduler" SCHEDULER_INSTALL_DEPS="${SCHEDULER_INSTALL_DEPS:-1}" SCHEDULER_PIP_PACKAGES="${SCHEDULER_PIP_PACKAGES:-croniter>=6.2.2}" ``` 容器启动命令中会执行: ```bash uv run --no-sync python -c "import croniter" >/dev/null 2>&1 || uv pip install "${SCHEDULER_PIP_PACKAGES:-croniter>=6.2.2}" ``` ### 5.5 Python 私服配置 如果内网 Python 私服需要配置索引地址,可以写到 `bundle/.env` 中: ```env UV_INDEX_URL=http://your-python-mirror/simple PIP_INDEX_URL=http://your-python-mirror/simple UV_TRUSTED_HOST=your-python-mirror PIP_TRUSTED_HOST=your-python-mirror ``` 如果私服使用 HTTPS 且证书正常,一般不需要 `*_TRUSTED_HOST`。 也可以在启动时临时覆盖安装包名: ```bash SCHEDULER_PIP_PACKAGES='croniter>=6.2.2' ./start_backend.scheduler.sh ``` ### 5.6 内网服务器部署步骤 在内网服务器上: ```bash cd /opt/deerflow/offline-backend-20260512-scheduler/bundle # 1. 备份旧配置和数据 cp -a config.yaml config.yaml.bak.$(date +%Y%m%d%H%M%S) cp -a .env .env.bak.$(date +%Y%m%d%H%M%S) cp -a data data.bak.$(date +%Y%m%d%H%M%S) # 2. 合并配置 # 保留原来的 AUTH_JWT_SECRET、模型 key、CORS 等配置。 # 追加或打开 Element、Python 私服相关配置。 vi .env # 3. 启动后端,继续使用原镜像 deerflow-backend-offline:20260505 chmod +x start_backend.scheduler.sh stop_backend.sh ./start_backend.scheduler.sh # 4. 查看日志 docker logs -f deerflow-backend ``` 本次已补充一个适配脚本: ```text offline-backend-20260505/bundle/start_backend.scheduler.sh ``` 它和 0509 脚本保持相同的 Docker 运行方式,同时支持: - 默认使用 `deerflow-backend-offline:20260505`。 - 默认从 `patch_20260512_scheduler` 挂载 Python patch。 - 允许通过环境变量覆盖 `IMAGE_TAG`、`PATCH_BASE`、`HOST_PORT`、`CONTAINER_NAME`。 - 启动前自动检查并安装 `croniter`,可通过 `SCHEDULER_INSTALL_DEPS=0` 关闭。 如果仍使用 0509 脚本名称,需要注意 0509 脚本里的 `PATCH_BASE` 是脚本内固定赋值,外部传环境变量不会生效。请直接编辑脚本里的: ```bash PATCH_BASE="${SCRIPT_DIR}/patch_20260512_scheduler" ``` 同时把镜像变量改成: ```bash IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}" ``` ### 5.7 数据库表创建说明 本次新增的 SQLAlchemy model 会在后端启动初始化时注册。 内网启动后检查日志: ```bash docker logs deerflow-backend --tail 200 ``` 如果看到定时任务相关表不存在的错误,需要确认当前项目是否在启动时自动 `create_all`。 手动验证 SQLite 数据库: ```bash sqlite3 data/data/deerflow.db ".tables" ``` 应能看到: ```text scheduled_tasks scheduled_task_runs scheduled_task_delivery_profiles scheduler_threads ``` 实际数据库路径以你当前 `DEER_FLOW_HOME` 和已有部署结构为准。0509 脚本中挂载的是: ```text bundle/data -> /app/backend/.deer-flow ``` ### 5.8 Element 联调步骤 1. 确认内网 Matrix/Element homeserver 地址,例如: ```text http://matrix.intranet.local:8008 ``` 2. 修改 `.env`: ```env ELEMENT_ENABLED=true ELEMENT_HOMESERVER_URL=http://matrix.intranet.local:8008 ELEMENT_AUTO_REGISTER=true ELEMENT_BOT_USERNAME=scheduled-bot ELEMENT_STATE_FILE=.deer-flow/data/element-bot-token ``` 3. 重启后端: ```bash ./stop_backend.sh ./start_backend.scheduler.sh ``` 4. 用户进入前端任务管理页面,填写自己的 Element 用户 ID。 5. 创建一个测试任务并点击「立即运行」。 6. 检查: - 定时任务聊天框是否收到结果。 - Element 私聊是否收到机器人消息。 - 后端日志是否有 `Failed to deliver scheduled task`。 ## 6. 验证清单 后端验证: ```bash docker logs deerflow-backend --tail 200 curl http://127.0.0.1:8001/openapi.json | grep scheduled-tasks ``` 前端验证: - 左侧栏出现「定时任务」和「任务管理」。 - 普通聊天列表不显示系统定时任务会话。 - 在智能体聊天中输入「每天 9 点生成 AI 发展日报」可以创建任务。 - 进入任务管理页可以看到任务,且可以修改、暂停、恢复、删除、立即运行。 - 进入定时任务聊天框可以看到执行结果。 Element 验证: - 未配置 `ELEMENT_HOMESERVER_URL` 时,任务仍然正常写入定时任务聊天框,不影响主流程。 - 配置 Element 且用户填写 Element ID 后,任务成功时会发送 Matrix DM。 ## 7. 回滚方案 后端回滚: ```bash ./stop_backend.sh docker load -i deerflow-backend-offline-image-20260505.tar IMAGE_TAG=deerflow-backend-offline:20260505 bash start_backend.sh ``` 前端回滚: - 恢复上一版前端静态资源包。 数据说明: - 新增表可以保留,不影响老版本读取原有聊天数据。 - 如果需要彻底清理定时任务数据,请先备份数据库,再清理新增的四张表。 ## 8. 已验证事项 本地已做过: - 前端 `npm run build` 通过。 - 新增后端定时任务与 Element 文件做过 `python -m py_compile`。 限制说明: - 本地全量后端 compileall 曾遇到项目中既有 Python 版本语法兼容问题,不是本次新增文件导致。 - 当前工作区中的 `.deer-flow/data/deerflow.db*` 是本地运行数据变更,不建议纳入代码发版。