docs/plan/2026-07-27-scheduled-task-system-design.md
Rama main · réplica de lectura del repositorio original.
# 定时任务调度系统技术方案
> 文档版本:v1.0
> 文档状态:待实施
> 编写日期:2026-07-27
> 适用项目:Mao Agent Workbench
## 1. 需求背景
当前 Agent 执行完全依赖用户通过 WebSocket 主动发送消息触发——系统没有任何自动或定时触发 Agent 执行的机制。当用户提出类似"每天早上帮我检查新股申购"、"每天下午5点给我生成日报"等需求时,Agent 无法实现。
核心限制:
- Agent 只能被动响应,不能主动发起执行
- 没有后台定时任务能力
- 没有消息注入机制
本方案构建一个通用的**定时任务调度系统**,让用户可以通过自然语言对话创建定时任务,系统按 cron 计划自动向固定 Session 注入消息触发 Agent 执行,复用现有 AgentLoop 引擎,执行结果通过现有 Webhook 通知系统推送。
## 2. 需求描述
### 2.1 功能范围
| 模块 | 功能点 | 结论 |
|------|--------|------|
| 任务创建 | 用户通过自然语言对话让 Agent 创建定时任务(Agent 内置工具) | 做 |
| 任务创建 | 通过 UI 表单手动创建定时任务 | 不做,仅对话式 |
| 任务管理 | 管理后台(admin/)定时任务管理页面:列表、启用/禁用、删除 | 做 |
| 任务管理 | 桌面端(desktop/)定时任务侧栏面板:列表、启用/禁用、删除 | 做 |
| 任务管理 | 对话中查看和管理任务(Agent 内置工具:列出、更新、删除) | 做 |
| 触发方式 | Cron 表达式定时触发 | 做 |
| 触发方式 | 固定间隔触发(如每30分钟) | 做 |
| 触发方式 | 外部 Webhook 回调触发 | 不做 |
| 触发方式 | 事件驱动触发(如文件变更、外部系统回调) | 不做 |
| 执行机制 | 向固定 Session 注入用户消息,复用现有 AgentLoop 执行 | 做 |
| 执行机制 | 独立后台执行(不依赖 Session) | 不做 |
| Session 策略 | 每个任务绑定固定 Session,历史上下文可累积 | 做 |
| Session 策略 | 每次触发新建 Session | 不做 |
| Agent 绑定 | 绑定创建任务时的当前 Agent | 做 |
| Agent 绑定 | 用户手动指定 Agent | 不做 |
| 并发控制 | 上次执行未完成时跳过本次触发 | 做 |
| 并发控制 | 取消上次执行,启动新的 | 不做 |
| 并发控制 | 排队等待上次完成 | 不做 |
| 执行超时 | 不限制执行时间,由用户取消、任务正常完成或真实错误终止 | 做 |
| 结果通知 | 复用现有 Webhook 通知系统(钉钉/飞书)+ Session 未读标记 | 做 |
| 结果通知 | 邮件通知 | 不做 |
| 结果通知 | 微信通知 | 不做 |
| Prompt 定义 | Agent 智能解析自然语言,转化为结构化 prompt 和 cron 表达式 | 做 |
| Prompt 定义 | 用户直接输入 cron 表达式和 prompt 文本 | 做(Agent 工具也接受结构化输入作为后备) |
| 权限 | 用户只能管理自己创建的任务 | 做 |
| 权限 | 管理员可管理所有用户的任务 | 做 |
### 2.2 业务规则
1. 定时任务绑定到创建时的 Session 对应的 Agent,执行时复用该 Agent 的 system_prompt、技能、模型配置。
2. 每个任务拥有一个专属 Session,所有执行历史在同一 Session 中累积。
3. 任务触发时,系统检查目标 Session 是否正在执行(phase 为 RUNNING/RESUMING/WAITING_APPROVAL),若是则跳过本次触发,记录跳过日志。
4. 任务触发时,系统向目标 Session 持久化一条 USER 角色消息(内容为任务的 prompt),然后调用 `HarnessService.executeFromEvent()` 启动 Agent 执行。
5. 任务进入 COMPLETED 或 FAILED 终态后,复用现有 Webhook 通知判定逻辑:先尝试 WS 推送,无在线客户端时通过 Webhook 发送。
6. 用户只能管理自己创建的定时任务;管理员可在管理后台查看和管理所有用户的任务。
7. 禁用任务后不再触发,启用后恢复。删除任务为逻辑删除。
8. cron 表达式解析失败时,任务不触发并在日志中记录错误。
### 2.3 用户流程
```
【创建任务】
用户在对话中: "每天早上9点帮我检查新股申购,有新股就提醒我"
→ Agent 调用 tool_create_scheduled_task({ prompt: "检查今日是否有新股申购...", cron: "0 0 9 * * ?" })
→ 后端创建定时任务 + 专属 Session
→ Agent 回复:"已创建定时任务'新股申购检查',每天早上9点执行"
【任务执行】
Spring Scheduler 扫描到期任务
→ 检查目标 Session 是否空闲
→ 空闲:持久化 USER 消息 → 调用 HarnessService.executeFromEvent()
→ 忙碌:跳过本次,记录日志
→ Agent 执行完毕 → 通知推送(WS 或 Webhook)
【管理任务】
用户在桌面端侧栏:查看任务列表、启用/禁用、删除
用户在管理后台:管理员查看所有任务、管理
用户在对话中:"列出我的定时任务" / "取消新股提醒任务"
```
## 3. 当前代码分析
### 3.1 可复用能力
| 能力 | 当前实现 | 复用方式 |
|------|----------|----------|
| Agent 执行引擎 | `HarnessService.executeFromEvent()` → `AgentLoop.execute()` | 直接复用,注入 USER 消息后调用 |
| 定时调度基础设施 | `@EnableScheduling` 已启用,已有 5 个 `@Scheduled` 任务 | 新增 `ScheduledTaskScheduler` 扫描到期任务 |
| Webhook 通知系统 | `TaskCompletionNotificationService` + Outbox 模式 | 任务终态复用相同通知逻辑 |
| Session 管理 | `SessionService` 完整的 CRUD + 状态管理 | 为每个定时任务创建专属 Session |
| Message 持久化 | `SessionService.saveMessage()` | 直接调用持久化 USER 消息 |
| WebSocket 事件推送 | `StreamingWsRegistry` 多设备推送 | 任务执行过程的实时事件自动推送 |
| 内置工具注册 | `@Component` 实现 `Tool` 接口自动注册 | 新增 4 个内置工具 |
| 管理后台路由 | `admin/src/router/index.ts` Vue Router | 新增定时任务管理路由 |
| 桌面端侧栏 | 左侧面板组件体系 | 新增定时任务面板 |
### 3.2 关键代码路径
| 组件 | 文件路径 |
|------|----------|
| Agent 执行入口 | `backend/.../harness/core/HarnessService.java` |
| Agent 循环 | `backend/.../harness/core/AgentLoop.java` |
| WS 消息处理 | `backend/.../session/ws/StreamingWsHandler.java` |
| WS 连接注册 | `backend/.../session/ws/StreamingWsRegistry.java` |
| 工具接口 | `backend/.../harness/tool/Tool.java` |
| 工具实现目录 | `backend/.../harness/tool/impl/` |
| 现有 Scheduler 示例 | `backend/.../session/service/StaleSessionSweepScheduler.java` |
| Webhook 通知 | `backend/.../notification/task/` |
| Flyway 迁移 | `backend/src/main/resources/db/migration/` (当前最新 V060) |
| 管理后台路由 | `admin/src/router/index.ts` |
| 管理后台视图 | `admin/src/views/` |
| 桌面端 composables | `desktop/src/composables/` |
| 桌面端组件 | `desktop/src/components/` |
## 4. 整体设计
### 4.1 系统架构
```
┌─────────────────────────────────────────────────────────────┐
│ 用户层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 桌面端侧栏 │ │ 管理后台页面 │ │ 对话式交互 │ │
│ │ (定时任务面板) │ │ (任务管理) │ │ (Agent 内置工具) │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ REST API / WebSocket │ │
│ └──────────────────────────┬───────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────┘
│
┌─────────────────────────────┼───────────────────────────────┐
│ 后端核心 │
│ │ │
│ ┌───────────────────────────▼───────────────────────────┐ │
│ │ ScheduledTaskScheduler (每分钟扫描) │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ 1. 查询 enabled=true 且 next_fire_time │ │ │
│ │ │ <= now 的任务 │ │ │
│ │ │ 2. 检查目标 Session 是否空闲 │ │ │
│ │ │ 3. 空闲 → 持久化 USER 消息 + 触发执行 │ │ │
│ │ │ 4. 忙碌 → 跳过,计算下次触发时间 │ │ │
│ │ │ 5. 更新 next_fire_time │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HarnessService.executeFromEvent() │ │
│ │ (现有 AgentLoop 引擎) │ │
│ └───────────────────────┬──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ TaskCompletionNotificationService (现有) │ │
│ │ WS 推送 / Webhook (钉钉/飞书) / 未读标记 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 数据层 │ │
│ │ scheduled_task 表 + session 表 + message 表 │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 4.2 核心数据模型
**scheduled_task 表:**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT AUTO_INCREMENT PK | 主键 |
| user_id | BIGINT NOT NULL | 创建者用户 ID |
| agent_id | BIGINT NOT NULL | 绑定的 Agent ID |
| session_id | BIGINT NOT NULL | 专属 Session ID |
| name | VARCHAR(200) NOT NULL | 任务名称 |
| prompt | TEXT NOT NULL | 触发时注入的 prompt 内容 |
| cron_expression | VARCHAR(100) NOT NULL | Cron 表达式(Spring cron 格式:秒 分 时 日 月 周) |
| status | VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' | 状态:ACTIVE / PAUSED / DELETED |
| last_fire_time | DATETIME | 上次触发时间 |
| last_execution_status | VARCHAR(20) | 上次执行状态:COMPLETED / FAILED / SKIPPED |
| next_fire_time | DATETIME | 下次触发时间(预计算,用于快速查询) |
| fire_count | INT DEFAULT 0 | 累计触发次数 |
| created_at | DATETIME NOT NULL | 创建时间 |
| updated_at | DATETIME NOT NULL | 更新时间 |
| deleted | TINYINT DEFAULT 0 | 逻辑删除标记 |
### 4.3 Cron 表达式格式
使用 Spring `CronExpression`(6 位:秒 分 时 日 月 周),支持:
| 用户意图 | Cron 表达式 | 说明 |
|----------|-------------|------|
| 每天早上 9 点 | `0 0 9 * * ?` | 标准 cron |
| 每个工作日早上 9 点 | `0 0 9 * * MON-FRI` | 周一到周五 |
| 每 30 分钟 | `0 */30 * * * ?` | 固定间隔 |
| 每周一上午 10 点 | `0 0 10 * * MON` | 指定星期 |
| 每月 1 号上午 9 点 | `0 0 9 1 * ?` | 指定日期 |
## 5. 实现步骤
### 5.1 数据库迁移
**文件**:`backend/src/main/resources/db/migration/V061__scheduled_task.sql`
```sql
CREATE TABLE scheduled_task (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
agent_id BIGINT NOT NULL,
session_id BIGINT NOT NULL,
name VARCHAR(200) NOT NULL,
prompt TEXT NOT NULL,
cron_expression VARCHAR(100) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
last_fire_time DATETIME DEFAULT NULL,
last_execution_status VARCHAR(20) DEFAULT NULL,
next_fire_time DATETIME DEFAULT NULL,
fire_count INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT NOT NULL DEFAULT 0,
INDEX idx_user_id (user_id),
INDEX idx_status_next_fire (status, next_fire_time, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
### 5.2 后端 — 实体与 Mapper
**新增文件:**
| 文件 | 说明 |
|------|------|
| `backend/.../schedule/entity/ScheduledTask.java` | 实体类,`@TableName("scheduled_task")` |
| `backend/.../schedule/mapper/ScheduledTaskMapper.java` | MyBatis-Plus Mapper 接口 |
**ScheduledTask 关键字段映射:**
```java
@Data
@TableName("scheduled_task")
public class ScheduledTask {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long agentId;
private Long sessionId;
private String name;
private String prompt;
private String cronExpression;
private String status; // ACTIVE / PAUSED
private LocalDateTime lastFireTime;
private String lastExecutionStatus; // COMPLETED / FAILED / SKIPPED
private LocalDateTime nextFireTime;
private Integer fireCount;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@TableLogic
private Integer deleted;
}
```
### 5.3 后端 — Service 层
**新增文件:**
| 文件 | 说明 |
|------|------|
| `backend/.../schedule/service/ScheduledTaskService.java` | 核心业务逻辑 |
**ScheduledTaskService 职责:**
```java
@Service
@RequiredArgsConstructor
public class ScheduledTaskService {
// 创建定时任务
// 1. 创建专属 Session(title=任务名, agent_id=绑定Agent, session_type=NORMAL)
// 2. 计算 next_fire_time
// 3. 持久化 scheduled_task 记录
public ScheduledTask createTask(Long userId, Long agentId, String name,
String prompt, String cronExpression);
// 更新任务(名称、prompt、cron、启用/禁用)
// 若 cron 变更,重新计算 next_fire_time
public ScheduledTask updateTask(Long taskId, Long userId, ...);
// 删除任务(逻辑删除)
public void deleteTask(Long taskId, Long userId);
// 查询用户的任务列表
public List<ScheduledTask> listByUser(Long userId);
// 查询所有任务(管理员用)
public Page<ScheduledTask> listAll(PageParams params);
// 执行定时任务(由 Scheduler 调用)
// 1. 检查 Session 是否空闲
// 2. 空闲 → 持久化 USER 消息 → 调用 HarnessService.executeFromEvent()
// 3. 更新 last_fire_time、fire_count、next_fire_time
public void executeTask(ScheduledTask task);
// 计算下次触发时间
public LocalDateTime calculateNextFireTime(String cronExpression);
}
```
### 5.4 后端 — Scheduler
**新增文件:**
| 文件 | 说明 |
|------|------|
| `backend/.../schedule/scheduler/ScheduledTaskScheduler.java` | 定时扫描与触发 |
**核心逻辑:**
```java
@Component
@RequiredArgsConstructor
@Slf4j
public class ScheduledTaskScheduler {
private final ScheduledTaskService scheduledTaskService;
private final ScheduledTaskMapper scheduledTaskMapper;
// 每分钟扫描一次
@Scheduled(fixedDelay = 60_000)
public void scanAndExecute() {
LocalDateTime now = LocalDateTime.now();
// 查询:status=ACTIVE AND next_fire_time <= now AND deleted=0
List<ScheduledTask> dueTasks = scheduledTaskMapper.selectList(
new LambdaQueryWrapper<ScheduledTask>()
.eq(ScheduledTask::getStatus, "ACTIVE")
.le(ScheduledTask::getNextFireTime, now)
.eq(ScheduledTask::getDeleted, 0)
);
for (ScheduledTask task : dueTasks) {
try {
scheduledTaskService.executeTask(task);
} catch (Exception e) {
log.error("定时任务执行失败: taskId={}", task.getId(), e);
// 更新状态为 FAILED,计算下次触发时间
}
}
}
}
```
### 5.5 后端 — Agent 内置工具
**新增 4 个工具,放置在 `backend/.../harness/tool/impl/` 目录:**
| 工具文件 | 工具名 | 说明 |
|----------|--------|------|
| `CreateScheduledTaskTool.java` | `create_scheduled_task` | 创建定时任务 |
| `ListScheduledTasksTool.java` | `list_scheduled_tasks` | 列出用户的定时任务 |
| `UpdateScheduledTaskTool.java` | `update_scheduled_task` | 更新任务(名称、prompt、cron、启用/禁用) |
| `DeleteScheduledTaskTool.java` | `delete_scheduled_task` | 删除定时任务 |
**CreateScheduledTaskTool 示例结构:**
```java
@Component
@RequiredArgsConstructor
public class CreateScheduledTaskTool implements Tool {
private final ScheduledTaskService scheduledTaskService;
@Override
public String getName() { return "create_scheduled_task"; }
@Override
public String getDescription() {
return "创建定时任务。任务将按照指定的 cron 计划自动执行。" +
"例如:每天早上9点检查新股申购、每小时检查服务器状态等。";
}
@Override
public Map<String, Object> getInputSchema() {
// 参数:name(任务名), prompt(执行内容), cron_expression(Spring cron 6位)
return Map.of(
"type", "object",
"properties", Map.of(
"name", Map.of("type", "string", "description", "任务名称"),
"prompt", Map.of("type", "string", "description", "任务触发时执行的 prompt 内容"),
"cron_expression", Map.of("type", "string",
"description", "Spring cron 表达式(6位:秒 分 时 日 月 周),如 '0 0 9 * * ?' 表示每天9点")
),
"required", List.of("name", "prompt", "cron_expression")
);
}
@Override
public String execute(String arguments, Long sessionId, Long userId, String workspace) {
// 1. 解析参数
// 2. 从 sessionId 获取 agent_id
// 3. 调用 scheduledTaskService.createTask()
// 4. 返回任务创建结果 JSON
}
}
```
**工具行为指南(toolPrompt)关键内容:**
```markdown
## create_scheduled_task 使用指南
当用户希望创建定时自动执行的任务时使用此工具。
### cron 表达式规则
- 格式:秒 分 时 日 月 周(Spring 6位 cron)
- "每天早上9点" → "0 0 9 * * ?"
- "每30分钟" → "0 */30 * * * ?"
- "工作日早上9点" → "0 0 9 * * MON-FRI"
### prompt 编写要求
- prompt 应该是完整的、自包含的指令
- 应包含足够的上下文,因为 Agent 执行时只有任务历史,没有用户实时对话
- 示例:"检查今日是否有新股可申购。如果有,列出新股代码、名称、申购价格和上限;如果没有,简要说明即可。"
```
### 5.6 后端 — REST API
**新增文件:**
| 文件 | 说明 |
|------|------|
| `backend/.../schedule/controller/ScheduledTaskController.java` | REST 接口 |
**接口定义:**
| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/api/v1/scheduled-tasks` | 查询当前用户的任务列表 | 登录用户 |
| GET | `/api/v1/scheduled-tasks/all` | 查询所有任务(分页) | 管理员 |
| PUT | `/api/v1/scheduled-tasks/{id}` | 更新任务(名称、prompt、cron、状态) | 任务创建者 |
| DELETE | `/api/v1/scheduled-tasks/{id}` | 删除任务 | 任务创建者 |
> 注:创建任务通过 Agent 工具(`create_scheduled_task`)完成,不提供 REST 创建接口。
> 注:管理员接口需添加 `@RequirePermission` 注解。
### 5.7 管理后台 — 定时任务管理页面
**新增文件:**
| 文件 | 说明 |
|------|------|
| `admin/src/views/scheduled-tasks/index.vue` | 定时任务管理页面 |
**页面功能:**
- 任务列表表格:名称、创建者、Agent、Cron 表达式、状态、上次执行状态、上次触发时间、下次触发时间、触发次数
- 操作列:启用/禁用开关、删除按钮
- 支持分页
**路由配置:**
在 `admin/src/router/index.ts` 中新增:
```typescript
{
path: 'scheduled-tasks',
name: 'ScheduledTasks',
component: () => import('@/views/scheduled-tasks/index.vue'),
meta: { title: '定时任务', icon: 'Clock' }
}
```
### 5.8 桌面端 — 定时任务侧栏面板
**新增文件:**
| 文件 | 说明 |
|------|------|
| `desktop/src/components/ScheduledTaskPanel.vue` | 定时任务面板组件 |
| `desktop/src/composables/useScheduledTasks.ts` | 定时任务数据管理 composable |
**面板功能:**
- 任务列表:名称、状态(Active/Paused)、下次触发时间
- 操作:启用/禁用切换、删除
- 空状态提示:"还没有定时任务,试试对 Agent 说'每天早上9点帮我检查新股'"
**集成位置:**
在桌面端左侧面板或设置区域添加入口,点击后展开定时任务面板。
### 5.9 消息注入与执行流程
**核心执行流程(ScheduledTaskService.executeTask):**
```
executeTask(task)
│
├─ 1. 查询 Session 当前 phase
│ └─ 若 phase 为 RUNNING/RESUMING/WAITING_APPROVAL
│ → 标记 last_execution_status = 'SKIPPED'
│ → 计算 next_fire_time
│ → return
│
├─ 2. 持久化 USER 消息
│ └─ sessionService.saveMessage(sessionId, "USER", task.prompt, ...)
│
├─ 3. 更新 Session 状态
│ └─ sessionService.updatePhase(sessionId, "RUNNING")
│
├─ 4. 创建 WsStreamingEventListener(用于推送执行事件)
│
├─ 5. 提交到 agentExecutor 线程池
│ └─ agentExecutor.submit(() -> {
│ harnessService.executeFromEvent(sessionId, eventId, listener, cancelFlag)
│ })
│
├─ 6. 更新任务状态
│ ├─ last_fire_time = now
│ ├─ fire_count++
│ ├─ next_fire_time = calculateNextFireTime(cron)
│ └─ last_execution_status = 根据执行结果设置
│
└─ 7. 通知判定
└─ 复用现有 TaskCompletionNotificationService 逻辑
```
## 6. 落地清单
### 6.1 后端
| 序号 | 任务 | 文件 | 依赖 |
|------|------|------|------|
| B1 | 创建数据库迁移脚本 | `V061__scheduled_task.sql` | 无 |
| B2 | 创建实体类 | `schedule/entity/ScheduledTask.java` | B1 |
| B3 | 创建 Mapper | `schedule/mapper/ScheduledTaskMapper.java` | B2 |
| B4 | 创建 Service | `schedule/service/ScheduledTaskService.java` | B2, B3 |
| B5 | 创建 Scheduler | `schedule/scheduler/ScheduledTaskScheduler.java` | B4 |
| B6 | 创建内置工具 `create_scheduled_task` | `harness/tool/impl/CreateScheduledTaskTool.java` | B4 |
| B7 | 创建内置工具 `list_scheduled_tasks` | `harness/tool/impl/ListScheduledTasksTool.java` | B4 |
| B8 | 创建内置工具 `update_scheduled_task` | `harness/tool/impl/UpdateScheduledTaskTool.java` | B4 |
| B9 | 创建内置工具 `delete_scheduled_task` | `harness/tool/impl/DeleteScheduledTaskTool.java` | B4 |
| B10 | 创建 REST Controller | `schedule/controller/ScheduledTaskController.java` | B4 |
| B11 | 编写单元测试 | `schedule/service/ScheduledTaskServiceTest.java` 等 | B4-B10 |
### 6.2 管理后台
| 序号 | 任务 | 文件 | 依赖 |
|------|------|------|------|
| A1 | 创建定时任务管理页面 | `admin/src/views/scheduled-tasks/index.vue` | B10 |
| A2 | 注册路由 | `admin/src/router/index.ts` 修改 | A1 |
### 6.3 桌面端
| 序号 | 任务 | 文件 | 依赖 |
|------|------|------|------|
| D1 | 创建定时任务 composable | `desktop/src/composables/useScheduledTasks.ts` | B10 |
| D2 | 创建定时任务面板组件 | `desktop/src/components/ScheduledTaskPanel.vue` | D1 |
| D3 | 集成到桌面端布局 | 相关布局组件修改 | D2 |
| D4 | 更新 package.json version | `desktop/package.json` | 全部 |
### 6.4 执行顺序
```
Phase 1 — 后端基础(B1 → B2 → B3 → B4)
Phase 2 — 后端核心(B5, B6, B7, B8, B9, B10 并行,均依赖 B4)
Phase 3 — 后端测试(B11)
Phase 4 — 管理后台(A1 → A2,依赖 B10)
Phase 5 — 桌面端(D1 → D2 → D3 → D4,依赖 B10)
```
## 7. 风险与注意事项
| 风险 | 说明 | 应对 |
|------|------|------|
| Session 消息膨胀 | 定时任务长期运行后 Session 消息量过大 | 现有 CompactionService 自动压缩机制兜底 |
| Scheduler 扫描性能 | 任务量大时每分钟全表扫描 | `idx_status_next_fire` 索引保证查询效率,仅查询 `next_fire_time <= now` 的记录 |
| 线程池饱和 | 大量定时任务同时触发 | 复用现有 `agentExecutor` 线程池,已有队列机制;跳过策略也避免并发堆积 |
| 时区问题 | Cron 触发时间与用户期望不一致 | 使用系统默认时区(Asia/Shanghai),后续可扩展用户级时区设置 |
| LLM 费用 | 定时任务频繁调用 LLM 产生费用 | 用户自行控制任务频率;管理员可在管理后台监控触发次数 |