基于飞书日历事件学习个人常用会议室,并按本地偏好列表预定会议室。
state/feishu-meeting-room-book.jsonskills/feishu-meeting-room-book/scripts/meeting_room_booker.pytmp/meeting-room-refresh-plan.jsonopenclaw-lark or any equivalent Feishu/Lark plugin that exposes:
feishu_calendar_eventfeishu_calendar_event_attendeefeishu_calendar_freebusypython3state/feishu-meeting-room-book.jsontmp/meeting-room-events-*.json and tmp/meeting-room-candidates-*.jsoncalendar:calendar.event:read for reading recent calendar events during init/refreshcalendar:calendar.event:create for creating new calendar eventscalendar:calendar.event:update for attaching meeting rooms and updating event attendeescalendar:calendar.free_busy:read for checking room availabilityresource attendeefeishu_calendar_eventfeishu_calendar_event_attendeefeishu_calendar_freebusyIf any Feishu tool returns auth or scope errors, finish the OAuth flow first and retry. The normal scopes for this skill are:
calendar:calendar.event:readcalendar:calendar.event:createcalendar:calendar.event:updatecalendar:calendar.free_busy:readdefault_city.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.会议default_city.create_and_book: still create the event without a roombook_existing: keep the event unchanged and tell the user to refresh after manually seeding more roomsUse these rules when the user asks how to update the candidate room list.
resource attendee.刷新会议室列表, 更新会议室列表, 重新学习会议室, or 更新会议室候选表.--room-id list.now - 1 day to now + 7 days.now - 7 days to now + 7 days.now - 20 days to now + 20 days - 1 minute.Use this flow when the user says:
When init is triggered implicitly because the state file does not exist, explicitly tell the user:
Steps:
default_refresh: when state already exists and the user says 刷新会议室列表, 更新会议室列表, 重新学习会议室, or 更新会议室候选表
now - 1 day to now + 7 daysinit: when the state file does not exist or the user explicitly says 初始化会议室偏好 or 初始化会议室预定技能
now - 7 days to now + 7 daysfull_rebuild: only when the user explicitly says 全量刷新会议室列表 or 重建会议室列表
now - 20 days to now + 20 days - 1 minutebash
python3 skills/feishu-meeting-room-book/scripts/meeting_room_booker.py plan-refresh \
--mode <default_refresh|init|full_rebuild> \
--output tmp/meeting-room-refresh-plan.json
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.feishu_calendar_event with action=listevent_file from the planbash
python3 skills/feishu-meeting-room-book/scripts/meeting_room_booker.py extract \
--input <event_file_from_plan> \
--output <candidate_file_from_plan>
extract fails with invalid JSON in <event_file> or any other parse error, treat that slice file as damaged or truncated.finalize-refresh.state/feishu-meeting-room-book.json during refresh:bash
python3 skills/feishu-meeting-room-book/scripts/meeting_room_booker.py finalize-refresh \
--input <candidate_file_from_plan_1> \
--input <candidate_file_from_plan_2> \
--mode <default_refresh|init|full_rebuild> \
--state state/feishu-meeting-room-book.json \
--output tmp/meeting-room-candidates.json
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.<default_city>default_refresh, explain that the existing list was preserved and newly discovered rooms were appendedbash
python3 skills/feishu-meeting-room-book/scripts/meeting_room_booker.py save \
--input tmp/meeting-room-candidates.json \
--state state/feishu-meeting-room-book.json \
--default-city "<city>" \
--output tmp/meeting-room-candidates-confirmed.json \
--room-id omm_first \
--room-id omm_second
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.
default_refresh is incremental:
display_name, score, last_seen_at, and location_hints for rooms that were seen againRoom 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.
Use this flow when the user wants to create a new event and also book a meeting room.
Steps:
If state/feishu-meeting-room-book.json is missing, run init first.
Load the state:
bash
python3 skills/feishu-meeting-room-book/scripts/meeting_room_booker.py show --state state/feishu-meeting-room-book.json --json
Parse:
Apply defaults before asking any follow-up:
会议.user_open_id is always passed.start_time + 1 hour.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.
user_open_idattendees only when the user explicitly provided themIf 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 successfullyneeds_action: event created and room booking submitted; tell the user it is pendingdecline or attendee-create failure: keep the event and tell the user no room was bookedUse this flow when the user wants to add a room to an existing event.
Steps:
If state/feishu-meeting-room-book.json is missing, run init first.
Resolve the target event:
event_idfeishu_calendar_event action=search or action=listRead 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.
The helper script writes:
json
{
"version": 1,
"default_city": "Shanghai",
"updated_at": "2026-03-17T20:00:00+08:00",
"rooms": [
{
"room_id": "omm_xxx",
"display_name": "A5-01",
"rank": 1,
"score": 18,
"last_seen_at": "2026-03-16T10:00:00+08:00",
"location_hints": ["Shanghai HQ"]
}
]
}
This file is renewable runtime state. Keep it in state/.