Back to Skills
wecomteam/wecom-cliCheck passed

SKILL DETAIL

wecomcli-meeting

wecomteam/wecom-cli/wecomcli-meeting

This skill manages the full lifecycle of WeCom 'online meetings'—meetings that include an online meeting link (with meeting ID/join link, enabling remote or video participation). It covers creating, querying, searching, retrieving details (including meeting info, notes, and to-dos), fetching raw meeting transcripts (verbatim speech records), updating, and canceling. If the user wants a 'calendar event' without an online meeting link (including purely in-person meetings), use the wecomcli-calendar skill instead. When the user only says 'meeting' or 'arrange a meeting' without specifying whether it's a calendar event or an online meeting, you must first read this skill and follow its disambiguation process to ask the user for confirmation before proceeding—never assume and create directly. For ambiguous queries (e.g., 'what meetings are coming up'), query both calendar events and meetings and display combined results.

Installs · 175View source

Installation

npx skills add https://github.com/wecomteam/wecom-cli --skill wecomcli-meeting

Skill files

SKILL.md

Last synced · Aug 27, 2026

references/meeting-cancel.md
# 操作参考:取消会议

取消已创建的会议。**写操作**,参数就绪后直接执行。不预先按"是否本人创建"拦截,能否取消由接口返回结果判断。**暂不支持取消周期会议**,识别到周期会议时应告知用户并引导其在企业微信客户端操作(见下文工作流与约束)。

## 命令

```bash
wecom-cli meeting cancel --json '{...}'
```

## 请求参数

| 字段             | 类型   | 必填 | 说明                                |
| ---------------- | ------ | ---- | ----------------------------------- |
| `meeting_id`     | string | 是   | 会议 ID(来自 `list`/`search` 返回的 `meeting_id` 字段,长字符串,非 9 位会议号) |

**返回**:成功时返回空对象 `{}`,这是正常结果,不代表失败。收到空对象即可告知用户取消成功。

## 约束

- **不预先按"是否本人创建"拦截取消**,直接执行 `cancel`,能否取消由接口返回结果判断:返回空对象 `{}` 即成功;返回权限类错误则说明当前用户无权取消,告知用户并建议联系会议发起人
- **周期会议不支持取消**:检测到目标会议 `repeat_rule` 非空时,直接告知用户目前暂不支持取消周期会议,引导其在企业微信客户端操作,禁止改为整系列直接 cancel 等变通方式
- **取消会议后,其关联日程会被一并取消,禁止再对同一场调用 `schedule cancel`**;模糊取消时若同一场(主题+时间一致)在会议和日程两边都命中,只走 `meeting cancel` 一次即可

## 定位目标时的跨载体消歧(模糊取消)[REQUIRED]

用户说"取消那个会 / 取消 xx 会 / 取消 xx 会议 / 把那个会取消掉"等模糊表述、未明确是日程还是在线会议时,**不要只在会议里找**——「会」可能是含在线会议链接的会议,也可能是一条纯日程,只查一边会漏定位:

- **明确是在线会议**(提到入会链接 / 会议号 / 视频会议 / 腾讯会议 / 远程参会等专属特征)→ 只在本技能 `meeting search`/`list` 定位,走 `meeting cancel`。
- **明确是日程 / 安排**(说的是"日程 / 安排 / 我的日历"且不带在线会议特征)→ 改用 `读取 wecomcli-calendar 技能` 在日程里定位并 `schedule cancel`。
- **模糊无法判定** → 会议和日程**两边都查**:本技能 `meeting search`/`list` + `读取 wecomcli-calendar 技能` 用同样关键词 / 时间查日程,合并候选、按"主题 + 时间"去重(同一场两边都命中只保留一条),再用文字让用户**选定要取消的唯一一条**;选定后按其归属路由——是会议(或两边都命中的同一场)→ `meeting cancel`(会连带取消关联日程);是纯日程 → 改用 `读取 wecomcli-calendar 技能` 走 `schedule cancel`。

> **与查询消歧的区别**:查询时可以两边都查、都展示;但取消是**写操作,绝不能两边都直接取消**,模糊时必须先让用户确认唯一目标,再执行对应的 cancel。

## 工作流

```
用户发起取消意图
    |
    +-- 定位目标会议
    |   +-- 有关键词 → meeting search(不追问时间)
    |   +-- 有时间信息 → meeting list 按时间范围查询
    |   +-- 都没有 → 用文字询问引导用户补全缺失的参数
    |
    +-- 匹配结果处理
    |   +-- 唯一匹配 → 继续
    |   +-- 多条匹配 → 用文字让用户选择:
    |   |     文字提问:"找到多个匹配会议,请选择要取消的一个:"
    |   |     列出候选(如"项目评审 - 4月8日 14:00 / 项目评审 - 4月15日 14:00",最多 4 条;超出时展示前 4 条并提示用户缩小关键词)
    |   +-- 无匹配 → 建议修改关键词或扩大时间范围重试
    |
    +-- 状态检查(不做"是否本人创建"的前置拦截,直接进入状态/周期判断)
    |   +-- meeting_status = "init"(待开始)→ 继续
    |   +-- meeting_status = "started"(进行中)→ 告知用户会议正在进行中,无法取消;终止流程
    |   +-- meeting_status = "end"(已结束)→ 告知用户会议已结束,无需取消;终止流程
    |
      +-- 判断是否周期会议(依据 meeting get 返回的 repeat_rule)
    |   +-- repeat_rule 为空 → 非周期会议,直接进入确认
    |   +-- repeat_rule 非空 → 周期会议,终止操作,用文字告知用户:"目前暂不支持取消周期会议,请在企业微信客户端对该会议进行取消操作",禁止改为整系列直接 cancel 或其他变通方式
    |
    +-- 执行 cancel(不论会议由谁创建,都直接执行,不提前拒绝)→ 依返回结果判断:
          +-- 返回空对象 {} → 取消成功,告知结果
          +-- 返回权限类错误 → 说明当前用户无权取消该会议,告知用户并建议联系会议发起人
```

### 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| 接口返回无权取消(非发起人) | 直接执行 cancel 后依返回判断;返回权限错误时告知用户无权操作, 建议联系会议发起人 |
| 会议已结束 | 告知用户该会议已结束, 无需取消 |
| 会议进行中 | 告知用户该会议正在进行中, 无法取消 |
| 周期会议取消 | 目前暂不支持取消周期会议,告知用户并引导其在企业微信客户端对该会议进行取消操作 |
| 取消接口返回错误 | 检查 meeting_id 是否正确, 确认权限后重新尝试 |
| 找不到目标会议 | 建议通过搜索或列表重新定位, 参见 [meeting-search](meeting-search.md) |

## 示例请求

**取消单次会议**:
```json
{
  "meeting_id": "<meeting_id>"
}
```

## 典型场景

### 1. 取消普通会议

```
用户:帮我取消明天的项目评审会
→ 调用 meeting search(keywords=["项目评审"])
→ 找到 1 条 → 调用 meeting get 获取详情
→ meeting_status = "init"(可取消),非周期会议
→ 调用 cancel(不做是否本人创建的前置拦截)→ 返回 {} → 告知已取消
```

### 2. 取消周期会议(不支持)

```
用户:取消下周一的周会
→ 调用 meeting search(keywords=["周会"])→ 找到周期会议
→ 调用 meeting get → repeat_rule 非空(周期会议)
→ 不调用 cancel → 告知:目前暂不支持取消周期会议,请在企业微信客户端对该会议进行取消操作
```

### 3. 无权取消(由接口返回判断)

```
用户:帮我取消张三组织的评审会
→ 找到目标会议 → 不因非本人创建而提前拒绝,直接调用 cancel
→ 返回权限错误 → 告知用户:您没有该会议的取消权限,建议联系发起人张三操作。
```
references/meeting-create.md
# 操作参考:创建会议

## 命令

```bash
wecom-cli meeting create --json '{...}'
```

## 请求参数

| 字段                         | 类型    | 必填 | 说明                                                   |
| ---------------------------- | ------- | ---- | ------------------------------------------------------ |
| `subject`         | string  | 是   | 会议主题                                               |
| `begin_time`      | string  | 是   | 开始时间, 格式 `YYYY-MM-DD HH:mm:ss`, 必须晚于当前时间 |
| `end_time`        | string  | 是   | 结束时间, 格式 `YYYY-MM-DD HH:mm:ss`, 必须晚于 `begin_time`, 且与 `begin_time` 间隔不超过 24 小时。**用户未给出时长时默认为 `begin_time` 的 1 小时后(不追问)** |
| `attendees`       | array   | 否   | 参会人,对象数组,格式 `[{"userid": "woxxx"}, {"userid": "woyyy"}]`(`wo` 前缀)。用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid |
| `location`        | string| 否| 会议地点(文本)。**用户给的地点是某会议室时**:须先经 `rooms search` 预订(见步骤 4),预订成功后**只传 `meeting_room_id`、不再写 `location`**(会议室名由后端关联返回,无需在 `location` 里重复填充)。**用户给的地点不是会议室时**(如"星巴克""客户现场"):直接写入 `location`,不涉及 `meeting_room_id`。禁止把会议室名仅写进 `location` 却不订房——那样不会真正占用会议室 |
| `meeting_room_id` | string  | 否   | 会议室 ID,传入即触发后端"建会议 + 占会议室"原子操作。查询会议室拿此 ID 的能力不在本技能,须 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md)(`buildings list` + `rooms search`)。订会议室时只传本字段即可,**不需要再把会议室名重复填进 `location`**。**该 ID 仅工具链使用,禁止出现在用户回复正文** |
| `description`     | string  | 否   | 会议备注/描述                                          |
| `timezone`        | object  | 否   | 时区设置,用户指定时区时传入,未指定则不传。格式:`{"timezone_id": "Asia/Shanghai", "timezone_offset": 28800}`,`timezone_id` 为 IANA 时区标识,`timezone_offset` 为 UTC 偏移量(秒) |

> **会议室预订能力在 wecomcli-calendar 技能 [CRITICAL]**:本技能不直接定义会议室查询接口——用户提出"订会议室 / 在 1605 开 / 找个会议室 / 某栋楼的会议室"等诉求时,须 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md)(`buildings list` + `rooms search`)查到真实会议室,拿 `meeting_room_id` 传入本创建(见步骤 4)。禁止把会议室名仅写进 `location`(那样不会真正占用会议室),也禁止凭记忆 / 猜测编造 `meeting_room_id`。仅当用户给的是非会议室的普通文本地点时,才只写 `location`。

## 返回字段

| 字段           | 说明                     |
| -------------- | ------------------------ |
| `meeting_id`   | 会议唯一标识             |
| `meeting_link` | 入会链接, 可分享给参会人 |
| `meeting_code` | 9 位会议号               |

## 约束

- 开始时间必须晚于当前时间, 否则创建失败
- 结束时间 `end_time` 必须晚于 `begin_time`, 且与 `begin_time` 间隔不超过 24 小时(86400 秒)。用户要建超过 24h 的单场会议时,直接告知不支持并拒绝,禁止自行拆分成多场会议或用其他方式变通绕过;如确需多天安排,由用户明确拆分要求后再分别创建
- 参会人总数不超过 100 人
- **不支持创建周期/重复会议**:API 仅支持创建单次会议。用户希望创建"每周/每月/每天重复"等周期会议时,**直接告知用户目前不支持创建周期会议,并引导用户在企业微信客户端手动预订周期会议**;不要尝试任何变通绕过的做法——包括但不限于:批量调用 create 接口创建多条单次会议模拟周期效果、传入参数表中未列出的字段、创建后再用 update 改造为周期会议。原因:API 层根本无此能力,伪造的"周期"会议在企微客户端中也无法被识别为周期,反而会造成多条独立会议难以批量管理。
- userid(前缀为 `wo`)不接受姓名直接传入;用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid,禁止把姓名当 userid 拼接,禁止凭记忆或猜测编造
- 时区字段(`timezone`)仅在用户明确指定时区时传入,否则省略

## 工作流

### 正常路径

0. **日程 / 会议消歧(仅当用户说"会议/会/开会/约会/xx会"且未明确时)**:用户未明确是日程还是会议(会议含在线会议链接、可远程/视频参会)时,必须先用文字追问,禁止默认直接创建会议。已明确是会议的场景(如"发个入会链接""要会议号""视频会议""远程参会")时才跳过本步骤。

   > **注意 1**:用户只说"会议/会/开会"等泛称,本身不构成"明确"——这些词没有表明是日程还是会议,**禁止仅因 query 里有"会议"二字就默认走会议创建、跳过本步骤**,必须先用文字追问。
   >
   > **注意 2**:仅给出地点/会议室号的表述(如"在 1605 开会""到 A 座会议室碰一下""订个会议室开会")不构成"明确是会议"——会议室里同样可能只是纯线下安排,是日程还是会议仍未知。此类"只有地点"的表述仍需先用文字询问消歧,不要因为带了地点就跳过本步骤。
   > **问题与选项固定 [CRITICAL]**:消歧确认时,问题与可选项都必须原文照用、严格禁止修改任何内容——问题固定为 `"需要创建日程还是会议?"`,可选项固定为 `日程` / `会议`;不得改写问题措辞、增减或改写选项、翻译,或自行设计其他表述(如"在线会议 / 线上会议 / 视频会议 / 线下会议"等)。

   用文字向用户提问:`需要创建日程还是会议?(请回复:日程 / 会议)`

   - 用户答「会议」→ 留在本技能,继续步骤 1。
   - 用户答「日程」→ 停止本工作流,改用 `读取 wecomcli-calendar 技能` 创建日程。
   - 若用户同时要线下与远程参会,因含在线会议链接,留在本技能创建即可(创建会议会同时生成对应日程,无需再去 wecomcli-calendar 技能另建日程)。
1. **参数补全**:从对话中提取主题、时间、时长、参会人信息。
   - `subject` 缺失时,用文字询问会议主题。仅描述参会方式或动作的词(如「视频会议 / 开个会 / 会面 / 远程接入」)不构成有效 `subject`,一律按缺失处理走文字询问,禁止把它们当主题直接创建——否则用户事后补主题需额外调一次修改会议工具,效率非常低。文字提问如:`会议主题是什么?`(可举例"项目同步 / 需求评审 / 周例会 / 一对一沟通"供参考,最多举 4 个)
   - `begin_time` 缺失时,用文字一步询问,直接列出具体的"日期+时刻"候选项让用户选(结合当前时间动态推断,所有候选项必须晚于当前时刻,禁止使用"上午/下午/傍晚"等模糊表述,最多列 4 个)。文字提问如:`会议什么时候开始?`(候选按当前时刻动态生成、均须晚于现在,例如当前 19:40 可列 "明天 09:00 / 明天 14:00 / 明天 16:00 / 后天 09:00")
   - `end_time` / 时长缺失时,**默认时长 60 分钟(1 小时),不追问**,按 `end_time = begin_time + 60 分钟` 换算为结束时间。仅当用户明确说了时长(如"开半小时""聊俩小时")时按其值换算。
   - `attendees` 缺失时,用文字追问参会人,禁止默认创建无参会人的会议或自行猜测;地点、会议室等非必填参数用户未明确指定时不追问,也不传该字段。文字提问如:`需要邀请哪些人参会?`(可列出"仅自己"及根据对话语境补充的 1-3 个候选人名供参考)
2. **参会人解析**:上下文中已有合法 userid(`wo` 前缀)则直接使用,跳过本步骤;用户提供的是人名时,通过 `读取 wecomcli-contact 技能` 将所有姓名批量搜索,逐个关键词独立处理结果:
   - 某关键词唯一匹配 → 直接使用,无需确认
   - 某关键词多个匹配 → 用文字让用户选择:`搜索到多个「{姓名}」,请确认要邀请哪一位?` 并列出候选(来自 wecomcli-contact 技能搜索结果,如"张三 - 产品部 - 产品经理 / 张三 - 技术部 - 前端工程师",最多 4 条,超出取前 4 并提示用户缩小范围)
   - 某关键词无结果 → 用文字提示用户重新输入:`未找到「{姓名}」,请确认姓名是否正确`
   - 所有姓名确认完毕后,汇总 userid 组装为对象数组一并传入 `attendees`
3. **参会人忙闲检查(新建一律必做)[REQUIRED]**:新建会议的查询对象必含当前用户自己(`wo` 前缀),故创建前必须先查忙闲,避免约到冲突时间(**含只有自己的会议——避免约到自己已占用的时段**);**本步骤是步骤 5(调用创建接口)的前置阻塞项——未完成忙闲检查、或检测到冲突但未经用户拍板,一律禁止进入创建(仅接口失败的降级例外,见下)**;外部联系人(`wm` 前缀,忙闲不可查)不纳入查询对象、但**不因此跳过**整体检查。忙闲接口不在本技能 —— 须 `读取 wecomcli-calendar 技能` 的 [忙闲查询参考](../../wecomcli-calendar/references/calendar-freebusy.md),调 `free list`(窗口 ≤ 24h,跨天需分段;**查询对象 = 自己 + 其他内部参会人,新建会议时自己也要纳入,避免约到自己已占用的时段**):
   - **推荐时段长度 ≠ 会议时长(精确 / 范围时间均适用)**:忙闲返回的推荐时段只用于确定会议**开始时间**,其长度不代表会议时长;用户选定时段后,会议时长仍以用户明确指定的为准,用户未明确时长时一律默认 1 小时(`begin_time + 1h`),禁止把推荐时段的长度直接当作会议时长。
   - **精确时间**(用户已给定具体开始时间):用 `[begin_time, end_time]` 查忙闲。有人占线时,必须用文字让用户在「坚持这个时间 / 换一个时间」二选一,禁止自行改期或劝阻:`该时间段{姓名}有冲突,如何处理?(请回复:坚持这个时间 / 换一个时间)`
   - **范围时间**(如"明天下午"):调 `free list` 拿共同空闲 `slots`,按 `slots[0].available_count` 与 `total_count` 判断全员空闲 / 降级 / 全忙(详见忙闲查询参考),挑前几个时段让用户选定后再继续。
   - **接口失败**:告知"忙闲查询暂时不可用",确认时间后继续创建,不阻塞。
4. **会议室预订(仅当用户有订房意图时触发)**:用户提到"订会议室 / 在 1605 / 找个会议室 / 某栋楼的会议室"等意图时才走本步骤,没提则跳过。会议室的查询接口定义不在本技能 —— 须 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md),按其编排执行。**五条硬性规则不可跳过**:① **先查询、后推荐、后创建**——`meeting_room_id` 必须来自 `rooms search` 的真实返回值,禁止跳过查询直接 create,禁止凭记忆 / 猜测编造;且在成功调用 `rooms search` 之前,禁止凭记忆 / 上下文 / 想象向用户罗列或推荐任何具体会议室(含用文字给出的候选、正文里的房间名 / 号 / 楼层 / 容量),要让用户选会议室必须先查到真实候选再组装选项;② **存在多个会议室必须让用户选**——命中多个候选时必须用文字让用户选择或指定具体会议室,禁止自动替用户挑选;③ **会议室必须订房、且只传 `meeting_room_id`**——只要用户给的地点是会议室,就必须经 `rooms search` 查到真实会议室并通过 `meeting_room_id` 传入,严禁把会议室名 / 房间号仅塞进 `location` 字段就创建(那样不会真正占用会议室);预订成功后创建时**只传 `meeting_room_id`**(占用),**不需要再把会议室名重复填进 `location`**(会议室名由后端关联返回),仅当用户给的是非会议室的普通地点时才只写 `location`、不走订房;④ **优先先订房、后建会**——用户在创建时就提到会议室的,应先把会议室敲定(拿到用户确认的 `meeting_room_id`)再进入步骤 5 创建会议,本步骤是步骤 5 的前置阻塞项,避免创建后会议室被抢占。若会议室查询 / 选择尚未完成(如等待用户在候选中选择),必须停在本步骤等待,不得提前调用 create;⑤ **指定会议室查无/不可用必须先告知、禁止静默替换**——用户指定的会议室 `not_found`(查无此名)或 `unavailable`(被占)时,先告知用户"未查到 / 无法预订你指定的『xxx』会议室",再让用户决定改订其他会议室或换时间,禁止静默替代(候选仅 1 个也须用户确认)。若创建时漏订或事后要换会议室,可走 [meeting-update](meeting-update.md) 传入新 `meeting_room_id` 改订(须先经 `rooms search` 确认 `status=bookable`),不必取消重建。
   - 用户提了楼名 → `buildings list` + LLM 匹配得到楼;没提楼则跳过(后端用当前所在楼兜底)
   - `rooms search`(带已定 `begin_time`/`end_time` + 可选楼 + 可选 `room_keyword` + `min_capacity = len(attendees) + 1`)
   - 按结果决策:用户**指定了具体会议室**(传了 `room_keyword`)且 `target` 中有 `bookable` 项 → 取该项 `meeting_room_id`(仅 1 个直接用,多个则用文字让用户选);指定的会议室 `target=[]`(查无此名)或命中项均 `unavailable`(被占)时——先告知用户"未查到 / 无法预订你指定的『xxx』会议室",再用文字让用户决定改订其他会议室或换时间,禁止静默替代(候选仅 1 个也须用户确认);用户**未指定具体会议室**(`target` 为 `[]`)时——`recommendations` 有**多个**候选则必须用文字让用户选择,只有 **1 个**候选可直接使用,**为空**则问是否跨楼或换时间
   - 选定后将用户确认的 `meeting_room_id` 带入下一步的 create
5. **调用创建接口**:参数就绪后直接执行 `wecom-cli meeting create --json '{...}'`。
   > **创建前置门禁(CRITICAL)**:
   > ① **忙闲门禁**——新建会议查询对象必含当前用户自己(`wo` 前缀),故调用 create 前**必须已完成步骤 3 的忙闲检查**;若检测到冲突,必须已通过文字询问让用户在「坚持这个时间 / 换一个时间」中拍板。禁止在"未查忙闲"或"冲突未经用户决定"的情况下直接 create(仅忙闲接口失败时按降级继续,不阻塞)。
   > ② **会议室门禁**——若用户提到过会议室相关内容,则调用 create 前**必须已持有一个来自 `rooms search`、并经用户确认的真实 `meeting_room_id`**,且该 ID 已写入创建参数。只要"提到会议室但 `meeting_room_id` 仍为空",就禁止调用 create——先回到步骤 4 完成查询 / 选择拿到 ID。严禁以"先把会议建起来、忙闲/会议室随后补"的方式跳过本门禁。
6. **查询详情并展示**:使用返回的 `meeting_id` 调用 `meeting get`(见 [meeting-list](meeting-list.md))获取 `subject`、`begin_time`/`end_time`、`attendees[].name`。**创建成功后输出内容只包含三部分:主题、时间、参会人**,禁止输出其他任何内容和额外语句(不展示地点、会议室、会议号、入会链接、meeting_id 等字段,也不附加说明、建议或寒暄);参会人原样取接口返回的 `attendees[].name` 展示(完全与接口返回的格式保持一致,如返回 `zhangsan(张三)` 就展示 `zhangsan(张三)`),禁止暴露 userid。

### 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| `begin_time` 早于当前时间 | 提示用户时间已过, 请重新选择未来时间 |
| `end_time` 与 `begin_time` 间隔超过 24 小时 | 提示用户会议时长不可超过 24 小时, 请调整;禁止自行拆分成多场会议 |
| 参会人数超过 100 | 提示用户参会人数已达上限, 需减少人数 |
| wecomcli-contact 技能搜索无结果 | 用文字提示用户重新输入:`未找到「{姓名}」,请确认姓名是否正确` |
| wecomcli-contact 技能返回多个候选人 | 用文字询问用户(列出候选姓名 + 部门),等待用户选择后汇总继续 |
| 创建接口返回错误 | 检查参数格式, 重新阅读本文档确认用法 |
| `meeting_room_taken`(会议室被抢占) | 查询通过后、create 前被他人占走。用文字告知"{会议室名} 刚被占用",让用户在「换会议室 / 换时间」二选一;换会议室则重走步骤 4 的 `rooms search`,禁止静默重试同一会议室 |
| `meeting_room_not_found`(会议室无效) | `meeting_room_id` 不存在或上下文过期,重新走步骤 4 的会议室查询 |
| 换会议室需求 | 走 [meeting-update](meeting-update.md) 传入新 `meeting_room_id` 改订(须先经 `rooms search` 确认新会议室 `status=bookable`),无需取消重建 |

## 典型场景

### 1. 信息完整(正常路径)

```
用户:帮我约一个明天下午两点和张三的项目对齐会,30 分钟
→ 通过 wecomcli-contact 技能搜索「张三」→ 返回 2 个候选
→ 用文字询问:搜索到多个「张三」,请确认要邀请哪一位?(列出:张三 - 产品部 - 产品经理 / 张三 - 技术部 - 前端工程师)
→ 用户选择后,获得对应 userid
→ attendees 含他人内部成员(张三)→ 忙闲检查:用 [begin_time, end_time] 调 free list(自己 + 张三);无冲突则继续,占线则用文字让用户「坚持这个时间 / 换一个时间」
→ 调用 meeting create(subject="项目对齐", begin_time="<明天日期> 14:00:00", end_time="<明天日期> 14:30:00", attendees=[{"userid": "userid1"}])
→ 调用 meeting get 获取主题、时间、参会人姓名
→ 只展示三部分:主题、时间、参会人
```

### 2. 参数缺失(逐步补全)

```
用户:帮我开个会
→ 未明确是日程还是会议 → 用文字追问(请回复:日程 / 会议)
→ 用户答「会议」→ 留在本技能继续;若答「日程」→ 改用 wecomcli-calendar 技能
→ subject 缺失 → 用文字询问会议主题
→ begin_time 缺失 → 用文字询问开始时间(结合当前时间动态推荐候选项)
→ end_time 缺失 → 默认时长 1 小时(不追问),按 begin_time + 1h 推算
→ attendees 缺失 → 用文字追问参会人(地点、会议室等非必填项不追问)
→ attendees 含他人内部成员 → 忙闲检查(free list,占线则用文字问坚持/换时间)
→ 参数就绪 → 调用 meeting create → 展示结果
```

### 3. 通讯录多候选人

```
用户:帮我约张三、李四参加明天 10 点的需求评审,1 小时
→ 通过 wecomcli-contact 技能批量搜索「张三」「李四」
→ "张三" 返回 2 条(产品部 / 技术部)→ 用文字让用户选择
→ "李四" 返回 1 条 → 直接使用,无需确认
→ 汇总 userid → 忙闲检查:调 free list(自己 + 张三 + 李四)查 [begin_time, end_time];占线则用文字让用户「坚持这个时间 / 换一个时间」 → 调用 meeting create → 展示结果
```

## 示例请求

```json
{
  "subject": "产品需求评审",
  "begin_time": "<明天日期> 14:00:00",
  "end_time": "<明天日期> 15:00:00",
  "attendees": [
    {"userid": "userid1"},
    {"userid": "userid2"},
    {"userid": "userid3"}
  ],
  "description": "评审Q2需求文档",
  "meeting_room_id": "mrmxxxx",
  "timezone": {
    "timezone_id": "Asia/Shanghai",
    "timezone_offset": 28800
  }
}
```

> `meeting_room_id` 为可选,订会议室时才传,来自 `读取 wecomcli-calendar 技能` 的会议室查询(`rooms search`,见 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md));订会议室时只传 `meeting_room_id` 即可,无需再把会议室名重复填进 `location`(会议室名由后端关联返回)。`location`仅在用户给的是非会议室的普通文本地点时才填。

## 示例输出

> 创建成功后只输出三部分:主题、时间、参会人,禁止输出地点、会议号、入会链接等其他内容和额外语句。

```
主题:产品需求评审
时间:<明天日期> 14:00:00 - 15:00:00
参会人:张三、李四
```
references/meeting-list.md
# 操作参考:查询会议列表

> [!CAUTION]
> **`meeting get` 单次最多查询 10 个会议**:`meeting_ids` 数组长度上限为 10。超过 10 个 meeting_id 时必须分批多次调用(每批 ≤ 10),分别拿到结果后在 Agent 侧合并;禁止一次性传入 > 10 个 ID(会被服务端拒绝)。例如:拉取了 25 个 meeting_id,需拆成 10 + 10 + 5 三批。
>
> **`meeting list` 必须翻页到底**:`meeting list` 返回中只要 `has_more == true`,就必须携带 `next_cursor` 再次调用 list,循环直到 `has_more == false`,否则会漏数据。

## 命令

```bash
wecom-cli meeting list --json '{...}'
wecom-cli meeting get --json '{...}'
```

## 请求参数(list)

| 字段         | 类型    | 必填 | 说明                                                                                           |
| ------------ | ------- | ---- | ---------------------------------------------------------------------------------------------- |
| `begin_time` | string  | 否   | 查询区间开始时间, 格式 `YYYY-MM-DD HH:mm:ss`, 与 `end_time` 必须同时提供或同时不提供, 不可只传其一 |
| `end_time`   | string  | 否   | 查询区间结束时间, 格式 `YYYY-MM-DD HH:mm:ss`, 与 `begin_time` 必须同时提供或同时不提供, 不可只传其一 |
| `cursor`     | string  | 否   | 分页游标, 首次请求不传                                                                         |
| `limit`      | integer | 否   | 单次返回数量, 默认 20                                                                          |

## 返回字段(list)

> 返回结果分为两个列表: `created_meetings`(当前用户创建的会议)和 `attended_meetings`(当前用户参加但非创建的会议),两个列表结构相同。

| 字段                                           | 说明                                     |
| ---------------------------------------------- | ---------------------------------------- |
| `created_meetings[].meeting_id`                | 会议唯一标识                             |
| `created_meetings[].sub_meeting_id`            | 子会议 ID, 周期会议涉及                  |
| `created_meetings[].subject`                   | 会议主题                                 |
| `created_meetings[].begin_time`                | 会议开始时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `created_meetings[].end_time`                  | 会议结束时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `created_meetings[].attendee_count`            | 参会人数                                 |
| `created_meetings[].meeting_room`              | 会议室名称                               |
| `created_meetings[].location`                  | 会议地点                                 |
| `created_meetings[].is_repeat_meeting`         | 是否为周期性会议                         |
| `created_meetings[].timezone.timezone_id`      | 时区 ID, 如 `"Asia/Shanghai"`            |
| `created_meetings[].timezone.timezone_offset`  | 时区偏移量(秒), 如 28800               |
| `attended_meetings[].meeting_id`               | 会议唯一标识                             |
| `attended_meetings[].sub_meeting_id`           | 子会议 ID, 周期会议涉及                  |
| `attended_meetings[].subject`                  | 会议主题                                 |
| `attended_meetings[].begin_time`               | 会议开始时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `attended_meetings[].end_time`                 | 会议结束时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `attended_meetings[].attendee_count`           | 参会人数                                 |
| `attended_meetings[].meeting_room`             | 会议室名称                               |
| `attended_meetings[].location`                 | 会议地点                                 |
| `attended_meetings[].is_repeat_meeting`        | 是否为周期性会议                         |
| `attended_meetings[].timezone.timezone_id`     | 时区 ID, 如 `"Asia/Shanghai"`            |
| `attended_meetings[].timezone.timezone_offset` | 时区偏移量(秒), 如 28800               |
| `attended_meetings[].creator_name`             | 会议创建人名称                           |
| `created_meetings_count`    | `created_meetings` 数组元素数量  |
| `attended_meetings_count`           | `attended_meetings` 数组元素数量 |
| `next_cursor`                                  | 下一页游标, `has_more` 为 true 时有效    |
| `has_more`                                     | 是否还有更多数据                         |

## 请求参数(get)

> **输入格式强制要求**:`meeting_ids` 必须使用以下嵌套对象数组结构传入,不可简化为字符串数组:
> ```json
> {
>   "meeting_ids": [
>     {
>       "meeting_id": "会议ID",
>       "sub_meeting_id": "子会议ID"
>     }
>   ]
> }
> ```
> 每个元素必须是包含 `meeting_id`(必填)和可选 `sub_meeting_id` 的对象,**不得直接传字符串**。

| 字段                           | 类型   | 必填 | 说明                                                 |
| ------------------------------ | ------ | ---- | ---------------------------------------------------- |
| `meeting_ids`                  | array  | 是   | 会议 ID 列表,最少 1 个,最多 10 个。超过 10 个时必须分批请求,每批不超过 10 个。**每个元素必须是对象(含 `meeting_id` 字段),不可传字符串** |
| `meeting_ids[].meeting_id`     | string | 是   | 会议 ID(长字符串, 如 `mtkSFfCg...`), 非 9 位会议号 |
| `meeting_ids[].sub_meeting_id` | string | 否   | 子会议 ID, 周期会议需指定                            |

## 返回字段(get)

| 字段                                                            | 说明                                                                                                             |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `meetings[].meeting_id`                                         | 会议 ID                                                                                                          |
| `meetings[].sub_meeting_id`                                     | 子会议 ID, 周期会议当前子会议 ID                                                                                 |
| `meetings[].subject`                                            | 会议主题                                                                                                         |
| `meetings[].begin_time`                                         | 开始时间, 格式 `YYYY-MM-DD HH:mm:ss`                                                                             |
| `meetings[].end_time`                                           | 结束时间, 格式 `YYYY-MM-DD HH:mm:ss`                                                                             |
| `meetings[].current_user_enter_time`   | 当前调用用户的入会时间, 格式 `YYYY-MM-DD HH:mm:ss`, 取最早一次入会时间; **仅会议结束后返回, 未入会时为空** |
| `meetings[].current_user_quit_time`        | 当前调用用户的离会时间, 格式 `YYYY-MM-DD HH:mm:ss`, 取最晚一次离会时间; **仅会议结束后返回, 未入会时为空** |
| `meetings[].timezone.timezone_id`                               | 时区 ID, 如 `"Asia/Shanghai"`                                                                                    |
| `meetings[].timezone.timezone_offset`                           | 时区偏移量(秒), 如 28800                                                                                       |
| `meetings[].meeting_room`                                       | 会议室名称                                                                                                       |
| `meetings[].location`                                           | 会议地点                                                                                                         |
| `meetings[].description`                                        | 会议备注描述                                                                                                     |
| `meetings[].repeat_rule`                                        | 周期规则, 非周期会议为空                                                                                         |
| `meetings[].repeat_rule.repeat_type`                            | 周期类型: `"daily"`-每天, `"weekday"`-每个工作日, `"weekly"`-每周, `"biweekly"`-每两周, `"monthly"`-每月         |
| `meetings[].repeat_rule.repeat_days`                            | 重复天数                                                                                                         |
| `meetings[].repeat_rule.until_type`                             | 结束方式: `"by_date"`-按日期结束, `"by_times"`-按次数结束                                                        |
| `meetings[].repeat_rule.until_date`                             | 周期结束日期, 格式 `YYYY-MM-DD HH:mm:ss`(until_type=`"by_date"` 时有效)                                        |
| `meetings[].repeat_rule.until_times`                            | 结束次数(until_type=`"by_times"` 时有效)                                                                       |
| `meetings[].repeat_rule.version`                                | 重复规则版本, 默认 0                                                                                             |
| `meetings[].repeat_rule.first_begin_time`                       | 第一次开始时间, 格式 `YYYY-MM-DD HH:mm:ss`                                                                       |
| `meetings[].repeat_rule.first_end_time`                         | 第一次结束时间, 格式 `YYYY-MM-DD HH:mm:ss`                                                                       |
| `meetings[].repeat_rule.repeat_step`                            | 每 n(天/周/月)重复一次, 与 repeat_type 配合使用; 例如 repeat_step=3, repeat_type=`"daily"` 表示每 3 天重复一次 |
| `meetings[].meeting_status`                                     | 会议状态: `"init"`-未开始, `"started"`-进行中, `"end"`-已结束(终止态, 不回退)                                  |
| `meetings[].attendees`                                          | 参会人列表,扁平对象数组,企业内部成员与外部成员混在同一数组,通过 `is_external` 区分                             |
| `meetings[].attendees[].userid`                                 | 成员 userid,内部成员与外部联系人统一用此字段,由 `is_external` 区分(内部 `wo` 前缀、外部 `wm` 前缀) |
| `meetings[].attendees[].name`                                   | 参会人名称(如 `"zhangsan(张三)"`),展示时**原样取此字段**(完全与接口返回的格式保持一致),禁止展示 userid                                       |
| `meetings[].attendees[].is_external`                            | 是否为外部联系人(bool)                                                                                         |
| `meetings[].attendees[].is_attended`                            | 是否已入会(bool)                                                                                               |
| `meetings[].attendees[].duration`                               | 参会时长(秒)                                                                                                   |
| `meetings[].notes[].note_content`                               | 智能纪要文字内容(每个媒体房间一条,最多 10 条)                                                                 |
| `meetings[].notes[].todo_content`                               | 智能纪要待办内容                                                                                                 |
| `meetings[].note_url`                                           | 会议智能纪要 URL(如 `"https://xxx"`)。**仅当用户明确询问会议链接 / 纪要链接时才展示**,其余情况不主动输出;**只要展示链接,就必须用 markdown 跳转链接格式 `[会议主题](链接)`**,`[]` 内放该会议主题(`subject`,如 `[产品评审周会](https://xxx)`),禁止裸贴 URL、禁止用固定文案 |
| `meetings[].has_note_permission`                                | 是否有会议纪要权限                                                                                               |
| `meetings[].record_url`                                         | 会议录制地址 URL(如 `"https://xxx"`)。**仅当用户明确询问录制链接 / 回放链接时才展示**,其余情况不主动输出;**只要展示链接,就必须用 markdown 跳转链接格式 `[会议主题](链接)`**,`[]` 内放该会议主题(`subject`,如 `[产品评审周会](https://xxx)`),禁止裸贴 URL、禁止用固定文案 |
| `meetings[].is_except_meet`                                     | 是否是例外(周期会议中被单独修改的子会议)                                                                       |
| `meetings_count`         | `meetings` 数组元素数量            |

## 约束

- `begin_time` 和 `end_time` 必须同时提供或同时不提供,不可只传其中一个
- `meeting get` 单次传入 1~10 个会议 ID,超出需分批请求
- `meeting_ids` 必须传入对象数组(每个元素含 `meeting_id` 字段),**禁止简化为字符串数组**,如 `["id1","id2"]` 格式是错误的
- `meeting_id` 是长字符串(如 `mtkSFfCg...`), 不要误传 9 位数字会议号
- 参会人 `name` 由接口直接返回,正常无需通讯录反查;`name` 为空时用 `userid` 通过 `读取 wecomcli-contact 技能` 反查姓名,禁止直接展示 userid
- `notes` 字段包含文字版智能纪要内容,每个媒体房间一条,最多 10 条;`has_note_permission` 为 false 时不展示纪要内容
- **作为「会议总结」用途时 [REQUIRED]**:**只有用户纯粹地说"总结下 / 讲了啥 / 纪要发我 / 看待办"、不带任何自定义描述时,才走本 get 返回现成内容**;取目标字段——要纪要看 `notes[].note_content`、要待办看 `notes[].todo_content`;`has_note_permission == true` 且目标字段有实质内容时**直接返回该现成内容**(无需再调用转写原文接口);目标字段为空或 `has_note_permission == false` 时,转 [meeting-original-get](meeting-original-get.md) 拉转写原文兜底再总结。**只要用户附带了任何自定义要求/描述**(指定结构/角度/范围/风格/长度等),就跳过本 get、直接走原文加工(详见 [SKILL.md 核心场景 7](../SKILL.md))。
- **链接展示格式(`note_url` 会议纪要链接、`record_url` 会议录制链接)[CRITICAL]**:
  - **默认不展示**:`note_url` 仅当用户明确询问会议链接 / 纪要链接时才输出;`record_url` 仅当用户明确询问录制链接 / 回放链接时才输出;其余情况一律不主动输出。
  - **展示格式强约束**:**只要要展示这两类链接,就必须用 markdown 跳转链接格式 `[会议主题](链接)`**——`[]` 内放该会议主题(`subject`),`()` 内放对应 URL,如 `[产品评审周会](https://xxx)`。
  - **严禁**:直接裸贴 URL、用「点击查看」等固定文案代替会议主题、或以纯文本形式输出链接。

## 工作流

> **模糊查询前置 [REQUIRED]**:若本次是"会 / xx会 / xx会议 / 有什么会 / 最近有哪些会"等模糊查询(见 [SKILL.md 查询消歧](../SKILL.md)),除按下面拉会议 `list` 外,必须同时 `读取 wecomcli-calendar 技能` 用相同时间范围拉日程 `list`,把两边结果合并、分「(会议)」「(日程)」两部分汇总展示(同一场会议按主题 + 时间去重)——不论会议是否查到都要查日程。仅当用户明确指向在线会议(入会链接 / 会议号 / 视频会议等)时才只查会议。

### 正常路径

1. **确认时间范围**:从用户意图提取时间范围。
   - 用户已明确时间(如"今天"、"本周"、"4月15日到4月20日")→ 直接映射为 `begin_time`/`end_time`
   - 用户未明确时间(如"查一下我的会议")→ **使用默认策略:今天起未来 7 天**(无需追问)
   - 用户说"最近"或"近期" → 使用"过去 3 天到未来 7 天"
   - 用户只提供了模糊但有意义的范围(如"上个月")→ 解析为对应日期范围
2. **拉取会议列表**:调用 `wecom-cli meeting list --json '{...}'`, 获取 `created_meetings` 和 `attended_meetings`。若 `has_more` 为 true, 携带 `next_cursor` 继续翻页, 直至获取全部 `meeting_id`(用于统计总条数 N)。
3. **获取详情**:按开始时间升序排序后,**只对要展示的前 10 条** `meeting_id` 调用 `wecom-cli meeting get --json '{...}'` 反查详情(每批 ≤ 10 个);其余条数计入"还有 N 条",不必逐一取详情。
4. **展示参会人名称**:原样使用详情中 `attendees[].name`(完全与接口返回的格式保持一致);`name` 为空时用该参会人 `userid` 通过 `读取 wecomcli-contact 技能` 反查姓名,禁止直接展示 userid。
5. **顺序输出**:禁止 markdown 表格,每条会议作为独立条目顺序输出,每个条目只含主题/时间/参会人;超过 10 条只展示前 10 条,末尾告知"还有 N 条,需要查看更多吗?"。

### 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| 列表为空 | 不要直接告知"无会议"——企微里「会」有「含在线会议链接的会议」和「日程」两种载体,团队聚一起的会常落在日程而非会议。先主动 `读取 wecomcli-calendar 技能` 用相同时间范围(及用户提及的关键词/参会人)在日程里查一把:命中则一并呈现并说明「这是一条日程,未关联在线会议链接」;日程也无果,再告知用户该时间段内会议和日程均无安排,并建议扩大时间范围 |
| 翻页过程中出错 | 展示已获取的部分结果, 告知用户可能有更多未加载的数据 |
| 详情获取失败(部分 ID) | 展示成功获取的会议, 标注获取失败的条目 |
| 参会人 `name` 字段为空 | 用该参会人 `userid` 通过 `读取 wecomcli-contact 技能` 反查姓名;反查不到再告知该参会人信息暂时无法获取。禁止直接展示 userid |

## 翻页策略

- `meeting list` 使用 `cursor`/`next_cursor` + `has_more` 分页
- `has_more` 为 true 时必须携带 `next_cursor` 继续翻页, 直至获取全部数据
- 周期会议需同时传入 `sub_meeting_id` 才能获取正确的子会议详情

## 示例请求

**list 请求**:
```json
{
  "begin_time": "2026-04-07 00:00:00",
  "end_time": "2026-04-07 23:59:59",
  "limit": 20
}
```

**get 请求**:
```json
{
  "meeting_ids": [
    { "meeting_id": "<meeting_id_1>" },
    { "meeting_id": "<meeting_id_2>", "sub_meeting_id": "<sub_meeting_id>" }
  ]
}
```

## 典型场景

### 1. 明确指定时间范围

```
用户:帮我看看今天有什么会议
→ 用户已明确"今天",直接映射:begin_time=今天 00:00:00,end_time=今天 23:59:59
→ 调用 meeting list 获取 created_meetings + attended_meetings 列表
→ 按开始时间升序,对前 10 条调用 meeting get 反查参会人姓名
→ 顺序输出每条会议(主题/时间/参会人,禁止 markdown 表格):

1. 项目复盘
   时间:4月7日(周二)09:00-10:00
   参会人:赵六、钱七

2. 产品评审
   时间:4月7日(周二)14:00-15:00
   参会人:张三、李四、王五
```

### 2. 未指定时间范围,使用默认策略

```
用户:帮我看看有什么会议
→ 未指定时间范围,直接使用默认策略:今天起未来 7 天
   begin_time = 今天 00:00:00,end_time = 7 天后 23:59:59
→ 调用 meeting list,对前 10 条调用 meeting get 反查参会人姓名
→ 顺序输出每条会议(主题/时间/参会人,禁止 markdown 表格),超过 10 条只展示前 10 条 + "还有 N 条,需要查看更多吗?"
```

### 3. 查询结果为空(用日程兜底)

```
用户:帮我看看这周有什么会
→ 用户已明确"这周",映射为本周一 00:00:00 ~ 本周日 23:59:59
→ 调用 meeting list → created_meetings 和 attended_meetings 均为空
→ 软性兜底:「会」在企微可能是日程,主动 `读取 wecomcli-calendar 技能` 用同样时间范围在日程里查一把
  - 日程命中 → 一并呈现并说明:在「日程」里找到了本周的安排(这是日程,未关联在线会议链接),随后展示日程列表
  - 日程也无果 → 告知用户:本周(4月7日-4月13日)会议和日程里都没有安排。
```

references/meeting-original-get.md
# 操作参考:查询会议转写原文

> [!CAUTION]
> **转写原文 ≠ 智能纪要**:本接口返回的是逐句原始发言记录(`original_data`,含时间戳 + 说话人),**不是** `meeting get` 里经 AI 总结的 `notes`。用户要"纪要 / 要点 / 待办"用 `meeting get`;要"原话 / 逐字记录 / 完整对话 / 转写"才用本接口,二者禁止相互替代。
>
> **输出方式取决于调用目的**:用户要的是"原话/逐字记录/转写"时,`original_data` **原样输出**(下方约束的默认要求);但当本接口是被「会议总结」场景调用(get 无现成纪要/待办需兜底,或用户带自定义总结要求,详见 [SKILL.md 核心场景 7](../SKILL.md))时,`original_data` 作为**素材**可按默认或用户指定的结构加工总结,不受"原样输出"约束限制。
>
> **必须翻页到底**:返回 `has_more == true` 时,必须携带 `next_cursor` 再次调用,循环直到 `has_more == false`,并把各页 `original_data` 按返回顺序拼接,否则会漏掉后半段转写。

## 命令

```bash
wecom-cli meeting original get --json '{...}'
```

## 请求参数

| 字段             | 类型    | 必填 | 说明                                                                                                                     |
| ---------------- | ------- | ---- | ------------------------------------------------------------------------------------------------------------------------ |
| `meeting_id`     | string  | 是   | 会议 ID(`mt` 前缀长字符串,如 `mtkSFfCg...`),非 9 位数字会议号                                                          |
| `sub_meeting_id` | string  | 否   | 子会议 ID,周期会议查某一场时指定                                                                                          |
| `media_index`      | integer | 否   | 媒体索引,指定拉取第几段转写,从 0 开始。**不传则返回全部段的转写**;仅当用户明确要"第 N 段"时才传 `N-1`(第 1 段传 0、第 2 段传 1) |
| `cursor`         | string  | 否   | 分页游标,首次请求不传                                                                                                     |
| `limit`          | integer | 否   | 每页数量,默认 100,上限 500                                                                                               |

## 返回字段

| 字段          | 说明                                                                    |
| ------------- | ----------------------------------------------------------------------- |
| `media_index`   | 当前返回的是第几段转写的原文                                            |
| `original_data`    | 转写原文文本,逐行格式 `序号 时间 说话人(姓名): 内容`,多行以换行符分隔 |
| `next_cursor` | 下一页游标,`has_more` 为 true 时有效                                    |
| `has_more`    | 是否还有更多数据                                                        |

## 约束

- **前置依赖**:需先通过 `list` / `search` 定位到 `meeting_id`(周期会议还需 `sub_meeting_id`)。
- **`media_index` 默认不传**:不传时接口返回全部段的转写;**只有用户明确指定"第 N 段"时才传 `N-1`**(从 0 开始计数),禁止在用户未指定时自行传入或主动追问"要哪一段"。
- `limit` 未传按 100,超过 500 按 500 处理。
- `meeting_id` 是 `mt` 前缀长字符串,禁止误传 9 位会议号(`meeting_code`)。
- 无权限 / 无转写等异常由接口返回错误信息,按 SKILL.md 通用错误处理呈现,不静默失败。

## 工作流

### 正常路径

1. **定位会议**:从上下文或 `list` / `search` 取得 `meeting_id`(周期会议带 `sub_meeting_id`)。
2. **确定段落**:用户明确指定"第 N 段" → `media_index = N-1`;**未指定 → 不传 `media_index`**(接口返回全部段),不主动追问。
3. **拉取转写**:调用 `wecom-cli meeting original get --json '{...}'`。
4. **翻页拼接**:`has_more == true` 时携带 `next_cursor` 续拉,直到 `false`,按返回顺序拼接 `original_data`。
5. **输出**:
   - **要原话/逐字记录**(默认)→ 保留时间戳 + 说话人的逐行格式,**不改写、不总结、不裁剪**。
   - **作为「会议总结」兜底或带自定义要求**(见 [SKILL.md 核心场景 7](../SKILL.md))→ 以拼接后的 `original_data` 为素材,按默认或用户指定结构加工总结。

### 异常路径

| 异常情况        | 处理方式                                                                     |
| --------------- | ---------------------------------------------------------------------------- |
| 接口返回错误    | 原样呈现错误含义(如无权限 / 会议不存在),并给出可行建议,不静默失败          |
| `original_data` 为空 | 告知该会议暂无转写原文(可能未开启转写、会议未开始或该段无内容)              |
| 翻页中途出错    | 展示已拼接的部分,并提示内容可能不完整                                        |

## 翻页策略

- 使用 `cursor` / `next_cursor` + `has_more` 分页;`has_more` 为 true 时必须携带 `next_cursor` 续拉,直至 `false`。
- 各页 `original_data` 按返回顺序拼接为完整转写文本。

## 示例请求

**默认(返回全部段转写)**:
```json
{ "meeting_id": "<meeting_id>", "limit": 100 }
```

**指定第 2 段 + 周期会议某场**:
```json
{ "meeting_id": "<meeting_id>", "sub_meeting_id": "<sub_meeting_id>", "media_index": 1, "limit": 100 }
```

## 典型场景

### 1. 查会议转写原文

```
用户:把上午产品评审会说了什么原话发我
→ 先 list/search 定位到该会议 meeting_id
→ 用户未指定段落,不传 media_index(接口返回全部段)
→ 调用 meeting original get,has_more 时带 next_cursor 翻页到底
→ 按序拼接 original_data,原样输出逐行转写(不总结、不改写)
```

### 2. 指定第几段

```
用户:这个会第二段转写发我
→ 用户明确"第二段" → media_index = 1(从 0 开始)
→ 调用 meeting original get,翻页到底后原样输出
```
references/meeting-search.md
# 操作参考:搜索会议

按关键词搜索会议,支持时间范围过滤和分页。只读操作。

## 命令

```bash
wecom-cli meeting search --json '{...}'
```

## 请求参数

| 字段         | 类型     | 必填 | 说明                                                                |
| ------------ | -------- | ---- | ------------------------------------------------------------------- |
| `keywords`   | string[] | 是   | 搜索关键词数组, 长度 ≤ 10, 用于匹配会议主题、参会人姓名、会议纪要内容、会议室名称等信息, 可与时间范围并用。支持多关键词组合逻辑:**数组多个元素之间 = OR**(命中任意一个即搜索);**单个元素内空格分隔 = AND**(必须同时命中所有词)。示例:`["周会 项目", "评审"]` 表示匹配"同时包含'周会'和'项目'"或"包含'评审'"的会议 |
| `begin_time` | string   | 否   | 搜索的开始时间, 限定搜索范围的起始时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `end_time`   | string   | 否   | 搜索的结束时间, 限定搜索范围的截止时间, 格式 `YYYY-MM-DD HH:mm:ss` |
| `cursor`     | string   | 否   | 分页游标, 首次查询不填, 后续翻页使用上次返回的 `next_cursor`        |
| `limit`      | number   | 否   | 每页数量, 固定传 `20`                                               |

## 返回字段

| 字段                        | 说明                                     |
| --------------------------- | ---------------------------------------- |
| `meetings[].meeting_id`     | 会议 ID                                  |
| `meetings[].sub_meeting_id` | 子会议 ID, 周期会议涉及                  |
| `meetings[].subject`        | 会议主题                                 |
| `meetings[].begin_time`     | 会议开始时间                             |
| `meetings[].end_time`       | 会议结束时间                             |
| `meetings[].attendee_count` | 参会人数量                               |
| `meetings[].meeting_room`   | 会议室名称                               |
| `meetings[].location`       | 地点                                     |
| `next_cursor`               | 下一页游标, 传入下次请求的 `cursor` 字段 |
| `has_more`                  | 是否还有更多数据, `true` 表示可继续翻页  |
| `meetings_count` | `meetings` 数组元素数量 |

> 每次固定返回 20 条数据(`limit` 固定传 `20`)。

## 约束

- 有关键词时不追问补全时间, 直接搜索
- `keywords` 为数组类型, 即使只有一个关键词也需包装为数组, 如 `["周会"]`
- 翻页时, 通过 `has_more` 判断是否还有更多数据; `has_more: false` 时停止翻页

## 意图分类

在处理搜索结果前,需先判断用户的意图类型:

| 意图类型 | 典型表达 | 判断依据 |
|---------|---------|---------|
| **定位型** | "找找上周的周会"、"搜索下项目评审的会议" | 想定位某一个特定会议,后续要查详情/取消/更新等 |
| **浏览型** | "我有哪些项目评审会议"、"列一下所有关于项目的会议" | 使用"有哪些"、"列出"、"所有"等表述,想查看全部匹配结果 |

## 接口选择规则

1. **有会议名称/关键词 → `search`**:用户提到会议主题/关键词时,不追问时间,直接搜索。
2. **无关键词、只给时间或泛泛浏览 → `list`**:用户只说时间(如"今天有什么会")或泛泛地说"看看我的会议"时,改用 [meeting-list](meeting-list.md) 按时间范围查询。
3. **要详情 → `get`**:`list`/`search` 返回摘要。需会议状态、参会人、入会链接等时,用 `get` 补充。
4. **与某人相关 → 优先 `search`**:寻找与某人相关的会议(如"我和张三开的会")时,优先用 `search`(把人名作为 `keywords` 匹配参会人),而非 `list` 拉全量再过滤。


## 工作流

> **模糊搜索前置 [REQUIRED]**:若用户搜的是"会 / xx会 / xx会议"等模糊目标(非明确在线会议,见 [SKILL.md 查询消歧](../SKILL.md)),除按下面搜会议外,必须同时 `读取 wecomcli-calendar 技能` 用同样关键词搜日程,把两边结果合并、分「(会议)」「(日程)」两部分汇总展示——不论会议是否搜到都要搜日程。仅当用户明确指向在线会议时才只搜会议。

### 正常路径

1. **提取关键词**:从用户意图提取搜索关键词,组装为字符串数组。缺失时必须用文字询问引导用户补全,禁止猜测或使用默认值(如"帮我搜一下会议"→ 用文字引导用户补全搜索关键词)
2. **判断意图类型**:根据"意图分类"表判断是定位型还是浏览型
3. **搜索会议**:调用 `wecom-cli meeting search --json '{...}'`, 传入 `keywords` 数组(固定带上 `"limit": 20`)和可选的时间范围
4. **按意图处理结果**:
   - **定位型**:参见下方"异常路径 - 搜索返回多个结果"
   - **浏览型**:自动翻页拉取全部数据(参见"翻页策略 - 浏览型自动翻页")用于统计总条数;按开始时间排序后,只对要展示的前 10 条 `meeting_id` 调用 `meeting get` 反查参会人姓名,再顺序输出每条会议(主题/时间/参会人,禁止 markdown 表格),超过 10 条只展示前 10 条并告知"还有 N 条,需要查看更多吗?"
   - **无结果**:按用户提供的关键词/时间无法搜索到会议时,不要立即告知"没找到",先按"异常路径 - 搜索无结果"主动改用日程查询兜底
5. **获取详情**(仅定位型需要):用搜索结果中的 `meeting_id` 调用 `wecom-cli meeting get --json '{"meeting_ids": [{"meeting_id": "<meeting_id>"}]}'` 获取完整信息(注意 `meeting_ids` 为数组格式)

### 翻页策略

- 首次查询不传 `cursor`,固定带上 `"limit": 20`
- 需要翻页时: 携带上次返回的 `next_cursor` 作为 `cursor`
- 到达边界时: `has_more: false` 表示没有更多数据,停止翻页

**浏览型自动翻页**:浏览型意图下,若 `has_more: true`,自动携带 `next_cursor` 继续请求下一页,循环至 `has_more: false` 为止,将所有页数据合并后一次性展示,无需用户确认每次翻页。

### 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| 搜索无结果 | 先建议修改关键词或扩大时间范围;同时主动 `读取 wecomcli-calendar 技能` 用同样关键词在日程里搜一把——企微里「会」有「含在线会议链接的会议」和「日程」两种载体,团队聚一起的会常落在日程而非会议。命中则一并呈现并说明「这是一条日程」,仍无果再告知两边都没有 |
| 定位型 - 搜索返回多个结果 | 用文字让用户指定目标会议:`搜索到多个匹配会议,请选择要操作的一个:`(列出如"项目评审 - 4月8日 14:00 / 项目评审 - 4月15日 14:00",最多 4 条;超出时展示前 4 条并提示用户缩小关键词) |
| 浏览型 - 搜索返回多个结果 | 自动翻页拉全部统计总数;按时间排序,对前 10 条 `meeting get` 反查参会人姓名,顺序输出主题/时间/参会人(禁止 markdown 表格),超过 10 条只展示前 10 条 + "还有 N 条,需要查看更多吗?" |

## 搜索结果的下一步

搜索结果中的 `meeting_id` 可用于后续操作:

- 获取详情:`wecom-cli meeting get --json '{"meeting_ids": [{"meeting_id": "<meeting_id>"}]}'`
- 取消会议:参见 [meeting-cancel](meeting-cancel.md)

## 示例请求

**基础搜索**:
```json
{
  "keywords": ["项目评审"],
  "limit": 20
}
```

**带时间范围搜索**:
```json
{
  "keywords": ["项目评审"],
  "begin_time": "2026-03-01 00:00:00",
  "end_time": "2026-03-31 23:59:59",
  "limit": 20
}
```

**翻页请求**:
```json
{
  "keywords": ["项目评审"],
  "cursor": "<next_cursor>",
  "limit": 20
}
```

## 典型场景

### 1. 定位型 - 搜索特定会议

```
用户:帮我找找上周的周会
→ 意图判断:定位型(想找某个特定会议)
→ 提取关键词"周会",不追问时间,直接搜索
→ 调用 meeting search(keywords=["周会"],limit=20)
→ 找到 2 条匹配,用文字让用户确认:搜索到多个匹配会议,请选择要操作的一个?(列出:周会 - 4月8日 10:00 / 周会 - 4月1日 10:00)
→ 用户选择 → 调用 meeting get 获取详情展示
```

### 2. 浏览型 - 查看全部匹配会议

```
用户:我有哪些项目评审会议
→ 意图判断:浏览型("有哪些"表述,想查看全部列表)
→ 提取关键词"项目评审",调用 meeting search(keywords=["项目评审"],limit=20)
→ 返回 15 条,has_more: true
→ 自动携带 next_cursor 继续请求下一页,循环至 has_more: false,合并统计总条数(共 15 条)
→ 按开始时间排序,对前 10 条调用 meeting get 反查参会人姓名
→ 顺序输出每条会议(主题/时间/参会人,禁止 markdown 表格),超过 10 条只展示前 10 条:

    1. 项目评审周会
       时间:11月13日(周三)15:00-16:00
       参会人:张三、李四

    2. 项目评审阶段汇报
       时间:11月25日(周一)16:00-17:00
       参会人:王五、赵六
    ...
    还有 5 条,需要查看更多吗?
```

### 3. 搜索无结果

```
用户:找一下项目启动会
→ 调用 meeting search(keywords=["项目启动会"],limit=20)→ 无结果
→ 软性兜底:「会」在企微可能是日程,主动 `读取 wecomcli-calendar 技能` 用关键词"项目启动会"在日程里搜一把
  - 日程命中 → 一并呈现并说明:在「日程」里找到了"项目启动会"(这是一条日程,未关联在线会议链接),随后展示日程摘要
  - 日程也无果 → 告知用户:会议和日程里都未找到"项目启动会"。
    建议:1) 尝试缩短关键词(如"启动会")2) 确认名称是否正确
```
references/meeting-update.md
# 操作参考:更新会议

更新已创建会议的信息,包括主题、时间、参会人、地点等。**写操作**,参数就绪后直接执行。不预先按"是否本人创建"拦截,能否修改由接口返回结果判断。**暂不支持更新周期会议**,识别到周期会议时应告知用户并引导其在企业微信客户端操作(见下文工作流与约束)。

## 命令

```bash
wecom-cli meeting update --json '{...}'
```

## 请求参数

| 字段 | 类型 | 必填 | 说明 |
| ---- | ---- | ---- | ---- |
| `meeting_id` | string | 是 | 会议 ID(来自 `list`/`search` 返回的 `meeting_id` 字段,长字符串,非 9 位会议号) |
| `subject` | string | 否 | 新的会议主题 |
| `begin_time` | string | 否 | 新的开始时间(格式 YYYY-MM-DD HH:mm:ss) |
| `end_time` | string | 否 | 新的结束时间(格式 YYYY-MM-DD HH:mm:ss) |
| `add_attendees` | array | 否 | 新增参会人列表,对象数组,格式 `[{"userid": "woxxx"}]` |
| `remove_attendees` | array | 否 | 移除参会人列表,对象数组,格式 `[{"userid": "woxxx"}]` |
| `location` | string | 否 | 新的会议地点(文本)。用户给的是**会议室**时须走 `meeting_room_id` 改订(见工作流「会议室变更解析」),不要把会议室名仅写进 `location`;用户给的是**非会议室的普通文本地点**时直接写入 `location` |
| `meeting_room_id` | string | 否 | 会议室 ID,传入预定(改订)会议室。用户要更换会议室时,须先经 `rooms search`(会议室查询接口定义在 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md))查询新会议室状态,确认`status=bookable` 可用后才传入新的 `meeting_room_id`;ID 仅工具链使用,禁止出现在用户回复正文 |
| `description` | string | 否 | 新的会议备注 |

## 返回字段

| 字段 | 说明 |
| ---- | ---- |
| `meeting_id` | 会议 ID |
| `sub_meeting_id` | 子会议 ID(周期会议时返回) |
| `subject` | 更新后的会议主题 |
| `begin_time` | 更新后的开始时间 |
| `end_time` | 更新后的结束时间 |
| `attendees` | 更新后的完整参会人列表,扁平对象数组,每项含 `userid` / `name` / `is_external`;内部成员与外部联系人统一用 `userid`,由 `is_external` 区分。展示取 `name`,禁止展示 userid |
| `attendees_count` | `attendees` 数组元素数量 |
| `location` | 更新后的会议地点 |
| `description` | 更新后的会议备注 |

## 约束

- **不预先按"是否本人创建"拦截修改**,直接执行 `update`,能否修改由接口返回结果判断:返回更新后的字段即成功;返回权限类错误则说明当前用户无权修改,告知用户并建议联系会议发起人
- 只需传入要修改的字段,未传入字段保持原值不变
- **周期会议不支持更新**:检测到目标会议 `repeat_rule` 非空时,直接告知用户目前暂不支持更新周期会议,引导其在企业微信客户端操作,禁止逐场 `update` 拼凑或改为取消重建等变通方式
- 修改时间时 `end_time` 必须晚于 `begin_time`
- **更换会议室**:用户要换会议室时,`meeting_room_id` 必须先经 `rooms search` 查询、确认新会议室 `status=bookable` 可用后才传入;禁止跳过查询凭记忆/猜测直接传,禁止把会议室名仅写进 `location`(那样不会真正占用会议室)。会议室查询接口须 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md)
- userid(前缀为 `wo`)不接受姓名直接传入;用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid,禁止把姓名当 userid 拼接,禁止凭记忆或猜测编造

## 工作流

```
用户发起更新意图
    |
    +-- 定位目标会议
    |   +-- 有关键词 → meeting search(不追问时间)
    |   +-- 有时间信息 → meeting list 按时间范围查询
    |   +-- 都没有 → 用文字询问引导用户补全信息
    |
    +-- 匹配结果处理
    |   +-- 唯一匹配 → 继续
    |   +-- 多条匹配 → 用文字让用户选择:
    |   |     文字提问:"找到多个匹配会议,请选择要修改的一个:"
    |   |     列出候选(如"项目评审 - 4月8日 14:00 / 项目评审 - 4月15日 14:00",最多 4 条)
    |   +-- 无匹配 → 建议修改关键词或扩大时间范围重试
    |
    +-- 判断是否周期会议(依据 meeting get 返回的 repeat_rule)
    |   +-- repeat_rule 为空 → 非周期会议,直接收集修改内容,执行更新
    |   +-- repeat_rule 非空 → 周期会议,终止操作,用文字告知用户:"目前暂不支持更新周期会议,请在企业微信客户端对该会议进行修改",禁止逐场 update 拼凑或改为取消重建
    |
    +-- 参会人变更解析(如有)
|   +-- 上下文中已有合法 userid(`wo` 前缀)→ 直接使用,跳过搜索
|   +-- 用户提供的是姓名 → 通过 `读取 wecomcli-contact 技能` 批量搜索所有新增/移除的人名
    |   |   +-- 某关键词唯一匹配 → 直接使用,无需确认
    |   |   +-- 某关键词多个匹配 → 用文字让用户选择(列出姓名 + 部门):
    |   |   |     文字提问:"搜索到多个「{姓名}」,请确认要操作哪一位?"
    |   |   |     列出候选(如"张三 - 产品部 - 产品经理 / 张三 - 技术部 - 前端工程师",最多 4 条;超出取前 4 条并提示用户可进一步缩小范围)
    |   |   +-- 某关键词无结果 → 用文字提示用户确认人名是否正确,停止执行
    |   +-- 汇总全部 userid → 组装 add_attendees / remove_attendees(对象数组 [{"userid": "woxxx"}])
    |
    |   > **关键约束**:只要存在多个候选人,必须等用户选择后才能继续,不得自动选取任何一个。
    |
    +-- 会议室变更解析(如用户要换会议室)
    |   +-- 确定查询时段:用会议起止时间;若本次同时改时间,用改后的新时段
    |   +-- 读取 wecomcli-calendar 技能的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md) → rooms search 查新会议室状态
    |   |   +-- target 中有 bookable 项 → 取该项 target[].room.meeting_room_id(多个 bookable 时用文字让用户选)
    |   |   +-- 指定会议室 target=[](查无此名)/ 命中项均 unavailable(被占)→ 必须先告知用户"未查到/无法预订你指定的『xxx』会议室",
    |   |   |       再用文字让用户决定是否改订其他会议室或换时间;禁止用其他名称会议室静默替代(候选仅 1 个也须用户确认)
    |   |   +-- 未指定具体会议室(target=[]):
    |   |   |   +-- recommendations 多个 → 用文字让用户选(禁止自动取第一个)
    |   |   |   +-- recommendations 仅 1 个 → 可直接使用
    |   |   |   +-- recommendations = [] → 告知无可用会议室,引导换楼或换时间
    |   +-- 拿到用户确认的、可用的 meeting_room_id → 传入 update
    |   > **关键约束**:新会议室未经 rooms search 确认 bookable 之前,禁止传 meeting_room_id 调 update。
    |
    +-- 时间/参会人忙闲检查(改时间或加参会人时必做)[REQUIRED]
    |   +-- 触发条件:本次修改了 begin_time/end_time,或新增了参会人(add_attendees)
    |   +-- 查询对象与时段:核心原则是排除"因本会议占用而必然忙碌"的时段,避免自冲突误报 [CRITICAL]
    |   |     ——【已在本会议中的人】(自己/创建者 + 已有参会人)只查"与本会议当前时段【不重叠】"的时间,【新增参会人】才查完整目标时段。分三种情况:
    |   |   +-- ① 只加人、不改时间 → 仅对【新增参会人 add_attendees 中的内部成员】查【会议原时段】;
    |   |   |     绝不把当前用户(自己/创建者)及已有参会人纳入——他们正被本会议占用、必然显示"忙",是误报
    |   |   |     (用户本意就是让别人加入自己这个已定时间的会议)
    |   |   +-- ② 改时间且新旧时段【不重叠】(平移/改期,如 15:00→17:00)→ 对【改后仍需参加的内部成员(含自己)+ 新增内部参会人】查【完整新时段】
    |   |   |     (新旧无交集,现有参会人查新时段不会撞上本会议原时段,可正常纳入自己/已有参会人)
    |   |   +-- ③ 改时间且新旧时段【有重叠】(延长/提前等,新时段含部分原时段)→ 分两类查:
    |   |   |     · 新增内部参会人:查【完整新时段】
    |   |   |     · 现有内部参会人及自己:只查【新时段去掉与原时段重叠后剩下的增量段】
    |   |   |       (如 15:00-16:00 延到 15:00-17:00 只查 16:00-17:00;15:00-16:00 提前到 14:00-16:00 只查 14:00-15:00);
    |   |   |       增量段为空(如仅缩短时间)则现有参会人及自己无需查
    |   +-- 按上面裁剪后的查询对象执行;裁剪后查询对象为空、或仅剩外部联系人(wm,忙闲不可查)时才跳过——不要因为"只有自己"就跳过(②/③ 里自己在新时段/增量段内仍要查,避免约到自己已占用的时段)
    |   +-- 忙闲接口不在本技能 → 读取 wecomcli-calendar 技能的 [忙闲查询参考](../../wecomcli-calendar/references/calendar-freebusy.md),按上面圈定的查询对象 + 时段调 free list(窗口 ≤ 24h)
    |   |   +-- 无冲突 → 继续执行 update
    |   |   +-- 有人占线 → 用文字让用户二选一(禁止自行改期):
    |   |   |     文字提问:"该时间段{姓名}有冲突,如何处理?(请回复:坚持这个时间 / 换一个时间)"
    |   |   +-- 接口失败 → 告知忙闲暂不可用,确认时间后继续,不阻塞
    |
    +-- 执行 update(不论会议由谁创建,都直接执行,不提前拒绝)→ 依返回结果判断:
          +-- 返回更新后的字段 → 修改成功,展示更新后的会议摘要
          +-- 返回权限类错误 → 说明当前用户无权修改该会议,告知用户并建议联系会议发起人
```

### 异常路径

| 异常情况 | 处理方式 |
|---------|---------|
| 接口返回无权修改(非发起人) | 直接执行 update 后依返回判断;返回权限错误时告知用户无权操作,建议联系会议发起人 |
| 周期会议更新 | 目前暂不支持更新周期会议,告知用户并引导其在企业微信客户端对该会议进行修改 |
| 修改时间冲突(end ≤ begin) | 提示用户结束时间必须晚于开始时间,请重新输入 |
| wecomcli-contact 技能搜索无结果 | 提示用户确认人名是否正确,或尝试其他搜索词 |
| wecomcli-contact 技能返回多个候选人 | 用文字询问用户(列出候选姓名 + 部门),等待用户选择后汇总继续 |
| 换会议室时新会议室不可用 | `rooms search` 返回 `unavailable`/`not_found`:用文字让用户从 `recommendations` 候选中选,或引导换楼(`expand_to_other_buildings`)/换时间;禁止传不可用的 `meeting_room_id` 调 update |
| 更新接口返回错误 | 检查参数格式,重新阅读本文档确认用法 |

## 示例请求

**修改普通会议时间和主题**:
```json
{
  "meeting_id": "<meeting_id>",
  "subject": "产品需求评审(更新)",
  "begin_time": "2026-04-08 15:00:00",
  "end_time": "2026-04-08 16:00:00"
}
```

**新增/移除参会人**:
```json
{
  "meeting_id": "<meeting_id>",
  "add_attendees": [{"userid": "woxxxc"}],
  "remove_attendees": [{"userid": "woxxxb"}]
}
```

**更换会议室**(`meeting_room_id` 须先经 `rooms search` 确认新会议室 `status=bookable`):
```json
{
  "meeting_id": "<meeting_id>",
  "meeting_room_id": "mrmxxxx"
}
```

## 典型场景

### 1. 修改会议时间

```
用户:把明天下午3点的评审会推迟1小时
→ 调用 meeting search(keywords=["评审"])→ 获取 meeting_id
→ 调用 meeting get → 判断非周期会议(不做是否本人创建的前置拦截)
→ 组装参数:begin_time="2026-04-08 16:00:00",end_time="2026-04-08 17:00:00"
→ 调用 update → 展示更新结果
```

### 2. 添加参会人

```
用户:把王五加到明天的评审会
→ 调用 meeting search → 获取 meeting_id
→ 通过 wecomcli-contact 技能搜索「王五」→ 返回 2 个候选
→ 用文字询问:搜索到多个「王五」,请确认要邀请哪一位?(列出:王五 - 市场部 - 市场专员 / 王五 - 技术部 - 前端工程师)
→ 用户选择后,获得对应 userid
→ 调用 update,add_attendees=[{"userid": "woxxxe"}]
→ 展示更新后完整参会人列表
```

### 3. 修改周期会议(不支持)

```
用户:下周一的周会改到下午3点,只改这一次
→ 调用 meeting search(keywords=["周会"])→ 找到周期会议
→ 调用 meeting get → repeat_rule 非空(周期会议)
→ 不调用 update → 告知:目前暂不支持更新周期会议,请在企业微信客户端对该会议进行修改
```

### 4. 更换会议室

```
用户:把明天评审会的会议室换到 1608
→ meeting search/list 拿 meeting_id(及会议起止时间)→ get 拿会议详情(不做是否本人创建的前置拦截)
→ 读取 wecomcli-calendar 技能的会议室查询参考,用会议时段 + room_keyword="1608" 调 rooms search
→ target 中有 bookable 项 → 取其 target[].room.meeting_room_id
→ 调用 update,meeting_room_id="mrmxxxx"
→ 展示更新后会议摘要(只露会议室 name)

用户:明天的评审会换个会议室
→ 拿 meeting_id 与时段 → rooms search(未指定具体会议室,target=[])
→ recommendations 多个 → 用文字让用户选(展示 name + 楼层 + 容量)
→ 用户选定后取其 meeting_room_id → update
```

## 参考

- [wecomcli-meeting](../SKILL.md) — 会议技能主文档
- [meeting-search](meeting-search.md) — 搜索会议(获取 meeting_id)
- [meeting-list](meeting-list.md) — 查看会议列表
- [meeting-cancel](meeting-cancel.md) — 取消会议
- `读取 wecomcli-calendar 技能` 的 [忙闲查询参考](../../wecomcli-calendar/references/calendar-freebusy.md) — 改时间/加参会人时查共同空闲
- `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../../wecomcli-calendar/references/calendar-meeting-room.md) — 更换会议室时确认新会议室 `status=bookable`
SKILL.md
---
name: wecomcli-meeting
description: "企业微信会议管理。本技能负责『在线会议』——即含在线会议链接(含会议号/入会链接、可远程或视频参会)的会议的创建、查询、搜索、获取详情(含会议信息、纪要、待办)、查询会议转写原文(逐字发言记录)、更新、取消等全部操作;若用户要的是不含在线会议链接的『日程』(也涵盖纯线下面对面碰头),改用 wecomcli-calendar 技能。用户仅说'开会/约个会/某会/某会议'等、未明确要创建的是日程还是在线会议时,必须先读取本技能并按其中的消歧流程向用户追问确认后再处理,不可臆断直接创建。"
metadata:
  requires:
    bins: ["wecom-cli"]
---

# 企业微信会议技能

> 执行任何 `wecom-cli` 命令前,必须先读取并完成 `wecomcli-shared` 技能的公共前置检查。

本 Skill 负责企业微信会议的全生命周期管理,包括创建、查询、搜索、取消会议,以及会议状态和参会人的管理。

**CRITICAL — 操作执行协议**(每次操作必须遵循):
1. 识别用户意图对应的操作类型(创建/查询/搜索/取消)
2. **使用 Read 工具读取该操作的参考文档**(见下方"操作参考"表)
3. 严格按照参考文档中的工作流和命令格式执行
4. 禁止跳过步骤 2 直接执行命令,即使你认为已经知道如何操作
   — 原因:每个操作的参数格式、可选字段和边界行为都在参考文档中精确定义,凭记忆操作极易因参数错误导致调用失败

## 适用范围

### 适用

- 创建 / 新建在线会议(含会议号 / 入会链接,可远程 / 视频参会;含"线下开 + 外地同事远程接入"的会)
- 查看 / 浏览会议列表(最近有什么会、查某时间段的会议)
- 搜索会议(按关键词、会议名找某个会议)
- 查看会议详情(主题、时间、参会人等)
- 更新 / 修改会议(改时间、加减人;不支持更新周期会议)
- 取消会议(不支持取消周期会议)

### 不适用

- 创建、更新、取消周期 / 重复会议(每周 / 每月 / 每天重复)→ 均不支持,引导用户在企业微信客户端手动操作
- 回复 / 拒绝会议邀请(接受 / 拒绝 / 待定,含"拒绝这个会""不参加")→ 不支持,引导用户在企业微信客户端操作或私信发起人

### 易混淆场景路由

- 用户要的是**不含在线会议链接的日程 / 纯线下面对面碰头**(约日程、看今天有什么安排、安排纯线下会议)→ 改用 `wecomcli-calendar`
- 查忙闲 / 约多人共同空闲 → 改用 `wecomcli-calendar`
- 预订 / 查询公司会议室(订会议室、查会议室空不空、查办公楼)→ 会议室查询能力在 `wecomcli-calendar` 技能;创建/更新会议时若要订/换会议室,`读取 wecomcli-calendar 技能` 的会议室查询参考拿 `meeting_room_id` 传入本技能的 create/update
- 用户仅说"开会 / 约个会 / 安排个会 / xx 会"等、**未明确是日程还是在线会议**(创建场景)→ 必须先用文字追问消歧(固定问题"需要创建日程还是会议?",请用户回复"日程 / 会议"),不得臆断直接创建
- **仅给了地点 / 会议室号**(如"在 1605 开会""订个会议室开会")→ 不构成"明确是会议",仍需先用文字询问消歧,不能因带地点就跳过追问
- **查询场景的模糊表述**("最近有什么会 / 有哪些会")→ 严禁追问,日程和会议都查并合并展示;仅当明确提到"在线会议 / 视频会议 / 入会链接 / 会议号 / 远程参会"时才只查会议

## 路由规则

| 用户意图 | 参考文档 |
|---------|---------|
| 新建会议、开个会、安排视频会议 | [meeting-create](references/meeting-create.md) |
| 查会议、我的会议列表、最近有什么会、查某个时间段的会议 | [meeting-list](references/meeting-list.md) |
| 搜索会议、找某个会议、找上周的周会 | [meeting-search](references/meeting-search.md) |
| 查看会议详情、看看参会人 | [meeting-list](references/meeting-list.md) |
| 修改会议、更新会议、改个时间、加人/移除人 | [meeting-update](references/meeting-update.md) |
| 取消会议、不开了 | [meeting-cancel](references/meeting-cancel.md) |
| 查看会议转写原文、逐字记录、把会上说的原话发我、要转写/转录文字、第几段转写 | [meeting-original-get](references/meeting-original-get.md) |
| 总结会议 / 要会议纪要 / 这个会讲了啥 / 看会议待办 / 总结待办 | 见下方核心场景「7. 会议总结(纪要/待办)」,编排 [meeting-list](references/meeting-list.md) 的 get 与 [meeting-original-get](references/meeting-original-get.md) |
| 约日程、日程安排、看看今天有什么安排 | wecomcli-calendar 技能 |
| 查忙闲、看看某人什么时候有空 | wecomcli-calendar 技能 |
| 安排纯线下面对面会议(不含在线会议链接) | wecomcli-calendar 技能 |

> **技能边界(会议 vs 日程)[CRITICAL]**:本技能只创建**含在线会议链接的会议**(含会议号/入会链接,供远程/视频参会)。只要涉及在线会议链接就归本技能;不含在线会议链接的纯线下面对面会议属于日程,使用 `读取 wecomcli-calendar 技能`。用户仅说"会议/会/开个会/约个会/安排个会/xx会/xx会议"等而未明确是日程还是会议时,**必须先用文字追问**,禁止默认直接创建会议:
>
> **问题与选项固定 [CRITICAL]**:消歧确认时,问题与可选项都必须原文照用、严格禁止修改任何内容——问题固定为 `"需要创建日程还是会议?"`,可选项固定为 `日程` / `会议`;不得改写问题措辞、增减或改写选项、翻译,或自行设计其他表述(如"在线会议 / 线上会议 / 视频会议 / 线下会议"等)。
>
> 用文字向用户提问:`需要创建日程还是会议?(请回复:日程 / 会议)`
>
> 用户答「会议」→ 留在本技能创建会议;答「日程」→ 改用 `读取 wecomcli-calendar 技能` 创建日程。
>
> 此文字消歧仅用于「创建」;查询场景严格禁止追问——明确指向在线会议时只查会议,明确是日程/安排时只查日程,模糊表述("会 / xx会 / 最近有什么会"等)则日程和会议都查(见下文「查询消歧」)。
>
> **"会议""会""开会"等词本身不构成"明确" [CRITICAL]**:这些词只表示要碰头议事,并未说明是日程还是会议。禁止仅因 query 里出现"会议"二字就默认归本技能(会议)创建,也禁止反向默认成日程——只要未明确,一律先用文字追问后再路由。只有出现"入会链接 / 会议号 / 视频会议 / 远程参会"等明确信号时才直接归会议。
>
> **同时支持线下与远程参会**(如"线下开、外地同事远程接入")时,因含在线会议链接,归本技能创建——创建会议会同时生成对应日程,无需再去 wecomcli-calendar 技能另建日程。
>
> **仅有地点/会议室号**(如"在 1605 开会""到 A 座会议室碰一下""订个会议室开会")不构成"明确是会议"——会议室里同样可能只是纯线下安排,是日程还是会议仍未知,必须先用文字询问消歧,不能因为带了地点就跳过追问。

> **list vs search 的选择原则**:用户明确提到主题/名称关键词时用 `search`(把关键词传入 `keywords`);只按时间范围或泛浏览时用 `list`,禁止把日期当 `keywords` 喂给 `search`。两者有时可组合:先search 定位,再 list 确认时间段全貌。

> **查询消歧(模糊查询时日程 + 会议都查)[REQUIRED]**:查询场景严格禁止用文字追问"是日程还是会议"——日程/会议消歧追问仅用于创建,查询时一律按以下规则直接处理、不追问。**判定分两个独立维度,不要混为一谈**:
>
> **维度一:查哪一边(日程 / 会议 / 两边都查)**
> - **明确是在线会议** → 用户明确提到"在线会议 / 视频会议 / 入会链接 / 会议号 / 腾讯会议 / 远程参会"等在线会议专属特征时,留在本技能只查会议。
> - **明确是日程 / 安排** → 用户说的明显是日程类内容(如"日程 / 安排 / 我的安排 / 日历",且不带在线会议特征)时,改用 `读取 wecomcli-calendar 技能` 只查日程。
> - **模糊表述无法判定**("会 / xx会 / xx会议 / 开会 / 最近有什么会 / 有哪些会 / 找下 xx会议"等,既可能是日程也可能是会议)→ **日程和会议都要查**:既用本技能查会议,又 `读取 wecomcli-calendar 技能` 查日程。
>
> **维度二:每一边用 `search` 还是 `list`(与维度一独立,逐边各自判断)**
> - **有主题/名称关键词**(如"找下 xx会议""搜一下项目评审会议")→ 该边用 `search`(把关键词传入 `keywords`)。
> - **只有时间/日期或泛浏览无关键词**(如"最近有什么会""查一下明天的会议")→ 该边用 `list`,禁止把日期当 `keywords` 喂给 `search`。
> - 即使"两边都查",也按本维度对每一边各自选择:带关键词时两边都用 `search`,纯时间/泛浏览时两边都用 `list`。
>
> **合并展示**:两边都查时,合并结果后统一展示——按是否含在线会议链接分成「(会议)」(来自会议侧、或日程中 `meeting.meeting_link` 非空者)和「(日程)」(`meeting_link` 为空的纯日程)两部分,同一场会议在两边都出现时按"主题 + 时间"去重只保留一条,末尾汇总"共 N 场,其中会议 X 场、日程 Y 场"。
>
> - 本消歧仅针对查询;创建仍按下文"日程 vs 会议"用文字追问。

**触发表达示例**:
- "帮我开个会" / "安排一场会议" / "创建视频会议"
- "看看我的会议" / "查一下明天的会议" / "最近有什么会"
- "搜一下项目评审会议" / "找找上周的周会"
- "取消那个会议" / "这个会不开了"
- "帮我总结下 xx 会议" / "这个会讲了啥" / "把 xx 会议纪要发我" / "看下这个会的待办" / "按决策点整理下这个会"

## 前置条件

- 需要企业微信账号且已登录
- 取消/更新操作不预先按"是否本人创建"拦截,直接执行命令、由接口返回结果判断能否操作
- 参会人 userid(前缀为 `wo`)组装为 `[{"userid": "woxxx"}]` 对象数组格式传入;用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid

## 核心场景

### 1. 新建会议

以当前用户为发起人创建一场新会议。

**CRITICAL — 执行前必须先读取参考文档**:收到创建会议意图后,第一步立即读取 [`meeting-create`](references/meeting-create.md),按其中的完整工作流(参数补全 → 参会人解析 → 参会人忙闲检查 → 调用创建接口 → 获取详情展示)逐步执行,禁止在未读取参考文档的情况下直接发起任何操作。

> **参会人忙闲检查 [REQUIRED]**:创建 / 更新会议时须在时间敲定前查忙闲,避免约到冲突时间。忙闲接口不在本技能,须 `读取 wecomcli-calendar 技能` 的 [忙闲查询参考](../wecomcli-calendar/references/calendar-freebusy.md) 调`free list`。**创建会议时**:查询对象 = 当前用户自己 + 其他内部参会人(`wo` 前缀),**只有自己也要查**(避免约到自己已占用的时段);外部联系人(`wm`,忙闲不可查)不纳入查询对象、但**不因此跳过**整体检查;仅忙闲接口调用失败时降级放行。**给已有会议加人、不改时间时**:忙闲查询只针对**新增参会人**、且查会议原时段,禁止把当前用户(自己/创建者)和已有参会人纳入——他们正被本会议占用、必然显示"忙",纳入会误报冲突(详见 [meeting-update](references/meeting-update.md) 工作流)。

> **会议室预订 [REQUIRED]**:用户创建会议时提到"订会议室 / 在 1605 开 / 找个会议室 / 某栋楼的会议室"等意图时,会议室查询能力不在本技能——须 `读取 wecomcli-calendar 技能` 的 [会议室查询参考](../wecomcli-calendar/references/calendar-meeting-room.md)(`buildings list` + `rooms search`)查到真实会议室,拿 `meeting_room_id` 传入 `meeting create`(占用)。禁止把会议室名仅写进 `location`、禁止凭记忆/猜测编造 `meeting_room_id`;先订房后建会,详见 [meeting-create](references/meeting-create.md) 步骤 4。

> 详见 [meeting-create](references/meeting-create.md)

### 2. 查询会议列表

**CRITICAL — 执行前必须先读取参考文档**:收到查询会议列表意图后,第一步立即读取 [`meeting-list`](references/meeting-list.md),按其中的完整工作流(时间范围确定 → 拉取列表 → 批量获取详情 → 反查参会人姓名 → 合并输出)执行,禁止在未读取参考文档的情况下直接发起任何操作。

> **模糊查询必须日程 + 会议都查 [CRITICAL]**:若本次是"会 / xx会 / xx会议 / 最近有什么会 / 有哪些会 / 找下 xx会议"等模糊查询(见上文「查询消歧」),无论会议列表是否查到结果,都必须同时 `读取 wecomcli-calendar 技能` 用相同时间范围拉日程 `list`,把两边结果合并、按是否含在线会议链接分「(会议)」「(日程)」两部分汇总展示(同一场会议按主题 + 时间去重),禁止因会议已查到就跳过日程查询。仅当用户**明确指向在线会议**(入会链接 / 会议号 / 视频会议 / 远程参会等)时才只查会议;此时若查无,再兜底去日程查一把(命中则说明「这是一条日程」,两边都无再告知)。

> 详见 [meeting-list](references/meeting-list.md)

### 3. 搜索会议

根据关键词匹配会议主题或内容。有关键词时不执行特定追问操作补全时间,直接搜索。适合用户知道会议名称或关键词的场景。

> **模糊搜索必须日程 + 会议都搜 [CRITICAL]**:若用户搜的是"会 / xx会 / xx会议"等模糊目标(非明确在线会议),无论会议是否搜到,都必须同时 `读取 wecomcli-calendar 技能` 用同样关键词搜日程,把两边结果合并分「(会议)」「(日程)」汇总展示;仅当**明确指向在线会议**时才只搜会议,此时搜不到再兜底去日程搜(命中则说明「这是一条日程」,两边都无再告知)。

> 详见 [meeting-search](references/meeting-search.md)

### 4. 取消会议

**CRITICAL — 执行前必须先读取参考文档**:收到取消会议意图后,第一步立即读取 [`meeting-cancel`](references/meeting-cancel.md),按其中的完整工作流(定位会议 → 状态检查 → 周期判断 → 执行取消 → 按返回结果判断)执行,禁止在未读取参考文档的情况下直接发起任何操作。

> 详见 [meeting-cancel](references/meeting-cancel.md)

### 5. 更新会议

**CRITICAL — 执行前必须先读取参考文档**:收到更新/修改会议意图后,第一步立即读取 [`meeting-update`](references/meeting-update.md),按其中的完整工作流(定位会议 → 周期判断 → 参数收集 → 执行更新 → 按返回结果判断)执行,禁止在未读取参考文档的情况下直接发起任何操作。

> 详见 [meeting-update](references/meeting-update.md)

### 6. 查询会议转写原文

**CRITICAL — 执行前必须先读取参考文档**:收到查询会议转写原文("原话/逐字记录/完整对话/转写/转录/第几段"等)意图后,第一步立即读取 [`meeting-original-get`](references/meeting-original-get.md),按其中的完整工作流(定位会议 → 确定段落 → 拉取转写 → 翻页拼接 → 原样输出)执行,禁止在未读取参考文档的情况下直接发起任何操作。

> **转写原文 ≠ 智能纪要 [CRITICAL]**:转写原文(`original_data`,逐句原始发言)与 `meeting get` 里 AI 总结的 `notes`(纪要/待办)是两种不同内容,禁止用纪要替代转写原文。**`media_index` 默认不传**——不传时接口返回全部段转写,仅当用户明确要"第 N 段"时才传 `N-1`(从 0 开始),不主动追问要哪一段。`has_more` 为 true 时必须翻页到底并按序拼接,输出时**原样保留**时间戳+说话人的逐行格式,不总结、不裁剪。

> 详见 [meeting-original-get](references/meeting-original-get.md)

### 7. 会议总结(纪要 / 待办)

用户要"总结某场会议"——包括要**会议纪要**、问"这个会讲了啥"、要**会议待办**、"总结下待办"等,本质是对已有的 `meeting get`(现成纪要/待办)与 `meeting original get`(转写原文)两个接口做**编排**,没有新接口。**纪要与待办同属此逻辑**,处理方式一致。

**CRITICAL — 执行前必须先读取参考文档**:先按 [`meeting-list`](references/meeting-list.md) 定位会议并(无自定义要求时)取 `get`;需回到原文加工时读取 [`meeting-original-get`](references/meeting-original-get.md)。

**唯一分叉维度:本次总结是否带「自定义要求 / 描述」**

**只要用户在"总结"之外附带了任何自定义的要求、描述、角度、范围、结构或风格,一律走原文生成**;**只有纯粹地说"总结下 / 讲了啥 / 纪要发我 / 看待办"、不带任何额外描述时,才返回已有的现成内容**。

- **只说"总结下"(无任何自定义描述)**:仅泛泛地要一份总结/概要/待办,没有附加任何要求。触发语如"总结下 xx 会""这个会讲了啥""纪要发我""看下这个会的待办""有哪些待办"。
  1. 先调 `meeting get`,取目标字段:要纪要 → 看 `notes[].note_content`;要待办 → 看 `notes[].todo_content`。
  2. **可用则直接返回官方现成内容**(判定:`has_note_permission == true` 且目标字段有实质内容),无需再调用转写原文接口。
  3. **不可用**(目标字段空 / `has_note_permission == false`)→ 转下方原文兜底。
- **带了任何自定义要求 / 描述**:只要用户附加了结构、角度、聚焦范围、风格或长度等任意描述,就归此类。触发语如"按决策点整理""用三段式""列出每人发言重点""重点讲预算那部分""写成正式会议纪要""一句话概括""结合上次的会说说进展"等。
  - **跳过 `get`,直接 `meeting original get` 拉全部转写**,按用户的要求/描述加工总结。理由:官方 `notes` 是固定视角的成品,满足不了任何定制诉求,必须回到原文重新加工。

**原文兜底顺序 [REQUIRED]**:凡需要走原文(get 不可用,或带自定义要求),一律先调 `meeting original get`(翻页到底),再按结果处理:
- 接口报错(无权限/其他)→ 按接口返回如实提示,不静默失败。
- 成功但 `original_data` 为空 → 告知"该会议暂无智能纪要,也没有转写原文(可能未开启会议转写、会议未开始或无发言记录)",不编造。
- 成功且有内容 → 按默认或用户指定的结构总结(此时 `original get` 允许加工总结,区别于"要原话/逐字记录"时的原样输出)。

> 详见 [meeting-list](references/meeting-list.md)(定位 + get)与 [meeting-original-get](references/meeting-original-get.md)(拉原文并加工)。

## 核心概念

- **会议(Meeting)**:企业微信会议实体,含主题、起止时间、参会人、入会链接等属性。
- **会议 ID(meeting_id)**:API 使用的会议唯一标识,较长的字符串(`mt` 前缀)。
- **会议号(meeting_code)**:9 位纯数字,仅用于用户入会,不能作为 meeting_id 使用。
- **周期会议(Recurring)**:按规则重复的会议,`update`/`cancel` 均不支持(见「已知限制」);仅 `original get` 查询转写原文某场时需指定 `sub_meeting_id`。
- **参会人(Attendee)**:以 userid(`wo` 前缀)标识。用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid。

## 核心规则

### 规则 1: userid 获取

- `attendees` 字段格式为 `[{"userid": "woxxx"}, {"userid": "woyyy"}]` 对象数组,不接受姓名,不接受平铺字符串数组。
- 用户提供的是姓名时,通过 `读取 wecomcli-contact 技能` 解析为对应 userid;多候选人时用文字让用户选择,不自行猜测。
- **禁止**把姓名当 userid 拼接,**禁止**凭记忆或猜测编造 userid。
- `open_vid` 和 `userid` 是同一概念的不同叫法,其他系统返回的 `open_vid` 可直接作为 `userid` 使用。

### 规则 2: 写操作直接执行

- 创建会议、取消会议时,参数就绪后直接执行,无需向用户展示摘要或询问确认。
- 结果返回时**禁止暴露 userid**,只展示人名。
- **原因**:上层交互已完整展示操作内容并完成确认,此处再展示一遍会造成冗余;userid 是系统内部标识,对用户没有实际意义,展示反而容易造成困惑。

### 规则 3: 参数补全

任何操作中,当必要参数不明确或需要用户做出选择时,**必须用文字直接向用户提问**,禁止自行猜测或使用默认值代替询问。提问时把可选项 / 候选值一并写进文字里,让用户直接回复。

以下情况均适用此规则:

- **必填参数及参会人缺失**:操作所需的参数无法从上下文中推断(如创建会议时 `subject`/`begin_time` 缺失、参会人 `attendees` 缺失、搜索时 `keywords` 缺失)时必须用文字询问;其余非必填参数(地点、会议室等)用户未明确指定时不追问,走默认值;`end_time`(时长)缺失时不追问,默认时长 1 小时(`begin_time + 1h`);仅描述参会方式或动作的词(如「视频会议 / 开个会 / 远程接入」)不构成有效 `subject`,按缺失处理走文字询问,禁止当主题直接创建
- **多候选项需用户选择**:搜索/查询返回多个匹配项、wecomcli-contact 技能搜索到多个同名候选人
- **操作范围需确认**:如更换会议室时查到多个 bookable 候选,需用户选定具体一个

**文字询问的约束**:
- 列出的可选项 / 候选建议以 **2~4 个**为宜。可选候选多于 4 个时(如同名候选人、多个匹配会议),取最相关的前 4 个列出,并提示用户可进一步缩小范围(输入更精确的关键词 / 完整姓名 / 具体时间),不要一次性罗列 5 个及以上候选。
- **询问时间时,列出的候选时刻必须是精确到分钟的具体时刻**(如"明天 14:00"、"后天 09:30"),禁止给出"上午/下午/傍晚/午间/上班后/下班前"等模糊时间选项——模糊选项会导致用户回复后仍需二次追问具体几点,必须一次问到可直接落为 `begin_time` 的精确时刻。

各场景具体的提问话术和候选项见对应操作的参考文档。

### 规则 4: 权限判定交给接口

- 取消 / 更新会议不预先按"是否本人创建"拦截,也不区分 `created_meetings` / `attended_meetings`——直接执行 `cancel` / `update`,能否操作由接口返回结果判断。
- 返回成功即操作完成;返回权限类错误则说明当前用户无权操作该会议,告知用户并建议联系会议发起人。

### 规则 5: 输入安全处理

- 用户提供的是姓名时,必须经过 `读取 wecomcli-contact 技能` 搜索验证后才能转换为 userid。**禁止**把姓名直接拼接为 userid,**禁止**凭记忆或猜测编造。原因:用户输入的字符串可能不对应真实员工(姓名不唯一、已离职等),直接拼接会导致将消息发送给错误的人或创建出包含无效参会人的会议,且此类错误无法被 API 在调用时拦截。

### 规则 6: 错误重试上限

- 同一操作失败后,最多重试 **2 次**。两次重试后仍失败,停止自动操作,向用户输出完整的错误诊断信息,等待人工介入。
- 不同错误类型应用不同策略:网络超时可重试,权限不足/参数错误不应重试(重试无效)。

> **长期记忆原则**:用户的常用参会人组合(如"产品团队"= 张三+李四)等个性化信息,在首次明确后应在当前会话内记忆,减少重复追问。如果当前会话不支持跨会话持久记忆,则在本次会话内保持记忆;会话结束后偏好清空,下次使用时重新澄清即可。(会议时长不在此列:用户未指定时一律默认 1 小时、不追问、也无需记忆时长偏好。)

## CLI 调用格式

```bash
wecom-cli meeting [action] --json '{"key": "value"}'
```

- `meeting action`:`create`、`list`、`get`、`search`、`cancel`、`update`、`original get`
- `--json`:JSON 参数,用**单引号**包裹

## 操作参考

操作参考文档是对常用操作的详细说明。**执行操作前务必先读取对应文档。**

| 操作参考 | 说明 |
|----------|------|
| [`meeting-create`](references/meeting-create.md) | 创建会议并确认详情 |
| [`meeting-list`](references/meeting-list.md) | 查询会议列表(list + get) |
| [`meeting-search`](references/meeting-search.md) | 按关键词搜索会议 |
| [`meeting-cancel`](references/meeting-cancel.md) | 取消已创建的会议 |
| [`meeting-update`](references/meeting-update.md) | 更新已创建会议的信息(主题、时间、参会人、地点等) |
| [`meeting-original-get`](references/meeting-original-get.md) | 查询会议转写原文(逐句原始发言,区别于纪要) |

## 上下文传递表

| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `search` | `meetings[].meeting_id` | `get` 查详情、`cancel` 取消会议 |
| `list` | `created_meetings[].meeting_id` / `attended_meetings[].meeting_id` | `get` 查详情、`cancel` 取消会议 |
| `search` | `meetings[].meeting_id`(周期会议加 `meetings[].sub_meeting_id`) | `original get` 拉取会议转写原文 |
| `list` | `created_meetings[].meeting_id` / `attended_meetings[].meeting_id`(周期会议加对应 `sub_meeting_id`) | `original get` 拉取会议转写原文 |
| `list` | `created_meetings` / `attended_meetings` | 展示会议列表时区分"我创建的"与"我参加的"(不用于取消/更新的权限判断) |
| wecomcli-contact 技能搜索 | `userid`(`wo` 前缀) | `create` 的 `attendees` 数组 |
| `get` | `meeting_status` | 判断会议状态(`"init"` / `"started"` / `"end"`) |
| `get` | `repeat_rule` | 判断是否周期会议(非空即周期会议):命中时 `cancel`/`update` 均不支持,告知用户并引导企业微信客户端操作 |
| `create` | `meeting_id` | 会议唯一标识 |
| `search` / `list` + `get` | 会议 ID(search 取 `meetings[]`、list 取 `created_meetings[]`/`attended_meetings[]`)、`repeat_rule` | `update` 的定位与周期会议判断(命中周期会议则不支持更新) |

## 错误处理

> 原则:告诉用户**出了什么问题** + **可以怎么做** + **备选方案**。禁止静默失败。最多重试 2 次,超出后停止自动操作。

| 错误场景 | 可能原因 | 恢复建议 |
|---------|---------|---------|
| 接口返回权限不足(非发起人取消/修改) | 当前用户非会议发起人 | 直接执行后依返回判断;返回权限错误时告知用户无权操作,建议联系会议发起人;不重试 |
| 时间校验失败 | `begin_time` 早于当前时间 | 提示用户重新选择未来的时间点;不重试,等待用户修正 |
| 参数格式错误(meeting_id) | meeting_id 误传 9 位会议号 | 检查 ID 来源:[正确] `"meeting_id": "mtkSFfCgNxxxxxxx"`(长字符串);[错误] `"meeting_id": "123456789"`(9 位会议号是 meeting_code,不能作为 meeting_id);不重试 |
| 参数格式错误(attendees) | attendees 格式不正确 | 检查格式:[正确] `"attendees": [{"userid": "woxxx"}]`;[错误] `"attendees": ["woxxx"]`(不接受平铺字符串数组)或 `"attendees": ["张三"]`(不接受姓名);用户提供的是姓名时通过 `读取 wecomcli-contact 技能` 解析为 userid |
| 搜索/列表无结果 | 时间范围或关键词不匹配;**或用户找的「会」其实是日程而非含在线会议链接的会议** | 先建议扩大时间范围或修改关键词重试;同时**主动 `读取 wecomcli-calendar 技能` 用同样关键词在日程里搜一把**(企微里「会」有「含在线会议链接的会议」和「日程」两种载体,团队聚一起的会常落在日程而非会议),命中则一并呈现并说明「这是一条日程」,仍无果再告知两边都没有 |
| 网络超时 | 网络不稳定 | 等待后重试,最多 2 次;2 次后提示用户稍后再试 |
| 连续 2 次失败 | 根因未知或持续性问题 | 停止自动重试,输出完整错误信息,建议用户联系管理员或手动操作 |

## 输出质量标准

好的输出应满足以下条件:
- 会议列表:每条只展示主题、时间、参会人姓名三项(不含状态标签、参会人数、地点、会议号、入会链接等),按开始时间升序排序,详见下方「会议列表展示规范」
- 参会人展示:原样使用接口返回的 `attendees[].name` 字段(完全与接口返回的格式保持一致,如返回 `zhangsan(张三)` 就展示 `zhangsan(张三)`),不展示 userid
- 会议详情:含关键字段(主题、时间、参会人姓名;不展示会议号、入会链接)
- 操作结果:明确告知成功/失败及原因,操作成功后展示最新状态

不可接受的输出:
- 直接展示 userid 而非姓名
- 遇到错误静默失败,不给用户任何提示

## 输出格式规范

**参会人姓名格式 [REQUIRED]**:所有展示参会人的场景(创建反馈、列表、单条详情等),姓名一律**原样使用接口返回的 `attendees[].name` 字段**,完全与接口返回的格式保持一致(如返回 `zhangsan(张三)` 就展示 `zhangsan(张三)`);下文模板中的 `{人名}` 均指该原样 name。

**时间年份显示 [REQUIRED]**:下文"时间"行默认省略年份、只到月日(模板中的 `{M月D日}`);仅当会议年份与当前年份不同(跨年)时,才在月日前补上年份,格式为 `{YYYY}年M月D日 {HH:mm}-{HH:mm}`。

**相对日期标签 [REQUIRED]**:当会议日期为昨天 / 今天 / 明天时,"时间"行在月日前加上相对词,格式 `{昨天|今天|明天} M月D日 {HH:mm}-{HH:mm}`(如 `时间:明天 6月11日 14:00-15:00`);其余日期按 `{M月D日} {HH:mm}-{HH:mm}` 展示。

**创建成功反馈 [REQUIRED]**:创建会议成功后,输出内容只包含三部分:主题、时间、参会人,禁止输出其他任何内容和额外语句(不展示地点、会议室、会议号、入会链接、meeting_id 等字段,也不附加说明、建议或寒暄):
```
主题:{subject}
时间:{M月D日} {HH:mm}-{HH:mm}
参会人:{人名1}、{人名2}
```

**会议列表展示规范 [REQUIRED]**(列表/搜索浏览均适用):
- **禁止使用 markdown 表格**;每条会议作为独立条目顺序输出,按开始时间升序排序。
- 每个条目 **只展示三项:主题、时间、参会人**(不展示状态标签、参会人数、地点、会议号、入会链接等)。
- **超过 10 条时只展示前 10 条**,并在末尾告知"还有 N 条,需要查看更多吗?"。
- 参会人姓名取 `meeting get` 返回的 `attendees[].name`;只需对要展示的前 10 条调用 `meeting get` 反查,禁止展示 userid。
- 单个条目格式:
```
1. {subject}
   时间:{M月D日} {HH:mm}-{HH:mm}
   参会人:{人名1}、{人名2}

2. {subject}
   时间:{M月D日} {HH:mm}-{HH:mm}
   参会人:{人名1}、{人名2}
```

**单条会议详情**(查看单条会议详情时,可展示完整字段):
```
- 主题:{subject}
- 时间:{M月D日} {HH:mm}-{HH:mm}
- 参会人:{人名1}、{人名2}(禁止展示 userid)
- 地点:{location}(如有)
```

> **禁止展示会议号 / 入会链接 [REQUIRED]**:任何场景(创建反馈、列表、搜索、单条详情等)都**不展示会议号(`meeting_code`)和入会链接(`meeting_link`)**。

## 已知限制

| 限制 | 替代方案 |
|------|---------|
| **不支持创建/更新/取消周期(重复)会议** | 用户希望创建"每周/每月/每天重复"等周期会议,或对已识别为周期会议(`repeat_rule` 非空)的会议发起更新、取消时,均直接告知用户目前不支持,并引导用户在企业微信客户端手动操作;禁止用批量创建多条单次会议、传入未公开参数等方式变通绕过 |
| **不支持回复 / 拒绝会议邀请(RSVP)** | 本技能不支持对收到的会议邀请做接受 / 拒绝 / 待定等回复(含"拒绝这个会""不参加""婉拒邀请"等)。用户有此需求时,告知其本技能不支持,建议直接在企业微信客户端对该会议邀请操作,或通过消息告知会议发起人 |
| **参会人上限 100 人** | `attendees` 数组不超过 100 个 userid |
| **时长上限 24 小时** | `begin_time` 与 `end_time` 间隔不超过 24 小时。出现超 24h 的单场会议需求时,直接告知不支持并拒绝,禁止自行拆分成多场会议或变通绕过;用户确需多天安排时,由其明确拆分要求后再分别创建 |
| **批量查询上限 10 个** | `meeting get` 单次最多 10 个 meeting_id,超出必须分批多次调用(按每批 ≤ 10 切分,再合并结果) |
| **list 不含 meeting_status** | 需额外调用 `meeting get` 才能获取会议状态 |

## 快速参考

### 接口对比

| 功能 | meeting create | meeting list | meeting get | meeting search | meeting cancel | meeting update | meeting original get |
|------|---------------|-------------|------------|---------------|---------------|----------------|----------------------|
| 用途 | 创建会议 | 查询会议列表 | 获取会议详情 | 按关键词搜索会议 | 取消会议 | 更新会议信息 | 查询会议转写原文 |
| 前置依赖 | 需先获取 userid | 无 | 需先 list/search 拿到 meeting_id | 无 | 需先确认 meeting_id | 需先确认 meeting_id | 需先 list/search 拿到 meeting_id |

### 参数速查表

| 接口 | 核心参数 |
|------|---------|
| `meeting create` | `subject`(必填)、`begin_time`(必填)、`end_time`(必填)、`attendees`(对象数组 `[{"userid": "woxxx"}]`)、`location`(地点文本;会议室须走 `meeting_room_id`,非会议室文本才只写 `location`)、`meeting_room_id`(会议室 ID,订会议室时传,来自 `读取 wecomcli-calendar 技能` 的会议室查询)、`description`、`timezone`(格式 `{"timezone_id": "Asia/Shanghai", "timezone_offset": 28800}`) |
| `meeting list` | `begin_time`、`end_time`(均选填,须同时传入或同时省略)、`cursor`、`limit` |
| `meeting get` | `meeting_ids`(必填,对象数组,格式 `[{"meeting_id": "xxx"}]`,**单次最多 10 个**,超出需分批多次调用;周期会议需加 `sub_meeting_id`) |
| `meeting search` | `keywords`(必填,字符串数组)、`begin_time`、`end_time`、`cursor`、`limit`(固定传 `20`);`keywords` 可匹配会议主题、参会人姓名、会议纪要内容、会议室名称等信息 |
| `meeting cancel` | `meeting_id`(必填);不支持取消周期会议 |
| `meeting update` | `meeting_id`(必填)、`subject`、`begin_time`、`end_time`、`add_attendees`/`remove_attendees`(对象数组 `[{"userid": "x"}]`)、`location`(地点文本;会议室须走 `meeting_room_id`)、`meeting_room_id`(更换会议室时传,须先经 `rooms search` 确认 `status=bookable`)、`description`;不支持更新周期会议 |
| `meeting original get` | `meeting_id`(必填,`mt` 长字符串)、`sub_meeting_id`(周期会议某场时传)、`media_index`(第几段,从 0 开始,**默认不传返回全部段**,仅用户明确指定"第 N 段"时传 `N-1`)、`cursor`、`limit`(默认 100,上限 500) |