diff --git a/events/vc/bot_events.go b/events/vc/bot_events.go index ae5c076c8..3fd11e661 100644 --- a/events/vc/bot_events.go +++ b/events/vc/bot_events.go @@ -19,7 +19,6 @@ type VCBotEventOutput struct { Timestamp string `json:"timestamp,omitempty" desc:"Event delivery time (ms timestamp string); taken from header.create_time when present" kind:"timestamp_ms"` CallID string `json:"call_id,omitempty" desc:"Bot invitation call ID; pass through to vc agent join when present"` MeetingNo string `json:"meeting_no,omitempty" desc:"Meeting number from the bot event's declared meeting field"` - Meeting map[string]interface{} `json:"meeting,omitempty" desc:"Meeting object extracted from the first activity item that carries one"` MeetingActivityItems []map[string]interface{} `json:"meeting_activity_items,omitempty" desc:"event.meeting_activity_items extracted one level up for direct consumption; nested user id objects keep id.open_id"` Payload json.RawMessage `json:"payload,omitempty" desc:"Original bot event body without schema/header envelope for non-activity events or malformed activity payloads"` } @@ -98,7 +97,6 @@ func fillBotEventOutput(eventType string, data json.RawMessage, out *VCBotEventO return } out.MeetingNo = botActivityMeetingNo(items) - out.Meeting = normalizeBotActivityMeeting(items) meetingActivityItems := normalizeBotActivityItems(items) if len(meetingActivityItems) == 0 { out.Payload = cloneRawMessage(data) @@ -159,19 +157,6 @@ func botActivityMeetingNo(items []interface{}) string { return "" } -func normalizeBotActivityMeeting(items []interface{}) map[string]interface{} { - for _, raw := range items { - item, _ := raw.(map[string]interface{}) - meeting, _ := item["meeting"].(map[string]interface{}) - if len(meeting) == 0 { - continue - } - normalized, _ := normalizeBotActivityValue(meeting).(map[string]interface{}) - return normalized - } - return nil -} - func normalizeBotActivityItems(items []interface{}) []map[string]interface{} { normalized := make([]map[string]interface{}, 0, len(items)) for _, raw := range items { @@ -181,7 +166,6 @@ func normalizeBotActivityItems(items []interface{}) []map[string]interface{} { } normalizedItem, _ := normalizeBotActivityValue(item).(map[string]interface{}) if normalizedItem != nil { - delete(normalizedItem, "meeting") normalized = append(normalized, normalizedItem) } } diff --git a/events/vc/bot_events_test.go b/events/vc/bot_events_test.go index 2181cffb4..aa5d41797 100644 --- a/events/vc/bot_events_test.go +++ b/events/vc/bot_events_test.go @@ -216,9 +216,8 @@ func TestProcessVCBotEvents_StableFields(t *testing.T) { if _, ok := row["payload"]; ok { t.Fatalf("meeting activity output should lift meeting_activity_items out of payload: %s", string(got)) } - meeting, ok := row["meeting"].(map[string]any) - if !ok || meeting["meeting_no"] != tc.want.MeetingNo { - t.Fatalf("meeting activity output should include top-level meeting: %s", string(got)) + if _, ok := row["meeting"]; ok { + t.Fatalf("meeting activity output should not include top-level meeting: %s", string(got)) } items, ok := row["meeting_activity_items"].([]any) if !ok || len(items) == 0 { @@ -226,8 +225,12 @@ func TestProcessVCBotEvents_StableFields(t *testing.T) { } for _, rawItem := range items { item, _ := rawItem.(map[string]any) - if _, ok := item["meeting"]; ok { - t.Fatalf("meeting_activity_items should not repeat meeting: %s", string(got)) + meeting, ok := item["meeting"].(map[string]any) + if !ok { + t.Fatalf("meeting_activity_items should keep meeting on each item: %s", string(got)) + } + if meeting["meeting_no"] == nil { + t.Fatalf("meeting_activity_items meeting should keep meeting_no: %s", string(got)) } } if tc.name == "meeting activity ignores nested reaction details" { diff --git a/skills/lark-event/SKILL.md b/skills/lark-event/SKILL.md index 23bf4b890..da3279b0c 100644 --- a/skills/lark-event/SKILL.md +++ b/skills/lark-event/SKILL.md @@ -149,6 +149,6 @@ Lark-defined semantic tags (**not** JSON Schema's standard `format`). Common val |------------|------------------------------------------------------------------------------|---| | IM | [`references/lark-event-im.md`](references/lark-event-im.md) | Catalog of 12 IM EventKeys + shape notes (flat vs V2 envelope) + `im.message.receive_v1` field gotchas (`sender_id` is open_id only; `.content` is plain text except for `interactive` cards) + common jq recipes (filter by chat_type / message_type / sender); for `card.action.trigger` see also [`../lark-im/references/lark-im-card-action-reply.md`](../lark-im/references/lark-im-card-action-reply.md) | | Task | [`references/lark-event-task.md`](references/lark-event-task.md) | Catalog of 1 Task EventKey (`task.task.update_user_access_v2`) + Native V2 envelope shape + task commit types + user/bot subscription notes | -| VC | [`references/lark-event-vc.md`](references/lark-event-vc.md) | VC user events plus bot-observed EventKeys (`vc.bot.meeting_invited_v1`, `vc.bot.meeting_activity_v1`, `vc.bot.meeting_ended_v1`) + compact stable fields, event-body payload reference, and meeting-events forwarding guidance | +| VC | [`references/lark-event-vc.md`](references/lark-event-vc.md) | VC user events plus bot-observed EventKeys (`vc.bot.meeting_invited_v1`, `vc.bot.meeting_activity_v1`, `vc.bot.meeting_ended_v1`) + compact stable fields, activity `meeting_activity_items`, fallback payload semantics, and meeting-events forwarding guidance | | Minutes | [`references/lark-event-minutes.md`](references/lark-event-minutes.md) | Catalog of 1 Minutes EventKey (`minutes.minute.generated_v1`) + field reference + source type semantics (meeting only) | | Whiteboard | [`references/lark-event-whiteboard.md`](references/lark-event-whiteboard.md) | Catalog of 1 Board EventKey (`board.whiteboard.updated_v1`) + per-whiteboard subscription model (requires `-p whiteboard_id=`) + payload field reference (whiteboard_id / operator_ids triple-id) | diff --git a/skills/lark-event/references/lark-event-vc.md b/skills/lark-event/references/lark-event-vc.md index 8e93984c0..a547acf14 100644 --- a/skills/lark-event/references/lark-event-vc.md +++ b/skills/lark-event/references/lark-event-vc.md @@ -141,12 +141,11 @@ These keys model what the bot observes. Do not treat them as aliases for: | `event_id` | string | Globally unique event ID; safe for deduplication | | `timestamp` | string (timestamp_ms) | Event delivery time from `header.create_time` when present | | `call_id` | string | Invitation call ID; pass through to VC agent join when present | -| `meeting_no` | string | Meeting number from the bot event's declared meeting field | -| `meeting` | object | First `event.meeting_activity_items[].meeting` object extracted one level up for `vc.bot.meeting_activity_v1` | -| `meeting_activity_items` | object[] | `event.meeting_activity_items` extracted one level up for `vc.bot.meeting_activity_v1`; item-level duplicate `meeting` is removed, each item carries its own `activity_event_type`, and nested user `id` objects keep `id.open_id` | -| `payload` | object | Original bot event body without the `schema` / `header` envelope for non-activity events or malformed activity payloads | +| `meeting_no` | string | Stable top-level meeting number. For `vc.bot.meeting_activity_v1`, this mirrors the first activity item's `meeting.meeting_no`; for invited/ended events, it comes from the event body directly. Prefer this field for routing and joins across `vc.bot.*` keys | +| `meeting_activity_items` | object[] | Only emitted for normal `vc.bot.meeting_activity_v1` output. This is `event.meeting_activity_items` lifted one level up; each item keeps its own `activity_event_type` and `meeting`, and nested user `id` objects keep only `id.open_id` | +| `payload` | object | Fallback/event-body field for non-activity bot events (`vc.bot.meeting_invited_v1`, `vc.bot.meeting_ended_v1`) or malformed activity payloads. Normal `vc.bot.meeting_activity_v1` output does not include `payload` | -Normal bot-event output intentionally does not include the full raw envelope. Use stable top-level fields for routing; for `vc.bot.meeting_activity_v1`, consume `meeting_activity_items[]` directly and branch on each item's `activity_event_type`. Do not assume one delivered bot event contains only one activity type. The repeated item-level `meeting` object is lifted to top-level `meeting` to avoid duplicating meeting metadata in every item. For richer cross-event meeting analysis or IM forwarding, prefer `vc +meeting-events --format json`, which normalizes actors, chat/reaction payloads, subtitles, and magic-share events. If the top-level event envelope cannot be parsed at all, `event consume` still passes the raw payload through for diagnostics. +Normal bot-event output intentionally does not include the full raw envelope. Use stable top-level fields for routing. For `vc.bot.meeting_activity_v1`, consume `meeting_activity_items[]` directly and branch on each item's `activity_event_type`; do not read activity content from `payload`, because normal activity output does not emit it. Do not assume one delivered bot event contains only one activity type. Meeting metadata stays on each activity item under `meeting`; there is no top-level `meeting` field. `meeting_no` remains top-level even when item-level `meeting.meeting_no` is also present so agents can use the same scalar field across invited, activity, and ended bot events. For richer cross-event meeting analysis or IM forwarding, prefer `vc +meeting-events --format json`, which normalizes actors, chat/reaction payloads, subtitles, and magic-share events. If the top-level event envelope cannot be parsed at all, `event consume` still passes the raw payload through for diagnostics. ### Forwarding meeting activity to IM