fix(vc): keep bot activity meeting per item

This commit is contained in:
renaocheng
2026-07-07 16:09:42 +08:00
parent 83724b48eb
commit e291fa93fe
4 changed files with 13 additions and 27 deletions

View File

@@ -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)
}
}

View File

@@ -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" {

View File

@@ -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=<token>`) + payload field reference (whiteboard_id / operator_ids triple-id) |

View File

@@ -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