# calendar schedules create — 创建日程

创建日程并按需邀请参与人。

> [!CAUTION]
> 这是**写入操作** — 参数就绪后直接执行。

## 命令

```bash
# 创建日程（含参与人）—— 以"明天下午2点"为例，实际日期需替换为当前时间之后的具体值
wecom-cli calendar schedules create --json '{
  "subject": "产品评审",
  "begin_time": "<明天日期> 14:00:00",
  "end_time": "<明天日期> 15:00:00",
  "attendees": [{"userid": "woxxxa"}, {"userid": "woxxxb"}]
}'

# 仅自己的日程（无参与人）
wecom-cli calendar schedules create --json '{
  "subject": "午餐",
  "begin_time": "<明天日期> 12:00:00",
  "end_time": "<明天日期> 13:00:00"
}'

# 全天日程
wecom-cli calendar schedules create --json '{
  "subject": "年假",
  "begin_time": "<目标日期> 00:00:00",
  "end_time": "<目标日期> 23:59:59",
  "is_all_day": true
}'

# 创建日程并预订会议室（meeting_room_id 来自 rooms search，见 calendar-meeting-room；订房成功后只传 meeting_room_id，无需再把会议室名重复填进 location）
wecom-cli calendar schedules create --json '{
  "subject": "产品评审",
  "begin_time": "<明天日期> 14:00:00",
  "end_time": "<明天日期> 15:00:00",
  "attendees": [{"userid": "woxxxa"}, {"userid": "woxxxb"}],
  "meeting_room_id": "mrmxxxx"
}'
```

## 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|:----:|--------|------|
| `subject` | string | 是 | — | 日程主题 |
| `begin_time` | string | 是 | — | 开始时间（格式 YYYY-MM-DD HH:mm:ss，**必须晚于当前时间**） |
| `end_time` | string | 是 | — | 结束时间（格式 YYYY-MM-DD HH:mm:ss，必须晚于 `begin_time`）。如果用户没有给出，默认填写开始时间的一小时后 |
| `attendees` | object[] | 否 | `[]` | 参与人列表，格式为 `[{"userid": "woxxx"}, {"userid": "woyyy"}]`。用户提供的是姓名时通过 `读取 wecomcli-contact.md` 解析为 userid |
| `location` | string | 否 | `""` | 地点（文本）。**用户给的地点是某会议室时**：须先经 `rooms search` 预订该会议室（见步骤 3.5），预订成功后**只传 `meeting_room_id`、不再写 `location`**（会议室名由后端关联返回，无需在 `location` 里重复填充）。**用户给的地点不是会议室时**（如"星巴克""3 楼茶水间""客户现场"）：直接写入 `location`，不涉及 `meeting_room_id`。禁止把会议室名仅写进 `location` 却不订房——那样不会真正占用会议室 |
| `meeting_room_id` | string | 否 | — | 会议室 ID，来自 [calendar-meeting-room](wecomcli-calendar-meeting-room.md) 的 `rooms search`。传入即触发后端"建日程 + 占会议室"原子操作。订会议室时只传本字段即可，**不需要再把会议室名重复填进 `location`**。**该 ID 仅工具链使用，禁止出现在用户回复正文** |
| `description` | string | 否 | `""` | 日程描述 |
| `reminders` | object | 否 | — | 提醒设置：`is_remind`（bool，是否提醒）+ `reminder_time`（负数秒数组，表示提前提醒的秒数）。**仅在用户明确表达提醒意图时才传**：① 用户明确说"不提醒 / 不用提醒"→ 传 `is_remind=false`；② 用户明确了提前多久提醒（如"提前 10 分钟""提前 1 小时"）→ 传 `is_remind=true`，并把时长换算为对应的负数秒数组（如提前 10 分钟 = `[-600]`、提前 1 小时 = `[-3600]`）。用户未提及提醒时省略本字段，不要自行补默认提醒 |
| `timezone` | object | 否 | 用户 vid 时区 | 时区：`timezone_id`（如 `Asia/Shanghai`）+ `timezone_offset`（秒，如 `28800`） |
| `allow_self_join` | bool | 否 | `true` | 是否允许主动加入 |
| `is_all_day` | bool | 否 | `false` | 是否全天日程 |

**返回**：`schedule_id`（新建日程的唯一标识）。传了 `meeting_room_id` 时额外返回 `meeting_room.{meeting_room_id, meeting_room_name}` 关联字段（展示用 name）。

### 地点（`location`）vs 会议室（`meeting_room_id`）的区别 [CRITICAL]

两者都描述"在哪开"，但语义和处理方式不同，按用户给的地点是否为**会议室**分流：

| 用户 query 中的地点 | 处理方式 | 传入字段 |
|--------------------|---------|---------|
| **是某会议室**（如"地点在 1605 会议室""在 A 座创新室开"） | 必须先经 `rooms search` 尝试预订该会议室（见步骤 3.5）。预订成功（`status=bookable`）→ 拿到 `meeting_room_id`；不可用 → 走候选/换时间流程 | 预订成功后**只传 `meeting_room_id`**（占用会议室）；`location` 留空、不重复填会议室名 |
| **不是会议室**（如"星巴克""3 楼茶水间""客户现场""线上腾讯会议"等自由文本地点） | 直接作为文本地点使用，无需也不要走会议室查询 | 仅传 `location`，不传 `meeting_room_id` |

- **判定原则**：地点文本中含"会议室 / 室 / 房间 / 1605 这类房间号 / 某楼某室"等指向公司可预订会议室的表述，按"会议室"处理；否则按普通文本地点处理。无法判断时，可用文字与用户确认"是否需要预订该会议室"。
- **关键约束**：会议室场景下严禁只写 `location` 不传 `meeting_room_id`——只写文本不会真正占用（预订）会议室，会导致会议室被他人占用。

## 预约日程工作流

> **设计理念**：减少用户决策负担——能推断的不问，必须问的只问一次，决策留给用户而非代劳。

### 步骤 0：日程 / 会议消歧（仅当意图是"开会/约会"且未明确时）

> **触发条件**：用户说"会议/会/开个会/约个会/安排个会/xx会/xx会议"等，但未明确是日程还是会议（会议含在线会议链接、可远程/视频参会）。已明确是纯线下场景（如"约个 1:1""碰个面"）时才跳过本步骤。
>
> **注意 1**：用户只说"会议/会/开会"等泛称，本身不构成"明确"——这些词没有表明是日程还是会议，**禁止仅因 query 里有"会议"二字就默认按日程创建、跳过本步骤**，必须先用文字追问。
>
> **注意 2**：仅给出地点/会议室号的表述（如"在 1605 开会""到 A 座会议室碰一下""订个会议室开会"）不能据此判定为日程——订会议室与是日程还是会议是两件事，会议室里同样可能要远程接入。此类"只有地点"的表述仍需先用文字询问消歧，不要因为带了地点就跳过本步骤。

用文字直接询问用户创建日程还是会议，禁止默认直接创建日程：

> **问题与选项固定 [CRITICAL]**：消歧确认时，问题与可选项都必须原文照用、严格禁止修改任何内容——问题固定为 `"需要创建日程还是会议？"`，可选项固定为 `日程` / `会议`；不得改写问题措辞、增减或改写选项、翻译，或自行设计其他表述（如"在线会议 / 线上会议 / 视频会议 / 线下会议"等）。

用文字向用户提问：`需要创建日程还是会议？（请回复：日程 / 会议）`

- 用户答「日程」→ 留在本技能，继续步骤 1。
- 用户答「会议」→ 停止本工作流，改用 `读取 wecomcli-meeting.md` 创建会议（创建会议会同时生成对应日程，无需在本技能再建一条）。

### 步骤 1：上下文提取与信息补全

- **主题**：默认必须用文字询问主题，**禁止从对话语境自行提炼或代填**。仅当用户已明确说明主题（如"建个『产品评审』的日程""主题就叫周会"）时，才直接使用用户给出的主题、不再询问；只要用户没点明主题（哪怕能从事由猜出来，如"和张三约下午聊聊"），一律用文字询问：`请问这个日程的主题是？`（可举例"需求对齐 / 方案评审 / 1:1 沟通"等供参考，最多举 4 个，用户也可自行输入）
- **时长**：用户明确说了时长则直接使用；未提供时，统一默认 60 分钟（1 小时），不追问，由 `begin_time + 时长` 推算 `end_time`。
- **参与人**：用户明确指定了参与人则解析使用（人名 → userid，见步骤 2）；未提供时必须用文字追问，禁止默认创建个人日程或自行猜测：`需要邀请哪些人参与？`（可列出"仅自己（个人日程）"及根据对话语境补充的 1-3 个候选人名供参考，合计最多 4 个，用户也可自行输入）

### 步骤 2：参与人解析（人名 → userid）

> 上下文中已有合法 userid（`wo` 前缀）则直接使用，无需重复查询。

用户提供的是姓名时，通过 `读取 wecomcli-contact.md` 将所有姓名批量搜索，逐个关键词独立处理结果：

- **唯一匹配** → 直接使用，无需确认
- **多个匹配** → 用文字让用户选择，不自行猜测：`搜索到多个「{姓名}」，请确认要邀请哪一位？` 并列出候选（如"张三 - 产品部 - 产品经理 / 张三 - 技术部 - 前端工程师"，最多列 4 条，超出取前 4 并提示用户缩小范围）
- **无结果** → 用文字提示用户重新输入：`未找到「{姓名}」，请确认姓名是否正确`
- 所有姓名确认完毕后，汇总 userid 一并组装为 `attendees` 数组（格式 `[{"userid": "woxxx"}]`）
- **偏好记忆**：记录用户历史选择（如"张三"总是选"产品部-张三"），后续同名直接复用，减少确认轮次。

### 步骤 3：时间协商与冲突处理

不区分日程类型（不存在"生活类/工作类"之分，也没有"纯个人事项"可跳过的说法），所有创建一律查忙闲：查询对象始终包含当前用户自己（新建场景自己也要纳入，避免把日程排到自己已占用的时段），有其他内部参与人时一并纳入。按用户给出的时间信息分三种处理：

> 边界说明：这里"自己按完整目标时段查"是因为新建日程尚不存在、没有"本日程已占时段"需要排除；这与 update 改已有日程时"对自己/现有参与人扣除原时段重叠、纯自己可跳过"是同一原则（忙闲只为发现本日程之外的冲突）在"日程未建 / 已存在"下的不同表现，不要把 update 的"纯自己跳过"套到新建上。
>
> 查忙闲时 `min_duration_minutes` 设成该日程时长（或直接传 1），否则被默认 30 分钟过滤掉的短空闲段，会让落在其中的短日程误报为冲突。
>
> **推荐时段长度 ≠ 日程时长（精确 / 范围 / 未提供时间三种情况均适用）**：忙闲查询返回的推荐时段只用于确定日程的**开始时间**，其长度不代表日程时长。用户选定时段后，日程时长仍以用户明确指定的为准；用户未明确时长时一律默认 1 小时（`begin_time + 1h`，与步骤 1「时长」一致），禁止把推荐时段的长度直接当作日程时长。

- 精确时间（如"明天下午3点"）：先验证晚于当前真实时刻，已过去则提示用户重选未来时间；时间有效后必须先读取 [calendar-freebusy](wecomcli-calendar-freebusy.md) 检查忙闲（查询对象 = 自己 + 其他内部参与人）。任一对象（含自己）占线时，必须用文字让用户在「坚持这个时间 / 换一个时间」之间二选一，禁止自行换时间或劝阻用户改期：`该时间段{姓名}有日程冲突，如何处理？（请回复：坚持这个时间 / 换一个时间）`（仅与自己冲突时 {姓名} 写"你"）
- 范围时间（如"明天"、"下午"）：读取 [calendar-freebusy](wecomcli-calendar-freebusy.md)（查询对象 = 自己 + 其他内部参与人）拿到空闲 `slots`，让用户从返回的空闲时段中选择，选定后再创建（无需手填候选时刻，直接用 free list 返回的时段）。
- 未提供时间：先用文字列出具体的"日期+时刻"候选项让用户选择（结合当前时间动态推断，所有候选项必须晚于当前时刻，禁止使用"上午/下午/傍晚"等模糊表述，最多列 4 个）；用户选定具体时刻后，按上面"精确时间"的方式查忙闲再创建。文字提问如：`日程什么时候开始？`（候选按当前时刻动态生成、均须晚于现在，例如当前 19:40 可列 "明天 09:00 / 明天 14:00 / 明天 16:00 / 后天 09:00"）

**时间与日期推断规范：**
- **星期基准**：周一是一周第一天，周日是最后一天。
- **整天范围**："明天"、"今天"覆盖 00:00:00 ~ 23:59:59。但"今天"作为候选范围时，只能推荐晚于当前时刻的具体时间点；若当天已无合适时段，自动顺延到明天。
- **全天日程（`is_all_day=true`）**：不主动猜测事件类型。仅在以下情形才设 `is_all_day=true`：① 用户明确说是"全天 / 请一天假 / 休一天"；② 起止时间实际就是完整一天，即 `begin_time="YYYY-MM-DD 00:00:00"`、`end_time="同一天 23:59:59"`。其余情况（给了具体时刻、或时段不满整天）一律按普通定时日程处理，不设全天。
  - **格式必须落在同一天、从早到晚**：`begin_time="YYYY-MM-DD 00:00:00"`、`end_time="同一天 23:59:59"`。禁止写成次日 0 点（`YYYY-MM-DD+1 00:00:00`）——那样不带 `is_all_day` 会显示成"0 点到 0 点"，带了又会多占一天。
  - **跨多天的全天事件**拆成 N 条单日全天，每条仍是同一天 `00:00:00 ~ 23:59:59`，逐条调用 create 分别创建。
- **历史约束**：不能创建已完全过去的日程，推荐的时间必须晚于当前时刻。
- **时间格式**：统一 `YYYY-MM-DD HH:mm:ss`。
- **模糊时间表达**：遇到"上班后"、"下班前"等表达，必须用文字询问引导用户补全，禁止猜测。澄清后将结果沉淀为长期偏好（如"上班后"=9:30），后续同类表达直接复用。
- **时区处理**：默认不传，由服务端使用用户 vid 时区。用户明确指定时区时，传入 `timezone_id`（IANA 时区名称，如 `"America/New_York"`）+ `timezone_offset`（与 UTC 的偏移秒数）。传入的 `begin_time` / `end_time` 按日程时区解释为墙上时间，禁止自行换算。

> **长期记忆**：用户的时间偏好、常见主题偏好等，在首次明确后应记忆，减少后续重复追问。（时长不在此列：用户未指定时一律默认 1 小时、不追问。）

### 步骤 3.5：会议室预订分支（仅当用户有订房意图时触发）

> **触发条件**：用户提到"订会议室 / 在 1605 / 找个会议室 / 某栋办公楼的会议室"等订房意图时才走本步骤；没提则跳过，按普通日程创建。
>
> **前置**：本步骤依赖确定的 `begin_time` / `end_time`，必须在步骤 3 时间敲定之后执行（范围时间先经 freebusy 选定时段）。

会议室的查询接口（楼清单 + 可订性）定义在 [calendar-meeting-room](wecomcli-calendar-meeting-room.md)，按其编排执行，拿到 `meeting_room_id` 后回填到本创建的 `meeting_room_id` 参数。

> [!CAUTION]
> **五条硬性规则（不可跳过）：**
> 1. **先查询、后推荐、后创建**：要预订会议室时，`meeting_room_id` 必须来自 `rooms search` 返回的真实值，禁止跳过会议室查询直接 create，禁止凭记忆 / 上下文 / 猜测编造 `meeting_room_id`——没有先查到真实 ID 就不允许带 `meeting_room_id` 创建。同样地，在成功调用 `rooms search` 之前，禁止凭记忆 / 上下文 / 想象向用户罗列或推荐任何具体会议室（含用文字给出的候选、正文里的房间名 / 号 / 楼层 / 容量）——要让用户选会议室，必须先查到真实候选再组装选项。
> 2. **存在多个会议室必须让用户选**：当查询结果命中多个可选会议室（`recommendations` 条目数 > 1，或用户未指定具体会议室而返回了多个候选）时，必须用文字让用户从候选中选择，或让用户指定具体会议室，禁止自动替用户挑选（如默认取第一个）。
> 3. **会议室必须订房、且只传 `meeting_room_id`**：只要用户给的地点是会议室（"订会议室 / 在 1605 开 / 找个会议室 / xx 楼会议室"等），就必须走 `rooms search` 查到真实会议室并通过 `meeting_room_id` 参数传入创建；严格禁止把会议室名 / 房间号仅塞进 `location` 字段就创建（那样不会真正占用会议室）。预订成功后创建时**只传 `meeting_room_id`**（占用），**不需要再把会议室名重复填进 `location`**（会议室名由后端关联返回）。仅当用户给的是非会议室的普通地点（如"星巴克"）时，才只写 `location`、不走订房。
> 4. **优先先订房、后建程**：用户在创建时就提到会议室的，应先把会议室敲定（拿到用户确认的 `meeting_room_id`）再进入步骤 4 创建日程，本步骤（3.5）是步骤 4 的前置阻塞项，避免创建后会议室被抢占。若会议室查询 / 选择尚未完成（如等待用户在候选中选择、等待用户确认换楼或换时间），必须停在本步骤等待，不得提前调用 create。若创建时漏订或事后要换会议室，可走 [calendar-update](wecomcli-calendar-update.md) 传入新 `meeting_room_id` 改订（须先经 `rooms search` 确认 `status=bookable`），不必取消重建。
> 5. **指定会议室查无/不可用时必须先告知、禁止静默替换**：用户指定的会议室在 `target` 中找不到可订项（`target = []` 查无此名，或命中项均为 `unavailable` 该时段被占）时，必须先告知用户"未查到 / 无法预订你指定的『xxx』会议室"，再用文字让用户决定改订其他会议室或换时间。严禁静默用其他名称的会议室替代——即使 `recommendations` 仅 1 个候选也须经用户确认。"`recommendations` 仅 1 个可直接使用"只适用于用户未指定具体会议室（`target = []`）的情形。

1. 用户提了楼名 → `buildings list` + LLM 匹配得到 `building_city/name`；没提楼则跳过（后端用当前所在楼兜底）。
2. `rooms search`（带时间 + 可选楼 + 可选 `room_keyword` + `min_capacity = len(attendees) + 1`）。
3. 按结果决策：
   - 用户**指定了具体会议室**（传了 `room_keyword`）且 `target` 中有 `bookable` 项 → 取该项 `target[].room.meeting_room_id`（仅 1 个直接用，多个则用文字让用户选）。
   - 用户**指定的会议室** `target = []`（查无此名）或命中项均 `unavailable`（该时段被占）：先告知用户"未查到 / 无法预订你指定的『xxx』会议室"，再用文字让用户决定改订其他会议室或换时间。禁止用其他名称的会议室静默替代——`recommendations` 仅 1 个候选也须经用户确认才改订；`recommendations` 为空则告知后问是否跨楼（`expand_to_other_buildings=true` 重试）或换时间。
   - 用户**未指定具体会议室**（`target` 为 `[]`）：
     - `recommendations` 有**多个**候选 → 必须用文字让用户选择（候选取前 2~4 个，展示会议室 `name` + 楼层 + 容量，`meeting_room_id` 不得出现在文案中）。
     - `recommendations` 只有 **1 个**候选 → 可直接使用该候选的 `meeting_room_id`。
     - `recommendations` 为空 → 用文字问用户是否跨楼（`expand_to_other_buildings=true` 重试）或换时间。
4. 选定后将用户确认的 `meeting_room_id` 带入步骤 4 的 create，**只传 `meeting_room_id` 即可**（无需再把会议室名重复填进 `location`）。

> **换会议室走 update**：创建后要换会议室时，用 [calendar-update](wecomcli-calendar-update.md) 传入新 `meeting_room_id` 改订即可（须先经 `rooms search` 确认新会议室 `status=bookable`），无需取消重建。

### 步骤 4：执行创建

参数就绪后直接执行 create 命令，无需展示摘要或等待确认。

### 步骤 5：结果反馈

创建成功后拿到返回的 `schedule_id`，调用日程详情查询 `wecom-cli calendar schedules get --json '{"schedule_ids": ["<schedule_id>"]}'`（见 [calendar-agenda](wecomcli-calendar-agenda.md)）获取 `subject`、`begin_time`/`end_time`、`attendees[].name`，据此输出。**输出内容只包含三部分：主题、时间、参与人**，禁止输出其他任何内容和额外语句（不展示地点、提醒、`schedule_id` 等字段，也不附加说明、建议或寒暄）。参与人原样取接口返回的 `attendees[].name` 展示（完全与接口返回的格式保持一致，如返回 `zhangsan(张三)` 就展示 `zhangsan(张三)`），禁止暴露 userid；非东八区日程按 [wecomcli-calendar.md 输出格式规范](wecomcli-calendar.md) 的时区标注规则在时间后带上时区。

```
主题：{subject}
时间：{月日} {HH:mm}-{HH:mm}
参与人：{人名1}、{人名2}
```

## 典型场景

### 1. 简单创建

```
用户：帮我和张三约明天下午3点，聊半小时
→ 通过 wecomcli-contact.md 搜索「张三」→ 返回 2 个候选
→ 用文字询问：搜索到多个「张三」，请确认要邀请哪一位？（列出：张三 - 产品部 - 产品经理 / 张三 - 技术部 - 前端工程师）
→ 用户选择后，获得对应 userid
→ 用户未说明主题 → 用文字询问主题（禁止自行起名）：请问这个日程的主题是？（可举例：需求对齐 / 1:1 沟通 / 项目同步）
→ 组装参数：subject=<用户选定/填写的主题>，begin_time="<明天日期> 15:00:00"，end_time="<明天日期> 15:30:00"
→ 调用 create
```

### 2. 约多人（先查共同空闲）

```
用户：帮我约张三和李四明天下午聊一下
→ 通过 wecomcli-contact.md 批量搜索「张三」「李四」
→ 逐个处理：唯一匹配直接使用，多个匹配则用文字让用户选择
→ 调用 free list 拿明天下午的共同空闲 slots
→ 比较 slots[0].available_count 与 total_count 判断是全员空闲 / 降级 / 全忙
→ 挑前几个时段让用户选择
→ 用户选择方案 → 调用 create
```

详细的共同空闲查询与降级处理流程见 [calendar-freebusy](wecomcli-calendar-freebusy.md)。

## 注意事项

- **参与人 userid**：userid 为 `wo` 前缀的编码字符串（如 `woxxx`），`attendees` 传入时需组装为对象数组格式 `[{"userid": "woxxx"}]`。用户提供的是姓名时通过 `读取 wecomcli-contact.md` 解析为 userid；禁止把姓名当 userid 拼接，禁止凭记忆或猜测编造。
- **时间约束**：`begin_time` 必须晚于当前时间，否则创建失败；`end_time` 必须晚于 `begin_time`。禁止推荐或传入已过去的时间。
- **无时长上限**：`end_time` 只需晚于 `begin_time`，支持创建时长超过 24 小时、跨天或多天的单条定时日程，无需拆分（全天日程 `is_all_day=true` 仍按前文全天日程规范按单日 `00:00:00~23:59:59` 拆分，这是全天格式要求、与时长无关）。
- **不支持创建周期/重复日程**：API 仅支持创建单次日程。用户希望创建"每周/每月/每天重复"等周期日程时，**直接告知用户目前不支持创建周期日程，并引导用户在企业微信客户端手动预订周期日程**；不要尝试任何变通绕过的做法——包括但不限于：创建多条单次日程模拟周期效果、传入 `repeat_rule` 等参数表中未列出的字段、创建后再用 `update` 改造为周期日程。原因：API 层根本无此能力，伪造的"周期"日程在企微客户端中也无法被识别为周期，反而会造成多条独立日程难以批量管理。
- **会议室预订**：用户给的地点是会议室时，必须先经 [calendar-meeting-room](wecomcli-calendar-meeting-room.md) 的 `rooms search` 查到真实会议室并以 `meeting_room_id` 传入创建（见步骤 3.5），禁止把会议室名仅写进 `location`（那样不会真正占用会议室）；订房成功后只传 `meeting_room_id`、不重复填 `location`。仅当用户给的是非会议室的普通文本地点时才只写 `location`、不走订房。
- **直接执行**：参数补全后直接调用创建接口，无需展示摘要或等待确认。
- **禁止暴露 userid**：结果展示中只显示人名。
- **参数补全原则**：缺失的必填参数（`subject` / `begin_time` / `end_time`）以及参与人 `attendees` 必须用文字询问。其中 `subject` **仅在用户已明确说明主题时才算"已提供"可直接用，否则一律视为缺失、必须询问，禁止用对话语境自行提炼代填**。其余非必填参数用户未明确指定时不追问——有默认值的走默认值，无默认值的则不传该字段（如地点不填、提醒不设置）。

## 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| `begin_time` 早于当前时间 | 提示用户时间已过，请重新选择未来时间，不重试，等待用户修正 |
| `end_time` 不晚于 `begin_time` | 提示用户结束时间必须晚于开始时间，请调整 |
| wecomcli-contact.md 搜索无结果 | 用文字提示用户重新输入：`未找到「{姓名}」，请确认姓名是否正确` |
| wecomcli-contact.md 返回多个候选人 | 用文字询问用户（列出候选姓名 + 部门），等待用户选择后汇总继续 |
| 创建接口返回错误 | 检查参数格式，重新阅读本文档确认用法 |
| `meeting_room_taken`（会议室被抢占） | 查询通过后、create 前会议室被他人占走。用文字告知"{会议室名} 刚被占用"，让用户在「换会议室 / 换时间」二选一；选换会议室则重走步骤 3.5 的 `rooms search`，禁止静默重试同一会议室 |
| `meeting_room_not_found`（会议室无效） | `meeting_room_id` 不存在或上下文已过期，重新走步骤 3.5 的 `rooms search` |

## 参考

- [wecomcli-calendar.md](wecomcli-calendar.md) — 日程主文档
- [calendar-freebusy](wecomcli-calendar-freebusy.md) — 查询忙闲状态
- [calendar-meeting-room](wecomcli-calendar-meeting-room.md) — 会议室查询（订房时拿 `meeting_room_id`）
