8.3 KiB
8.3 KiB
HarborForge Calendar System — Design Document
Date: 2026-03-22
Overview
为 HarborForge 新增日程表(Calendar)系统,支持 Agent/人类用户的任务调度、周期性计划、以及通过 OpenClaw 插件心跳自动唤醒 Agent 执行日程。
同时包含一个 bug fix:acc-mgr 用户密码不可修改,前端隐藏修改密码入口。
一、数据模型
1.1 TimeSlot(日程槽)
| 字段 | 类型 | 说明 |
|---|---|---|
| slot_id | int (PK) | 已物化 slot 的数据库 ID |
| user_id | FK -> users.id | 所属用户 |
| date | date | 日期 |
| slot_type | Enum(Work, OnCall, Entertainment, System) | 槽类型 |
| estimated_duration | int (1-50) | 预估时长(分钟),设计上限 50 分钟,超过需拆分 |
| scheduled_at | time (00:00-23:00) | 计划开始时间 |
| started_at | time, nullable | 实际开始时间 |
| attended | bool, default false | 是否已出席 |
| actual_duration | int (0-65535), nullable | 实际时长(分钟),无上限 |
| event_type | Enum(Job, Entertainment, SystemEvent), nullable | 事件类型 |
| event_data | JSON, nullable | 事件详情(见下方 Event 子类型) |
| priority | int (0-99) | 优先级 |
| status | Enum(NotStarted, Ongoing, Deferred, Skipped, Paused, Finished, Aborted) | 状态 |
| plan_id | FK -> schedule_plans.id, nullable | 来源计划(物化自 plan 时填入,被 edit/cancel 后置 NULL) |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
1.2 Event 子类型(存储在 event_data JSON 中)
Job:
{
"type": "Task|Support|Meeting",
"code": "TASK-42",
"working_sessions": ["session-id-1", "session-id-2"]
}
SystemEvent:
{
"event": "ScheduleToday|SummaryToday"
}
Entertainment:
待设计
1.3 SchedulePlan(周期性计划)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int (PK) | plan-id |
| user_id | FK -> users.id | 所属用户 |
| slot_type | Enum(Work, OnCall, Entertainment, System) | 槽类型 |
| estimated_duration | int (1-50) | 预估时长 |
| event_type | Enum(Job, Entertainment, SystemEvent), nullable | 事件类型 |
| event_data | JSON, nullable | 事件详情 |
| at_time | time | 每天的计划时间 (--at HH:mm) |
| on_day | Enum(Sun, Mon, Tue, Wed, Thu, Fri, Sat), nullable | 星期几 (--on-day) |
| on_week | int (1-4), nullable | 第几周 (--on-week) |
| on_month | Enum(Jan-Dec), nullable | 月份 (--on-month) |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
周期参数层级约束:
- 使用
--on-month则--on-week必须也用 - 使用
--on-week则--on-day必须也用 --at始终必填
示例:
--at 09:00 --on-day Sun --on-week 1 --on-month Jan→ 每年一月第一周周日 09:00--at 09:00 --on-day Sun --on-week 1→ 每月第一周周日 09:00--at 09:00 --on-day Sun→ 每周日 09:00--at 09:00→ 每天 09:00
1.4 Agent 表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int (PK) | |
| user_id | FK -> users.id, UNIQUE | 关联用户 |
| agent_id | VARCHAR, UNIQUE | OpenClaw agent name ($AGENT_ID) |
| claw_identifier | VARCHAR | OpenClaw 实例 identifier(与 Monitor 碰巧一致,无 FK) |
| status | Enum(Idle, OnCall, Busy, Exhausted, Offline), default Idle | Agent 当前状态 |
| last_heartbeat | datetime, nullable | 最后心跳时间 |
| created_at | datetime | 创建时间 |
1.5 MinimumWorkload(最小工作量配置)
{
"daily": { "work": 0, "on_call": 0, "entertainment": 0 },
"weekly": { "work": 0, "on_call": 0, "entertainment": 0 },
"monthly": { "work": 0, "on_call": 0, "entertainment": 0 },
"yearly": { "work": 0, "on_call": 0, "entertainment": 0 }
}
值为分钟数 (0-65535)。存储方式待定(可作为用户级配置存 JSON 字段或独立表)。
二、Slot ID 策略
- 已物化的 slot:使用数据库自增 ID
- Plan 虚拟 slot(未物化):使用
plan-{plan_id}-{date}格式 - 当虚拟 slot 被
edit或cancel时,物化到数据库,获得真实 ID,同时该日期该 plan 不再生成虚拟 slot
三、存储与缓存策略
3.1 Plan 不预展开
Plan 只存规则,不为每一天生成数据行。
3.2 物化时机
以下情况写入 time_slots 表:
hf calendar schedule手动创建- Plan 的虚拟 slot 被
edit或cancel(物化后断开 plan 关联) - 每天预计算:服务器每日将当天所有 plan 匹配的 slot 物化到缓存/数据库
3.3 当日缓存
- 每天(凌晨或首次心跳时)预计算当天所有 plan → 物化为当日 slot 缓存
- 当天新增
schedule/plan-schedule影响当天时,同步更新缓存
3.4 不可变性
cancel/edit不能操作过去的 slotplan-cancel/plan-edit不追溯过去已物化的 slot
四、时区
统一使用 HarborForge 服务器时区,不做用户级时区。
五、验证规则
schedule / plan-schedule 提交时验证:
- Overlap 检测:与同日已有 slot 时间重叠 → 拒绝,报错
- 最小工作量检查:不满足 MinimumWorkload 配置 → 警告,但允许提交
六、Agent 唤醒机制
6.1 心跳流程
- 插件每分钟向 HarborForge 服务器发送心跳
- 服务器返回该插件(claw_identifier)对应所有 Agent 当前需要执行的日程
- 筛选条件:当天 slot,status 为 NotStarted 或 Deferred,scheduled_at 已过
- 插件检查 Agent 状态
6.2 唤醒逻辑
Agent 状态为 Idle:
- 唤醒 Agent,提供提示词开始工作
- 向服务器设置 Agent status = Busy 或 OnCall(取决于 slot_type)
- 设置 slot: attended = true, started_at = now, status = Ongoing
Agent 状态非 Idle:
- 设置 slot status = Deferred
6.3 多 Slot 竞争
- 选 priority 最高的执行,其余 Deferred 且 priority += 1
- 通知 Agent 当前任务完成后重新规划所有 Deferred + NotStarted 的 slot
6.4 状态转移
| 触发条件 | Agent Status 变化 |
|---|---|
| 超过 2 分钟无心跳 | → Offline |
| 无待执行日程 | → Idle |
| 被 TimeSlot 唤醒 | → Busy / OnCall |
| 完成 TimeSlot | → Idle |
七、CLI 命令
7.1 日程操作
# 创建单次日程
hf calendar schedule <slot-type> <scheduled-at> <estimated-duration> \
[--job <code>] [--date <yyyy-mm-dd>]
# 查看某天日程
hf calendar show [--date <yyyy-mm-dd>]
# 取消日程(plan 来源的 slot 物化后断开 plan)
hf calendar cancel [--date <yyyy-mm-dd>] <slot-id>
# 编辑日程(plan 来源的 slot 物化后断开 plan)
hf calendar edit [--date <yyyy-mm-dd>] <slot-id> \
[--slot-type <type>] [--estimated-duration <mins>] \
[--job <code>] [--scheduled-at <HH:mm>]
# 列出所有有已物化日程的未来日期(纯 plan 的不算)
hf calendar date-list
7.2 计划操作
# 创建周期性计划
hf calendar plan-schedule <slot-type> <estimated-duration> \
--at <HH:mm> [--on-day <day>] [--on-week <1-4>] [--on-month <month>]
# 列出所有计划
hf calendar plan-list
# 取消计划(不追溯过去)
hf calendar plan-cancel <plan-id>
# 编辑计划(不追溯过去)
hf calendar plan-edit <plan-id> \
[--at <HH:mm>] [--on-day <day>] [--on-week <1-4>] [--on-month <month>] \
[--slot-type <type>] [--estimated-duration <mins>]
7.3 用户创建(Agent 支持)
hf user create <username> [--agent-id <id>] [--claw-identifier <id>] ...
--agent-id+--claw-identifier必须同时出现或同时不出现- pcexec 模式下:
--agent-id←$AGENT_ID--claw-identifier←openclaw config get plugins.harbor-forge.identifier
- 后端
POST /users扩展:接受agent_id+claw_identifier,创建 User 同时写入 agents 表
八、前端
- 每个用户可查看/调整自己的日程表
- Admin 可查看/调整所有用户的日程表
- 日程表页面展示(具体 UI 待设计)
九、Bug Fix
- acc-mgr 用户:后端禁止修改密码(admin 也不行)
- 前端:acc-mgr 用户不显示修改密码入口
十、待定项
- Entertainment 事件子类型设计
- MinimumWorkload 存储方式(JSON 字段 vs 独立表)
- 前端日程表 UI 详细设计
- Agent 唤醒的提示词模板