Feishu Meeting Room Book
基于飞书日历事件学习个人常用会议室,并按本地偏好列表预定会议室。
Paths
- - State file: INLINECODE0
- Helper script: INLINECODE1
- Refresh plan file: INLINECODE2
Dependencies And Permissions
- - Required OpenClaw plugin:
-
openclaw-lark or any equivalent Feishu/Lark plugin that exposes:
-
feishu_calendar_event
-
feishu_calendar_event_attendee
-
feishu_calendar_freebusy
- - Required local runtime:
-
python3
- the bundled helper script uses Python standard library only and does not require extra pip packages
- persistent state is written to
state/feishu-meeting-room-book.json
- temp working files may be written under
tmp/meeting-room-events-*.json and
tmp/meeting-room-candidates-*.json
- - Required Feishu OAuth scopes:
-
calendar:calendar.event:read for reading recent calendar events during init/refresh
-
calendar:calendar.event:create for creating new calendar events
-
calendar:calendar.event:update for attaching meeting rooms and updating event attendees
-
calendar:calendar.free_busy:read for checking room availability
- this skill does not call a separate meeting-room booking service
- room booking is performed by creating or updating Feishu calendar events and attaching the room as a INLINECODE15
Required Tools
- - INLINECODE16
- INLINECODE17
- INLINECODE18
If any Feishu tool returns auth or scope errors, finish the OAuth flow first and retry. The normal scopes for this skill are:
- - INLINECODE19
- INLINECODE20
- INLINECODE21
- INLINECODE22
Product Rules
- - Manual refresh only. Never auto-refresh cached rooms.
- The cached room list is single-city v1. Store one base location (default city) and one ordered room list. The internal state field name remains
default_city. - On first use, if
state/feishu-meeting-room-book.json does not exist and the user asked to create/book a room or book an existing event, do not ask the user to initialize separately. Tell the user you are starting first-time initialization, run init immediately, and continue the original request after the default room order has been saved. - During init/refresh, the extracted candidate order is the default priority order. Do not reshuffle it unless the user explicitly changes it.
- When creating a new event, use defaults instead of asking follow-up questions for common omissions:
- missing title: use summary
会议
- missing attendees: create a self-only event with no extra attendees
- missing duration/end time: default to 1 hour
- - If the user does not specify a city, use the saved base location (default city) from
default_city. - If the user specifies a different city from the saved base location (default city), create the event but do not auto-book a room. Explain that the current cache only covers the base location (default city), and tell the user to seed that city manually and run refresh.
- When no preferred room is free:
-
create_and_book: still create the event without a room
-
book_existing: keep the event unchanged and tell the user to refresh after manually seeding more rooms
Updating Candidate Rooms
Use these rules when the user asks how to update the candidate room list.
- - To add a new candidate room:
1. Ask the user to create any calendar event within the next 7 days.
2. Tell the user to attach the desired meeting room to that event as a
resource attendee.
3. Then tell the user to say
刷新会议室列表,
更新会议室列表,
重新学习会议室, or
更新会议室候选表.
4. Default refresh scans only a narrow seeding window and includes that room if it appears in the event payload.
- - To remove a room from the candidate list:
- Run refresh and keep only the rooms the user still wants.
- Or keep the current list and overwrite the state with a smaller subset and a new order.
- - To change room priority:
- Run refresh and let the user reply with the final room order.
- Or keep the current rooms and overwrite the state with a reordered
--room-id list.
- Only events in the chosen scan window affect candidates.
- Default refresh uses
now - 1 day to
now + 7 days.
- First-use init uses
now - 7 days to
now + 7 days.
- Only explicit full rebuild uses
now - 20 days to
now + 20 days - 1 minute.
- A seeding event outside the current scan window will not update the candidate list.
Init Or Refresh
Use this flow when the user says:
- - "初始化会议室偏好"
- "初始化会议室预定技能"
- "刷新会议室列表"
- "更新会议室列表"
- "重新学习会议室"
- "更新会议室候选表"
- "全量刷新会议室列表"
- "重建会议室列表"
- or the state file does not exist
When init is triggered implicitly because the state file does not exist, explicitly tell the user:
- - this is the first use of the skill
- you are learning their recent meeting room history now
- you will save a default room priority order first
- you will continue the current booking request automatically after init finishes
Steps:
- 1. Choose one scan mode before calling any Feishu tool:
-
default_refresh: when state already exists and the user says
刷新会议室列表,
更新会议室列表,
重新学习会议室, or
更新会议室候选表
- scan window:
now - 1 day to
now + 7 days
- save behavior: incremental update; keep existing rooms and order, append newly discovered rooms, refresh metadata for rooms seen again
-
init: when the state file does not exist or the user explicitly says
初始化会议室偏好 or
初始化会议室预定技能
- scan window:
now - 7 days to
now + 7 days
- save behavior: rebuild the state from the extracted candidates
-
full_rebuild: only when the user explicitly says
全量刷新会议室列表 or
重建会议室列表
- scan window:
now - 20 days to
now + 20 days - 1 minute
- save behavior: rebuild the state from the extracted candidates
- 2. Build the refresh plan with the helper script.
Never compute the slice boundaries manually:
CODEBLOCK0
- 3. Read
tmp/meeting-room-refresh-plan.json.
Use exactly the
slices[].start_time,
slices[].end_time,
slices[].event_file, and
slices[].candidate_file values from that plan.
Do not invent your own temp filenames or time windows.
- 4. For each slice from the plan:
- call
feishu_calendar_event with
action=list
- immediately save only that slice result to the exact
event_file from the plan
- immediately run extract on that single slice:
CODEBLOCK1
- If extract fails with invalid JSON in <event_file> or any other parse error, treat that slice file as damaged or truncated.
- Stop immediately. Tell the user refresh failed because one slice result was not valid JSON.
- Do not continue to later slices and do not run finalize-refresh.
- 5. After all slices succeed, finalize refresh with one helper command.
This command is the only allowed way to write
state/feishu-meeting-room-book.json during refresh:
CODEBLOCK2
- 6. Read
tmp/meeting-room-candidates.json.
If the helper command failed or returned
saved != true, stop, tell the user refresh failed, and keep the previous state unchanged.
- 7. Show the candidate rooms to the user with explicit priority numbers. Tell the user:
- this is the current default priority order
- the current base location (default city) is
<default_city>
- if this was
default_refresh, explain that the existing list was preserved and newly discovered rooms were appended
- it has already been saved as the fallback order
- if the user does not reply, future bookings will use this default order
- if this init was triggered by a booking request, the current request will proceed with this default order
- if the user wants to adjust it, reply with:
- the base location (default city) if they want to change it
- which rooms to keep
- the final room order
- 8. If the user replies with a custom order or subset, overwrite the state with:
CODEBLOCK3
Rules:
- - Learn only from resource attendees already attached to real events.
- Prefer resource attendees with
rsvp_status=accept. - If
rsvp_status is missing in the event payload, keep the room candidate. - The helper script's candidate order is already the default priority order.
- If the user gives no reordering instructions, keep the saved default order unchanged.
- INLINECODE76 is incremental:
- keep existing rooms even if they did not appear in the current narrow scan window
- keep the existing order for already saved rooms
- append newly discovered rooms after the existing list
- only refresh metadata such as
display_name,
score,
last_seen_at, and
location_hints for rooms that were seen again
- - Room removal is not part of incremental refresh. To remove rooms, the user must explicitly reorder/trim the list or run
full_rebuild and then confirm a smaller subset. - The current official
feishu_calendar_event tool does not expose reliable paging controls for list, so keep each time slice small enough that a single slice result remains manageable. - Never fetch a whole multi-week scan window in one tool call.
- Never widen the scan window beyond
now - 20 days to now + 20 days - 1 minute. - Never use
write, edit, or any other direct file-writing tool on state/feishu-meeting-room-book.json. - During refresh,
write is allowed only for raw per-slice temp files under tmp/meeting-room-events-*.json. - Never use shell redirection like
> with meeting_room_booker.py JSON commands. Use the helper's --output flag so it writes atomically. - If any
event_file is truncated or malformed, extract will fail. Treat that as the real failure cause; do not continue with an empty or partially written candidate file. - Never hand-copy, paraphrase, or manually reconstruct the
feishu_calendar_event JSON into another file. - Never invent candidate rooms or scores from memory if parsing fails.
- The only valid persistent state file for this skill is
state/feishu-meeting-room-book.json. - Never write or read
state/meeting-room-preferences.json or any other alternate state filename. - If writing any slice result or running
extract / plan-refresh / finalize-refresh / save fails, stop immediately, tell the user refresh failed, and keep the previous state unchanged. - Never overwrite the state with a guessed list when no new candidates were extracted.
Create And Book
Use this flow when the user wants to create a new event and also book a meeting room.
Steps:
- 1. If
state/feishu-meeting-room-book.json is missing, run init first.
- Tell the user you detected first use and are initializing meeting room preferences before booking.
- After init saves the default order, continue this create-and-book flow automatically.
- 2. Load the state:
CODEBLOCK4
- 3. Parse:
- title
- start time
- end time or duration
- attendees
- city
- 4. Apply defaults before asking any follow-up:
- If title is missing, set it to
会议.
- If attendees are missing, use no extra attendees. The event is still created for the current user because
user_open_id is always passed.
- If both end time and duration are missing, set end time to
start_time + 1 hour.
- 5. If city is missing, use the saved base location (default city) from
default_city. - If city is not the saved base location (default city), create the event without a room and explain the single-city v1 limitation.
- Otherwise, iterate the cached rooms in rank order. For each room, call
feishu_calendar_freebusy with action=list and room_id=<room_id>. - Pick the first room whose
freebusy_items is empty. - Create the event with
feishu_calendar_event action=create.
- Always pass
user_open_id
- Put normal attendees in
attendees only when the user explicitly provided them
- Do not put the room into the initial create call
- 10. If a free room was found, attach it with
feishu_calendar_event_attendee action=create. - Immediately call
feishu_calendar_event_attendee action=list once to inspect the room attendee status.
Do not tell the user that topic, attendees, and duration are required. The user can simply give a start time; the defaults above fill the rest.
Interpretation:
- -
accept: room booked successfully - INLINECODE121 : event created and room booking submitted; tell the user it is pending
- INLINECODE122 or attendee-create failure: keep the event and tell the user no room was booked
Book Existing Event
Use this flow when the user wants to add a room to an existing event.
Steps:
- 1. If
state/feishu-meeting-room-book.json is missing, run init first.
- Tell the user you detected first use and are initializing meeting room preferences before booking.
- After init saves the default order, continue this booking flow automatically.
- 2. Resolve the target event:
- Prefer an explicit
event_id
- Otherwise search by title/time with
feishu_calendar_event action=search or
action=list
- If multiple plausible matches remain, ask the user to choose before mutating
- 3. Read attendees first with
feishu_calendar_event_attendee action=list. - If the event already has a resource attendee in
accept or needs_action, stop and report the current room state. - If the requested city is different from the saved base location (default city), do not try booking; explain the single-city v1 limitation.
- Otherwise, iterate cached rooms with
feishu_calendar_freebusy and attach the first free room with feishu_calendar_event_attendee. - Re-read attendees once and report the resulting room RSVP state.
Local State Shape
The helper script writes:
CODEBLOCK5
This file is renewable runtime state. Keep it in state/.
飞书会议室预定
基于飞书日历事件学习个人常用会议室,并按本地偏好列表预定会议室。
路径
- - 状态文件:state/feishu-meeting-room-book.json
- 辅助脚本:skills/feishu-meeting-room-book/scripts/meetingroombooker.py
- 刷新计划文件:tmp/meeting-room-refresh-plan.json
依赖与权限
- openclaw-lark 或任何等效的飞书/Lark 插件,需暴露以下功能:
- feishu
calendarevent
- feishu
calendarevent_attendee
- feishu
calendarfreebusy
- python3
- 捆绑的辅助脚本仅使用 Python 标准库,不需要额外的 pip 包
- 持久化状态写入 state/feishu-meeting-room-book.json
- 临时工作文件可能写入 tmp/meeting-room-events-
.json 和 tmp/meeting-room-candidates-.json
- calendar:calendar.event:read 用于在初始化/刷新时读取近期日历事件
- calendar:calendar.event:create 用于创建新的日历事件
- calendar:calendar.event:update 用于附加会议室和更新事件参与者
- calendar:calendar.free_busy:read 用于检查会议室可用性
- 此技能不调用单独的会议室预定服务
- 会议室预定通过创建或更新飞书日历事件并将会议室作为资源参与者附加来实现
必需工具
- - feishucalendarevent
- feishucalendareventattendee
- feishucalendar_freebusy
如果任何飞书工具返回认证或作用域错误,请先完成 OAuth 流程并重试。此技能的常规作用域为:
- - calendar:calendar.event:read
- calendar:calendar.event:create
- calendar:calendar.event:update
- calendar:calendar.free_busy:read
产品规则
- - 仅手动刷新。切勿自动刷新缓存的会议室。
- 缓存的会议室列表为单城市 v1 版本。存储一个基础位置(默认城市)和一个有序的会议室列表。内部状态字段名称保持为 default_city。
- 首次使用时,如果 state/feishu-meeting-room-book.json 不存在且用户要求创建/预定会议室或预定已有事件,不要要求用户单独初始化。告知用户正在开始首次初始化,立即运行初始化,并在保存默认会议室顺序后继续处理原始请求。
- 在初始化/刷新期间,提取的候选顺序为默认优先级顺序。除非用户明确更改,否则不要重新排序。
- 创建新事件时,使用默认值而不是就常见遗漏项追问用户:
- 缺少标题:使用摘要会议
- 缺少参与者:创建仅包含自己的事件,无额外参与者
- 缺少时长/结束时间:默认为 1 小时
- - 如果用户未指定城市,使用 default_city 中保存的基础位置(默认城市)。
- 如果用户指定的城市与保存的基础位置(默认城市)不同,创建事件但不自动预定会议室。解释当前缓存仅覆盖基础位置(默认城市),并告知用户手动为该城市播种并运行刷新。
- 当没有首选会议室可用时:
- create
andbook:仍然创建事件但不附加会议室
- book_existing:保持事件不变,告知用户在手动播种更多会议室后刷新
更新候选会议室
当用户询问如何更新候选会议室列表时,使用以下规则。
1. 要求用户在接下来 7 天内创建任意日历事件。
2. 告知用户将所需的会议室作为资源参与者附加到该事件。
3. 然后告知用户说刷新会议室列表、更新会议室列表、重新学习会议室或更新会议室候选表。
4. 默认刷新仅扫描一个狭窄的播种窗口,如果该会议室出现在事件负载中则将其包含。
- 运行刷新并仅保留用户仍需要的会议室。
- 或保留当前列表并用更小的子集和新顺序覆盖状态。
- 运行刷新并让用户回复最终的会议室顺序。
- 或保留当前会议室并用重新排序的 --room-id 列表覆盖状态。
- 只有所选扫描窗口内的事件影响候选列表。
- 默认刷新使用 现在 - 1 天 到 现在 + 7 天。
- 首次使用初始化使用 现在 - 7 天 到 现在 + 7 天。
- 仅显式全量重建使用 现在 - 20 天 到 现在 + 20 天 - 1 分钟。
- 当前扫描窗口外的播种事件不会更新候选列表。
初始化或刷新
当用户说以下内容时使用此流程:
- - 初始化会议室偏好
- 初始化会议室预定技能
- 刷新会议室列表
- 更新会议室列表
- 重新学习会议室
- 更新会议室候选表
- 全量刷新会议室列表
- 重建会议室列表
- 或状态文件不存在
当由于状态文件不存在而隐式触发初始化时,明确告知用户:
- - 这是该技能的首次使用
- 正在学习用户的近期会议室使用历史
- 将首先保存默认的会议室优先级顺序
- 初始化完成后将自动继续当前的预定请求
步骤:
- 1. 在调用任何飞书工具之前,选择一种扫描模式:
- default_refresh:当状态已存在且用户说刷新会议室列表、更新会议室列表、重新学习会议室或更新会议室候选表时
- 扫描窗口:现在 - 1 天 到 现在 + 7 天
- 保存行为:增量更新;保留现有会议室和顺序,追加新发现的会议室,刷新再次出现的会议室的元数据
- init:当状态文件不存在或用户明确说初始化会议室偏好或初始化会议室预定技能时
- 扫描窗口:现在 - 7 天 到 现在 + 7 天
- 保存行为:从提取的候选列表重建状态
- full_rebuild:仅当用户明确说全量刷新会议室列表或重建会议室列表时
- 扫描窗口:现在 - 20 天 到 现在 + 20 天 - 1 分钟
- 保存行为:从提取的候选列表重建状态
- 2. 使用辅助脚本构建刷新计划。
切勿手动计算切片边界:
bash
python3 skills/feishu-meeting-room-book/scripts/meetingroombooker.py plan-refresh \
--mode refresh|init|fullrebuild> \
--output tmp/meeting-room-refresh-plan.json
- 3. 读取 tmp/meeting-room-refresh-plan.json。
精确使用该计划中的 slices[].start
time、slices[].endtime、slices[].event
file 和 slices[].candidatefile 值。
不要自行创建临时文件名或时间窗口。
- 4. 对于计划中的每个切片:
- 使用 action=list 调用 feishu
calendarevent
- 立即仅将该切片结果保存到计划中指定的 event_file
- 立即对该单个切片运行提取:
bash
python3 skills/feishu-meeting-room-book/scripts/meetingroombooker.py extract \
--input filefrom_plan> \
--output filefrom_plan>
- 如果 extract 因 中的 JSON 无效 或任何其他解析错误而失败,将该切片文件视为损坏或截断。
- 立即停止。告知用户刷新失败,因为一个切片结果不是有效的 JSON。
- 不要继续处理后续切片,也不要运行 finalize-refresh。
- 5. 所有切片成功后,使用一个辅助命令完成刷新。
此命令是刷新期间写入 state/feishu-meeting-room-book.json 的唯一允许方式:
bash
python3 skills/feishu-meeting-room-book/scripts/meetingroombooker.py finalize-refresh \
--input filefromplan1> \
--input filefromplan2> \
--mode refresh|init|fullrebuild> \
--state state/feishu-meeting-room-book.json \
--output tmp/meeting-room-candidates.json
- 6. 读取 tmp/meeting-room-candidates.json。
如果辅助命令失败或返回 saved != true