229805a070
Add WeChat service account routing with sync acks, connectivity tests, and context isolation; document deploy runbooks; and bundle related MindSpace, voice, Plaza, and server gateway changes for production rollout. Co-authored-by: Cursor <cursoragent@cursor.com>
684 lines
21 KiB
Markdown
684 lines
21 KiB
Markdown
# 待办 / 日程 / 提醒能力设计文档
|
||
|
||
> **状态:** 设计稿
|
||
> **目标版本:** v0.2.x
|
||
> **适用范围:** H5 门户、微信服务号 Agent、MindSpace 行程展示页
|
||
> **生产提醒:** 当前仓库目录可能承载生产服务。开发和验证按 `docs/service-isolation-runbook.md` 先在测试目录完成,不在生产目录直接跑迁移或重启服务。
|
||
|
||
## 背景
|
||
|
||
用户希望用自然语言完成两类高频动作:
|
||
|
||
1. 记录事项:例如“我明天要去开会,帮我记录增加一个提醒”。
|
||
2. 查看计划:例如“看看我的行程计划”。
|
||
|
||
现状里已有几个可利用基础:
|
||
|
||
- `capabilities.mjs` 已预留 `todo` 能力,但默认关闭,且当前项目没有本地一等的待办/日程表。
|
||
- `wechat-mp.mjs` 已能接收微信服务号文本、转给用户专属 Agent、再通过客服消息回复。
|
||
- MindSpace 已有生成公开/私有 HTML 页面的能力,适合把一周行程做成简洁精美页面。
|
||
|
||
缺口在于:日程和提醒还没有结构化存储、没有到点派发 worker、没有 Agent 可调用的本地日程工具,也没有固定的澄清规则。
|
||
|
||
## 产品目标
|
||
|
||
### 必须支持
|
||
|
||
- 用户用自然语言创建待办、日程、提醒。
|
||
- 缺少必要时间信息时,助手必须追问,不猜测具体钟点。
|
||
- 用户明确“不提醒”时,只记录到待办或日程列表,不创建提醒推送。
|
||
- 对会议类表达,如果已有明确开始时间但没有指定提前多久提醒,默认提前 1 小时提醒。
|
||
- 用户查看行程时,按指定时间范围展示;未指定范围时展示最近 7 天。
|
||
- 行程查询结果优先以简洁消息回复;当内容较多或用户要求“页面/好看一点”时,生成 MindSpace HTML 页面。
|
||
|
||
### 暂不纳入 MVP
|
||
|
||
- 复杂重复规则,如“每月第二个周三”。
|
||
- 多人共享日程、会议邀请、外部日历同步。
|
||
- 地理围栏提醒。
|
||
- 跨端原生 push。MVP 先走已绑定微信服务号通知通道,后续再扩展短信、邮件或 App push。
|
||
|
||
## 概念模型
|
||
|
||
| 概念 | 说明 | 例子 |
|
||
|------|------|------|
|
||
| 待办 task | 有或没有截止时间的任务,不一定占用时间段 | “买票”、“周五前交材料” |
|
||
| 日程 event | 有开始时间,通常可以有结束时间,占用时间段 | “今天下午三点开会” |
|
||
| 提醒 reminder | 某个时间点触发的一次通知,可挂在 task/event 上 | “会议前 1 小时提醒” |
|
||
|
||
设计原则:
|
||
|
||
- 待办和日程是“记录”;提醒是“通知计划”。
|
||
- 一条待办/日程可以没有提醒。
|
||
- 一条待办/日程可以有多条提醒,但 MVP 默认最多一条。
|
||
- 所有时间入库使用 epoch 毫秒,额外保存用户时区,展示时按用户时区格式化。
|
||
|
||
## 自然语言交互规则
|
||
|
||
### 创建提醒或日程
|
||
|
||
#### 规则 1:缺少事件发生时间时必须追问
|
||
|
||
用户说:
|
||
|
||
```text
|
||
我明天要去开会,帮我记录增加一个提醒
|
||
```
|
||
|
||
如果系统当前日期是 2026-06-18,助手能解析“明天”为 2026-06-19,但缺少具体钟点,不能创建到点提醒。回复:
|
||
|
||
```text
|
||
可以。我先记下“明天开会”。你想几点提醒?会议大概几点开始,提前多久提醒你?
|
||
```
|
||
|
||
待用户补充后再创建。
|
||
|
||
#### 规则 2:用户明确不提醒时只记录
|
||
|
||
用户说:
|
||
|
||
```text
|
||
不用提醒,先记一下
|
||
```
|
||
|
||
系统创建一条待办或全天日程,不创建 `h5_schedule_reminders` 记录。回复:
|
||
|
||
```text
|
||
已记录到待办列表:明天开会,未设置提醒。
|
||
```
|
||
|
||
#### 规则 3:会议类有明确开始时间时默认提前 1 小时
|
||
|
||
用户说:
|
||
|
||
```text
|
||
今天下午三点有个会
|
||
```
|
||
|
||
如果系统当前日期是 2026-06-18,解析为:
|
||
|
||
- 日程:2026-06-18 15:00,标题“开会”或“会议”
|
||
- 提醒:2026-06-18 14:00
|
||
- 提醒偏移:60 分钟
|
||
|
||
回复:
|
||
|
||
```text
|
||
已记录:今天 15:00 会议。我会提前 1 小时,也就是 14:00 提醒你。
|
||
```
|
||
|
||
#### 规则 4:非会议类默认不擅自加提前提醒
|
||
|
||
用户说:
|
||
|
||
```text
|
||
明天上午十点去取护照
|
||
```
|
||
|
||
创建日程,但如果用户没有说“提醒我”,不自动创建提醒。回复可提示:
|
||
|
||
```text
|
||
已记录:明天 10:00 取护照。需要我提前提醒的话,可以告诉我提前多久。
|
||
```
|
||
|
||
#### 规则 5:用户直接指定提醒时间时按提醒时间创建
|
||
|
||
用户说:
|
||
|
||
```text
|
||
明天上午九点提醒我带材料
|
||
```
|
||
|
||
创建待办“带材料”,并创建 `remind_at = 明天 09:00` 的提醒。此时不需要追问“提前多久”。
|
||
|
||
#### 规则 6:每天固定时间推送当天待办记录
|
||
|
||
用户说:
|
||
|
||
```text
|
||
每天早上 7 点给我发一天的待办记录
|
||
```
|
||
|
||
系统必须创建一条每日待办摘要订阅:
|
||
|
||
- 类型:`todo_day`
|
||
- 时间:每天 07:00
|
||
- 通道:微信服务号
|
||
- 行为:每天到点查询用户当天待办记录,通过服务号主动发送给用户
|
||
|
||
回复:
|
||
|
||
```text
|
||
已设置:我会每天早上 7点 通过服务号把当天待办记录发给你。
|
||
```
|
||
|
||
如果用户只说“每天给我发待办记录”,缺少时间,必须追问具体时间。
|
||
|
||
### 查看行程
|
||
|
||
#### 规则 7:没有时间范围时默认最近 7 天
|
||
|
||
用户说:
|
||
|
||
```text
|
||
看看我的行程计划
|
||
```
|
||
|
||
查询范围:
|
||
|
||
- 起点:用户时区当天 00:00
|
||
- 终点:起点 + 7 天
|
||
|
||
回复格式优先按日期分组:
|
||
|
||
```text
|
||
未来 7 天你有 3 个安排:
|
||
|
||
6 月 18 日 周四
|
||
14:00 会议提醒
|
||
15:00 会议
|
||
|
||
6 月 19 日 周五
|
||
全天 开会
|
||
```
|
||
|
||
#### 规则 8:指定时间范围时按范围查询
|
||
|
||
用户说“看下明天的安排”、“下周有什么会”、“6 月 20 到 25 日的计划”,按指定范围查询。
|
||
|
||
#### 规则 9:需要精美展示时生成页面
|
||
|
||
触发条件:
|
||
|
||
- 用户明确说“用页面展示”、“好看一点”、“生成一个行程页”。
|
||
- 查询结果超过 8 条,普通文本不易读。
|
||
- 用户来自 H5 页面上下文,适合打开 MindSpace 页面。
|
||
|
||
页面要求:
|
||
|
||
- 第一屏直接是行程表,不做营销式 landing page。
|
||
- 按日期分组,突出今天、明天、逾期、即将到来。
|
||
- 对日程、待办、提醒用不同视觉标识。
|
||
- 移动端优先,宽屏下使用双栏或周视图。
|
||
|
||
## 系统架构
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
U["用户文本: 微信/H5"] --> I["意图解析层"]
|
||
I -->|缺必要信息| Q["追问用户"]
|
||
I -->|可执行| T["Schedule Service"]
|
||
T --> DB["MySQL: schedule tables"]
|
||
T --> R["Reminder Worker"]
|
||
R --> W["微信通知通道"]
|
||
T --> V["行程查询 API"]
|
||
V --> A["Agent 文本回复"]
|
||
V --> P["MindSpace 行程页面"]
|
||
```
|
||
|
||
### 组件职责
|
||
|
||
| 组件 | 职责 |
|
||
|------|------|
|
||
| 意图解析层 | 从自然语言提取 action、title、date/time、reminder offset、query range;判断是否需要追问 |
|
||
| Schedule Service | 统一创建、查询、修改、取消待办/日程/提醒 |
|
||
| Reminder Worker | 周期扫描到期提醒,锁定、投递、记录成功/失败 |
|
||
| 微信通知通道 | 发送提醒消息。MVP 优先复用已绑定服务号 openid,生产上线前确认模板/订阅通知资质 |
|
||
| MindSpace 展示 | 将查询结果渲染为 HTML 页面,保存到用户 `public/` 或私有页面记录 |
|
||
|
||
## 数据库设计
|
||
|
||
### `h5_schedule_items`
|
||
|
||
记录待办和日程主体。
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS h5_schedule_items (
|
||
id CHAR(36) PRIMARY KEY,
|
||
user_id CHAR(36) NOT NULL,
|
||
kind ENUM('task', 'event') NOT NULL,
|
||
title VARCHAR(255) NOT NULL,
|
||
description TEXT NULL,
|
||
status ENUM('active', 'completed', 'cancelled', 'deleted') NOT NULL DEFAULT 'active',
|
||
start_at BIGINT NULL,
|
||
end_at BIGINT NULL,
|
||
due_at BIGINT NULL,
|
||
all_day TINYINT(1) NOT NULL DEFAULT 0,
|
||
timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
|
||
location VARCHAR(255) NULL,
|
||
source_channel ENUM('h5', 'wechat', 'agent', 'api') NOT NULL DEFAULT 'agent',
|
||
source_session_id VARCHAR(128) NULL,
|
||
source_message_id VARCHAR(128) NULL,
|
||
source_text TEXT NULL,
|
||
metadata_json JSON NULL,
|
||
created_at BIGINT NOT NULL,
|
||
updated_at BIGINT NOT NULL,
|
||
deleted_at BIGINT NULL,
|
||
KEY idx_schedule_user_time (user_id, status, start_at, due_at),
|
||
KEY idx_schedule_user_updated (user_id, updated_at),
|
||
CONSTRAINT fk_schedule_item_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
```
|
||
|
||
字段说明:
|
||
|
||
- `kind = task`:待办,通常使用 `due_at`,也可没有时间。
|
||
- `kind = event`:日程,通常使用 `start_at/end_at`。
|
||
- `all_day = 1`:只有日期没有具体钟点,如“明天开会”。
|
||
- `source_text`:保留用户原文,便于回溯和修正。
|
||
|
||
### `h5_schedule_reminders`
|
||
|
||
记录提醒计划和投递状态。
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS h5_schedule_reminders (
|
||
id CHAR(36) PRIMARY KEY,
|
||
user_id CHAR(36) NOT NULL,
|
||
item_id CHAR(36) NOT NULL,
|
||
remind_at BIGINT NOT NULL,
|
||
offset_minutes INT NULL,
|
||
channel ENUM('wechat', 'in_app') NOT NULL DEFAULT 'wechat',
|
||
status ENUM('pending', 'locked', 'sent', 'failed', 'cancelled') NOT NULL DEFAULT 'pending',
|
||
attempts INT NOT NULL DEFAULT 0,
|
||
last_error VARCHAR(500) NULL,
|
||
locked_until BIGINT NULL,
|
||
sent_at BIGINT NULL,
|
||
created_at BIGINT NOT NULL,
|
||
updated_at BIGINT NOT NULL,
|
||
UNIQUE KEY uq_schedule_item_remind_at (item_id, remind_at, channel),
|
||
KEY idx_reminder_due (status, remind_at),
|
||
KEY idx_reminder_user (user_id, status, remind_at),
|
||
CONSTRAINT fk_schedule_reminder_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
|
||
CONSTRAINT fk_schedule_reminder_item FOREIGN KEY (item_id) REFERENCES h5_schedule_items(id) ON DELETE CASCADE
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
```
|
||
|
||
### `h5_schedule_delivery_logs`
|
||
|
||
记录每次投递尝试,用于排错和审计。
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS h5_schedule_delivery_logs (
|
||
id CHAR(36) PRIMARY KEY,
|
||
reminder_id CHAR(36) NOT NULL,
|
||
user_id CHAR(36) NOT NULL,
|
||
channel ENUM('wechat', 'in_app') NOT NULL,
|
||
status ENUM('success', 'failed') NOT NULL,
|
||
provider_message_id VARCHAR(128) NULL,
|
||
error_code VARCHAR(64) NULL,
|
||
error_message VARCHAR(500) NULL,
|
||
created_at BIGINT NOT NULL,
|
||
KEY idx_delivery_reminder (reminder_id, created_at),
|
||
KEY idx_delivery_user (user_id, created_at),
|
||
CONSTRAINT fk_schedule_delivery_reminder FOREIGN KEY (reminder_id) REFERENCES h5_schedule_reminders(id) ON DELETE CASCADE,
|
||
CONSTRAINT fk_schedule_delivery_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
```
|
||
|
||
## 后端模块设计
|
||
|
||
### 新增文件建议
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `schedule-service.mjs` | 领域服务:创建、查询、更新、取消、完成 |
|
||
| `schedule-intent.mjs` | 轻量规则解析和澄清决策,先覆盖中文常用表达 |
|
||
| `schedule-reminder-worker.mjs` | 到点提醒扫描、锁定、投递、重试 |
|
||
| `schedule-render.mjs` | 文本摘要和页面数据结构渲染 |
|
||
| `schedule-service.test.mjs` | 服务层单测 |
|
||
| `schedule-intent.test.mjs` | 意图解析和追问规则单测 |
|
||
| `schedule-reminder-worker.test.mjs` | worker 锁和重试单测 |
|
||
|
||
### `schedule-service.mjs`
|
||
|
||
建议导出:
|
||
|
||
```js
|
||
export function createScheduleService(pool, deps = {}) {
|
||
return {
|
||
createItem,
|
||
updateItem,
|
||
completeItem,
|
||
cancelItem,
|
||
listItems,
|
||
createReminder,
|
||
cancelReminder,
|
||
listDueReminders,
|
||
lockReminder,
|
||
markReminderSent,
|
||
markReminderFailed,
|
||
};
|
||
}
|
||
```
|
||
|
||
关键约束:
|
||
|
||
- 所有写入校验 `user_id` 所属。
|
||
- 删除使用软删除,避免误删历史提醒。
|
||
- 创建提醒时必须确认 `remind_at` 是具体时间点,不能是全天日期。
|
||
- `remind_at <= now` 的提醒允许创建,但 worker 应尽快投递,并在回复里提示“时间已到,会立即提醒”。
|
||
|
||
### `schedule-intent.mjs`
|
||
|
||
MVP 不必追求全量 NLP。先做规则 + LLM 辅助的混合策略:
|
||
|
||
1. 规则层识别高置信意图:提醒、开会、行程查询、不提醒、完成/取消。
|
||
2. 规则层解析常用中文时间:今天、明天、后天、上午/下午/晚上、几点、半点、下周。
|
||
3. 不确定时让 Agent 追问,而不是静默创建。
|
||
4. 后续可引入 LLM JSON 解析,但输出必须过 schema 校验。
|
||
|
||
解析结果结构:
|
||
|
||
```ts
|
||
type ScheduleIntent =
|
||
| {
|
||
action: 'create';
|
||
kind: 'task' | 'event';
|
||
title: string;
|
||
startAt?: number;
|
||
endAt?: number;
|
||
dueAt?: number;
|
||
allDay?: boolean;
|
||
reminder?: { remindAt?: number; offsetMinutes?: number; explicitNoReminder?: boolean };
|
||
needsClarification?: Array<'event_time' | 'reminder_time' | 'reminder_offset'>;
|
||
}
|
||
| {
|
||
action: 'query';
|
||
rangeStart?: number;
|
||
rangeEnd?: number;
|
||
preferPage?: boolean;
|
||
}
|
||
| {
|
||
action: 'update' | 'cancel' | 'complete';
|
||
targetText: string;
|
||
};
|
||
```
|
||
|
||
### Agent 接入策略
|
||
|
||
推荐分两阶段做。
|
||
|
||
#### 阶段 A:微信服务号入口先做确定性拦截
|
||
|
||
在 `wechat-mp.mjs` 转发给 Agent 前增加可选的 schedule preflight:
|
||
|
||
1. 对用户文本调用 `schedule-intent.mjs`。
|
||
2. 如果能确定执行,直接调用 `schedule-service.mjs` 并通过微信回复结果。
|
||
3. 如果缺必要信息,直接追问。
|
||
4. 如果不是日程意图,继续走现有 Agent 回复链路。
|
||
|
||
优点:
|
||
|
||
- 不依赖 Agent 是否会正确调用工具。
|
||
- 对“提醒”这种强业务流程更稳定。
|
||
- 不需要先改 goosed extension。
|
||
|
||
#### 阶段 B:给 H5 Agent 增加本地 schedule 工具
|
||
|
||
后续把日程能力暴露为平台工具,而不是让 Agent 调 H5 内部 API。建议工具:
|
||
|
||
- `schedule_create_item`
|
||
- `schedule_list_items`
|
||
- `schedule_update_item`
|
||
- `schedule_cancel_item`
|
||
- `schedule_create_reminder`
|
||
- `schedule_render_plan_page`
|
||
|
||
注意:当前 `api_lockdown` 会阻止 Agent 经代理访问 H5 本地接口,所以不要设计成“Agent 直接请求 `/mindspace/...` 或 `/api/schedule/...`”。更稳的是在服务端扩展平台工具,或在代理层专门白名单化受控的 schedule 工具调用。
|
||
|
||
## API 设计
|
||
|
||
H5 前端和管理调试可用 REST API;Agent 工具不直接走这些 API。
|
||
|
||
### 创建事项
|
||
|
||
`POST /api/schedule/items`
|
||
|
||
```json
|
||
{
|
||
"kind": "event",
|
||
"title": "会议",
|
||
"start_at": 1781766000000,
|
||
"end_at": null,
|
||
"all_day": false,
|
||
"timezone": "Asia/Shanghai",
|
||
"source_text": "今天下午三点有个会",
|
||
"reminder": {
|
||
"remind_at": 1781762400000,
|
||
"offset_minutes": 60,
|
||
"channel": "wechat"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 查询事项
|
||
|
||
`GET /api/schedule/items?from=1781712000000&to=1782316800000&include_reminders=1`
|
||
|
||
返回按时间升序的 items,每条带提醒摘要。
|
||
|
||
### 更新、完成、取消
|
||
|
||
- `PATCH /api/schedule/items/:id`
|
||
- `POST /api/schedule/items/:id/complete`
|
||
- `POST /api/schedule/items/:id/cancel`
|
||
- `POST /api/schedule/reminders/:id/cancel`
|
||
|
||
所有接口需要登录态和 user ownership 校验。
|
||
|
||
## 提醒 worker 设计
|
||
|
||
### 扫描策略
|
||
|
||
每 30 秒扫描一次:
|
||
|
||
```sql
|
||
SELECT *
|
||
FROM h5_schedule_reminders
|
||
WHERE status = 'pending'
|
||
AND remind_at <= ?
|
||
ORDER BY remind_at ASC
|
||
LIMIT 50
|
||
```
|
||
|
||
锁定时使用事务和状态更新:
|
||
|
||
```sql
|
||
UPDATE h5_schedule_reminders
|
||
SET status = 'locked',
|
||
locked_until = ?,
|
||
attempts = attempts + 1,
|
||
updated_at = ?
|
||
WHERE id = ?
|
||
AND status = 'pending';
|
||
```
|
||
|
||
投递成功后标记 `sent`;失败则:
|
||
|
||
- 可重试错误:状态改回 `pending`,`remind_at = now + backoff`。
|
||
- 不可重试错误:状态改为 `failed`。
|
||
- 超过最大次数:状态改为 `failed`。
|
||
|
||
### 幂等和并发
|
||
|
||
- `locked_until` 防止多进程重复投递。
|
||
- `delivery_logs` 记录每次尝试。
|
||
- `uq_schedule_item_remind_at` 防止同一事项重复创建同一时间提醒。
|
||
|
||
### 通知通道
|
||
|
||
MVP:
|
||
|
||
- 已绑定微信服务号用户:通过微信通知。
|
||
- 未绑定或通知失败:保留站内提醒状态,用户下次打开 H5 时展示。
|
||
|
||
生产上线前必须确认:
|
||
|
||
- 服务号客服消息是否适用于该次主动提醒。
|
||
- 是否需要模板消息或订阅通知模板。
|
||
- 对失败码做明确分流,例如未关注、超出发送窗口、模板不可用。
|
||
|
||
## 行程页面设计
|
||
|
||
### 页面数据结构
|
||
|
||
```json
|
||
{
|
||
"title": "未来 7 天行程",
|
||
"range_label": "6 月 18 日 - 6 月 24 日",
|
||
"days": [
|
||
{
|
||
"date": "2026-06-18",
|
||
"weekday": "周四",
|
||
"items": [
|
||
{
|
||
"time": "15:00",
|
||
"title": "会议",
|
||
"kind": "event",
|
||
"reminder": "14:00 提醒",
|
||
"status": "active"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 视觉要求
|
||
|
||
- 页面第一屏就是行程,不做大段介绍。
|
||
- 移动端使用纵向时间线;桌面端可使用 7 日网格。
|
||
- 颜色不使用单一紫蓝渐变;建议用清爽白底、墨色文本、低饱和蓝/绿/橙作为状态色。
|
||
- 所有文字在 375px 宽度下不溢出。
|
||
- 对“已过期、今天、明天、已提醒、无提醒”有明确状态。
|
||
|
||
## 权限和隐私
|
||
|
||
- 行程属于用户私密数据,默认不发布到 Plaza。
|
||
- 生成 MindSpace 页面时默认保存为私有草稿或用户私有空间;只有用户明确要求分享时才创建公开页。
|
||
- API 只返回当前登录用户的数据。
|
||
- 日程原文 `source_text` 可能含隐私,后台日志不要直接打印全文。
|
||
|
||
## 配置项
|
||
|
||
建议新增环境变量:
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `H5_SCHEDULE_ENABLED` | `0` | 是否启用日程 API 和微信 preflight |
|
||
| `H5_REMINDER_WORKER_ENABLED` | `0` | 是否启动提醒 worker |
|
||
| `H5_REMINDER_SCAN_INTERVAL_MS` | `30000` | worker 扫描间隔 |
|
||
| `H5_REMINDER_DEFAULT_MEETING_OFFSET_MINUTES` | `60` | 会议默认提前提醒分钟数 |
|
||
| `H5_REMINDER_MAX_ATTEMPTS` | `5` | 最大投递次数 |
|
||
| `H5_DEFAULT_TIMEZONE` | `Asia/Shanghai` | 默认用户时区 |
|
||
|
||
部署“每天早上 7 点服务号推送待办记录”时,至少需要:
|
||
|
||
```bash
|
||
H5_WECHAT_MP_ENABLED=1
|
||
H5_SCHEDULE_ENABLED=1
|
||
H5_REMINDER_WORKER_ENABLED=1
|
||
H5_DEFAULT_TIMEZONE=Asia/Shanghai
|
||
```
|
||
|
||
## 开发步骤
|
||
|
||
### P0:设计和测试骨架
|
||
|
||
- 新增本文档。
|
||
- 新增 `schedule-intent.test.mjs`,把关键中文场景先写成测试。
|
||
- 新增空服务骨架,确保不影响现有启动。
|
||
|
||
### P1:本地存储和 API
|
||
|
||
- 在 `schema.sql` 加三张表。
|
||
- 在 `db.mjs` 的 `migrateSchema` 加 `CREATE TABLE IF NOT EXISTS`。
|
||
- 实现 `schedule-service.mjs`。
|
||
- 实现 REST API,并补 `src/api/client.ts` 类型。
|
||
|
||
### P2:微信入口 preflight
|
||
|
||
- 在 `wechat-mp.mjs` 中,当 `H5_SCHEDULE_ENABLED=1` 时启用日程意图解析。
|
||
- 对可执行请求直接创建并回复。
|
||
- 对缺信息请求直接追问。
|
||
- 非日程消息保持现有 Agent 路径。
|
||
|
||
### P3:提醒 worker
|
||
|
||
- 实现 `schedule-reminder-worker.mjs`。
|
||
- 在 `server.mjs` 启动时按 env 开关启动。
|
||
- 先接入微信发送方法,失败后写 delivery log。
|
||
|
||
### P4:行程展示
|
||
|
||
- 文本摘要:直接在微信/H5 聊天里展示。
|
||
- 页面摘要:生成 MindSpace HTML 草稿或公开页,用户确认后再公开。
|
||
|
||
## 测试计划
|
||
|
||
### 单元测试
|
||
|
||
必须覆盖:
|
||
|
||
- “我明天要去开会,帮我记录增加一个提醒”解析为缺 `event_time` 和 `reminder_offset`。
|
||
- “不用提醒,先记一下”不会创建 reminder。
|
||
- “今天下午三点有个会”创建 event,并默认 offset 60。
|
||
- “明天上午九点提醒我带材料”创建 task reminder,remind_at 为明天 09:00。
|
||
- “看看我的行程计划”默认查询最近 7 天。
|
||
- “看下明天的安排”查询明天 00:00 到后天 00:00。
|
||
|
||
### 服务层测试
|
||
|
||
- 创建 event + reminder 成功。
|
||
- 同一 item 同一 remind_at 重复创建被幂等处理。
|
||
- cancel item 后 reminder 自动不可投递。
|
||
- listItems 不返回其他用户数据。
|
||
|
||
### Worker 测试
|
||
|
||
- 到期 reminder 被锁定并发送。
|
||
- 并发 worker 不重复发送。
|
||
- 发送失败按 backoff 重试。
|
||
- 超过最大次数标记 failed。
|
||
|
||
### 回归测试
|
||
|
||
```bash
|
||
pnpm test
|
||
pnpm run build
|
||
```
|
||
|
||
涉及微信入口时补 `wechat-mp.test.mjs`:
|
||
|
||
- 日程意图命中时不进入 Agent。
|
||
- 非日程文本仍走 Agent。
|
||
- 重复微信 `msgId` 仍只处理一次。
|
||
|
||
## 验收用例
|
||
|
||
| 输入 | 期望 |
|
||
|------|------|
|
||
| 我明天要去开会,帮我记录增加一个提醒 | 追问具体会议时间和提前多久提醒 |
|
||
| 不提醒,先记一下 | 创建待办/全天日程,无 reminder |
|
||
| 今天下午三点有个会 | 创建 15:00 会议,14:00 提醒 |
|
||
| 明天上午九点提醒我带材料 | 创建 09:00 提醒 |
|
||
| 每天早上 7 点给我发一天的待办记录 | 创建每日 07:00 服务号待办摘要订阅 |
|
||
| 看看我的行程计划 | 展示最近 7 天 |
|
||
| 看看明天行程 | 只展示明天 |
|
||
| 生成一个好看的本周行程页面 | 生成 MindSpace 行程页面 |
|
||
|
||
## 风险和决策
|
||
|
||
| 风险 | 处理 |
|
||
|------|------|
|
||
| 微信主动通知规则限制 | 上线前确认模板/订阅通知资质;客服消息仅作可用时通道 |
|
||
| 自然语言时间歧义 | 缺关键时间必须追问 |
|
||
| Agent 幻觉创建 | MVP 在微信入口做确定性 preflight,后续再开放工具 |
|
||
| 生产库迁移风险 | 测试库先迁移,生产按维护窗口执行 |
|
||
| 用户隐私 | 默认私有,不自动公开行程页面 |
|
||
|
||
## 推荐结论
|
||
|
||
这项能力可做,建议按 P1 到 P3 先交付一个可靠 MVP:能记录、能追问、能查询、能到点提醒。MindSpace 精美行程页作为 P4 增强,不阻塞核心提醒能力上线。
|