Files
HarborForge/plans/NEXT_WAVE_DEV_DIRECTION.md

383 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HarborForge Calendar System — Design Document
> Date: 2026-03-22
---
## Overview
为 HarborForge 新增日程表Calendar系统支持 Agent/人类用户的任务调度、周期性计划、以及通过 OpenClaw 插件心跳自动唤醒 Agent 执行日程。
同时记录一项项目结构调整:
- Propose 改名为 Proposal
- Proposal Accept 不再生成单个 Feature Task
- 改为在 Proposal 下维护多个 EssentialAccept 时将该 Proposal 下所有 Essential 按类型生成对应的 story task并落到 Proposal 选择的 Milestone
- story 整个大类改为 restricted只允许通过 Proposal Accept 创建
- Essential 拥有独立的 EssentialCode编码风格参考 ProjectCode / TaskCode 等既有结构
同时包含一个 bug fixacc-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:**
```json
{
"type": "Task|Support|Meeting|Essential",
"code": "TASK-42",
"working_sessions": ["session-id-1", "session-id-2"]
}
```
**SystemEvent:**
```json
{
"event": "ScheduleToday|SummaryToday|ScheduledGatewayRestart"
}
```
- `ScheduleToday` — 每日日程规划
- `SummaryToday` — 每日总结
- `ScheduledGatewayRestart` — OpenClaw 网关计划重启前触发;插件收到后应持久化状态、发送最终心跳、暂停定时任务
**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最小工作量配置
```json
{
"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 表:
1. `hf calendar schedule` 手动创建
2. Plan 的虚拟 slot 被 `edit``cancel`(物化后断开 plan 关联)
3. 每天预计算:服务器每日将当天所有 plan 匹配的 slot 物化到缓存/数据库
### 3.3 当日缓存
- 每天(凌晨或首次心跳时)预计算当天所有 plan → 物化为当日 slot 缓存
- 当天新增 `schedule` / `plan-schedule` 影响当天时,同步更新缓存
### 3.4 不可变性
- `cancel` / `edit` 不能操作过去的 slot
- `plan-cancel` / `plan-edit` 不追溯过去已物化的 slot
---
## 四、时区
统一使用 HarborForge 服务器时区,不做用户级时区。
---
## 五、验证规则
`schedule` / `plan-schedule` 提交时验证:
1. **Overlap 检测**:与同日已有 slot 时间重叠 → **拒绝,报错**
2. **最小工作量检查**:不满足 MinimumWorkload 配置 → **警告,但允许提交**
---
## 六、Agent 唤醒机制
### 6.1 心跳流程
1. 插件每分钟向 HarborForge 服务器发送心跳
2. 服务器返回该插件claw_identifier对应所有 Agent 当前需要执行的日程
- 筛选条件:当天 slotstatus 为 NotStarted 或 Deferredscheduled_at 已过
3. 插件检查 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 |
| API rate-limit 或 billing 错误 | → Exhausted |
| Exhausted 恢复计时器到期 | → Idle |
### 6.5 Exhausted 状态详细规则
**前提:** 所有 Agent 只使用一个主模型,没有 fallback 模型。
**进入条件:**
- Agent 调用 LLM API 时收到 rate-limit 错误HTTP 429 等)
- Agent 调用 LLM API 时收到 billing 相关错误(额度不足、配额耗尽等)
**恢复逻辑:**
- 解析错误信息,查找类似 `reset in X mins``retry after X``resets at <timestamp>` 的模式
- 如果能解析出时间 → 设置 **X 分钟** 后恢复为 Idle
- 如果无法解析 → 默认 **5 小时** 后恢复为 Idle
**Exhausted 期间行为:**
- Agent 视为不可用,不被心跳唤醒
- 待执行的 slot 设为 Deferred
- 前端/Monitor 显示 Exhausted 状态 + 预计恢复时间
**Agent 表扩展字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| exhausted_at | datetime, nullable | 进入 Exhausted 的时间 |
| recovery_at | datetime, nullable | 预计恢复时间 |
| exhaust_reason | Enum(RateLimit, Billing), nullable | 原因 |
---
## 七、CLI 命令
### 7.1 日程操作
```bash
# 创建单次日程
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 计划操作
```bash
# 创建周期性计划
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 支持)
```bash
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 表
---
## 八、项目结构调整记录(与 Calendar 相关联的设计备注)
### 8.1 命名调整
- `Propose` 统一改名为 `Proposal`
### 8.2 新结构
```text
Project
└─ Proposal
├─ Essential
│ ├─ feature
│ ├─ improvement
│ └─ refactor
└─ accept -> 将该 Proposal 下所有 Essential 生成对应类型的 story task并创建到 Proposal 选择的 Milestone
```
### 8.3 Proposal Accept 语义变更
旧行为:
- Proposal Accept 后生成单个 `story/feature` task
新行为:
- Proposal Accept **不再生成单个 Feature Task**
- 每个 Proposal 可维护多个 Essential
- Accept 时,遍历该 Proposal 下全部 Essential
- 按 Essential 类型映射生成对应的 story task
- `feature` -> `story/feature`
- `improvement` -> `story/improvement`
- `refactor` -> `story/refactor`
- 生成目标 Milestone 由 Proposal 在 Accept 前或 Accept 时明确选择
### 8.4 Story 创建限制
- `story` 整个大类视为 **restricted**
- 任何 `story/*` task 都不允许通过通用 task create endpoint 直接创建
- `story` 仅允许通过 `Proposal Accept` 工作流生成
### 8.5 Essential
新增 `Essential` 概念,用于承载 Proposal 下的可落地核心条目。
建议字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int (PK) | |
| essential_code | VARCHAR(64), UNIQUE | EssentialCode风格参考 ProjectCode / TaskCode / MilestoneCode / ProposalCode |
| proposal_id | FK -> proposals.id | 所属 Proposal |
| type | Enum(feature, improvement, refactor) | Essential 类型 |
| title | VARCHAR(255) | 标题 |
| description | TEXT, nullable | 描述 |
| created_by_id | FK -> users.id, nullable | 创建人 |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
### 8.6 代码生成
- `Essential` 拥有独立 `EssentialCode`
- 编码规则参考现有类似结构,例如:
- `ProjectCode`
- `MilestoneCode`
- `TaskCode`
- `ProposalCode`
- 具体前缀与编号策略待后续单独定稿
## 九、前端
- 每个用户可查看/调整自己的日程表
- Admin 可查看/调整所有用户的日程表
- 日程表页面展示(具体 UI 待设计)
---
## 十、Bug Fix
- acc-mgr 用户后端禁止修改密码admin 也不行)
- 前端acc-mgr 用户不显示修改密码入口
---
## 十一、待定项
- Entertainment 事件子类型设计
- MinimumWorkload 存储方式JSON 字段 vs 独立表)
- 前端日程表 UI 详细设计
- Agent 唤醒的提示词模板
- Proposal / Essential 的详细数据模型、API、前端页面与 accept 流程实现细节
- Story restricted 后,现有 task create / propose accept / milestone flow 的兼容迁移策略