From 0883f452feb3b2a55c522e58e4342ee3966bc896 Mon Sep 17 00:00:00 2001 From: renaocheng Date: Wed, 24 Jun 2026 12:18:49 +0800 Subject: [PATCH] fix: default meeting events to normalized output --- shortcuts/vc/vc_meeting_events.go | 35 ------------ shortcuts/vc/vc_meeting_events_test.go | 53 ------------------- skills/lark-vc-agent/SKILL.md | 8 +-- .../lark-vc-agent-meeting-events.md | 45 +++++++--------- .../lark-vc-agent-meeting-list-active.md | 2 +- 5 files changed, 24 insertions(+), 119 deletions(-) diff --git a/shortcuts/vc/vc_meeting_events.go b/shortcuts/vc/vc_meeting_events.go index 427a43031..c1982ccc6 100644 --- a/shortcuts/vc/vc_meeting_events.go +++ b/shortcuts/vc/vc_meeting_events.go @@ -32,13 +32,6 @@ const ( var meetingDisplayLocation = time.FixedZone("UTC+8", 8*60*60) -type meetingEventsView string - -const ( - meetingEventsViewCompact meetingEventsView = "compact" - meetingEventsViewRaw meetingEventsView = "raw" -) - // toUnixSeconds converts a supported CLI time input into a Unix seconds string. func toUnixSeconds(input string, hint ...string) (string, error) { ts, err := common.ParseTime(input, hint...) @@ -67,15 +60,11 @@ var VCMeetingEvents = common.Shortcut{ {Name: "page-token", Desc: "page token for the next page"}, {Name: "page-size", Default: "20", Desc: "page size, 20-100 (default 20)"}, {Name: "page-all", Type: "bool", Desc: "automatically paginate through all available pages"}, - {Name: "view", Default: string(meetingEventsViewCompact), Desc: "output view: compact or raw"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := validateMeetingEventsMeetingID(runtime.Str("meeting-id")); err != nil { return err } - if _, err := meetingEventsOutputView(runtime); err != nil { - return err - } if _, err := meetingEventsPageSize(runtime); err != nil { return err } @@ -112,12 +101,6 @@ var VCMeetingEvents = common.Shortcut{ if err != nil { return err } - if view, err := meetingEventsOutputView(runtime); err != nil { - return err - } else if view == meetingEventsViewRaw { - runtime.Out(data, &output.Meta{Count: len(events)}) - return nil - } events = compactMeetingEvents(events) identity, err := meetingEventsCurrentIdentity(runtime) if err != nil { @@ -192,24 +175,6 @@ type normalizedMeetingEvent struct { Raw map[string]interface{} `json:"raw,omitempty"` } -func meetingEventsOutputView(runtime *common.RuntimeContext) (meetingEventsView, error) { - view := strings.TrimSpace(runtime.Str("view")) - if view == "" { - view = string(meetingEventsViewCompact) - } - switch meetingEventsView(view) { - case meetingEventsViewCompact: - return meetingEventsViewCompact, nil - case meetingEventsViewRaw: - if runtime.Format != "" && runtime.Format != "json" { - return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--view raw only supports --format json").WithParam("--view") - } - return meetingEventsViewRaw, nil - default: - return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--view must be compact or raw, got %q", view).WithParam("--view") - } -} - func buildNormalizedMeetingEvents(data map[string]interface{}, events []interface{}, currentRoster []interface{}, identity normalizedIdentity) normalizedMeetingEventsOutput { normalized := normalizedMeetingEventsOutput{ Identity: identity, diff --git a/shortcuts/vc/vc_meeting_events_test.go b/shortcuts/vc/vc_meeting_events_test.go index 1625f5ddc..afde54a94 100644 --- a/shortcuts/vc/vc_meeting_events_test.go +++ b/shortcuts/vc/vc_meeting_events_test.go @@ -27,7 +27,6 @@ func newMeetingEventsRuntime() *common.RuntimeContext { cmd.Flags().String("page-token", "", "") cmd.Flags().String("page-size", "", "") cmd.Flags().Bool("page-all", false, "") - cmd.Flags().String("view", "compact", "") return common.TestNewRuntimeContext(cmd, defaultConfig()) } @@ -592,39 +591,6 @@ func TestMeetingEvents_ExecuteJSON_UserIdentitySkipsBotInfo(t *testing.T) { } } -func TestMeetingEvents_ExecuteRawJSONSkipsBotInfo(t *testing.T) { - f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig()) - reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "1710000000000000000")) - - err := mountAndRun(t, VCMeetingEvents, []string{ - "+meeting-events", - "--meeting-id", "7628568141510692381", - "--format", "json", - "--view", "raw", - "--as", "bot", - }, f, stdout) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - reg.Verify(t) - - out := strings.ReplaceAll(stdout.String(), " ", "") - out = strings.ReplaceAll(out, "\n", "") - for _, want := range []string{ - `"events":[`, - `"event_type":"participant_joined"`, - `"has_more":true`, - `"page_token":"1710000000000000000"`, - } { - if !strings.Contains(out, want) { - t.Fatalf("raw json output missing %q: %s", want, stdout.String()) - } - } - if strings.Contains(out, `"current_roster"`) || strings.Contains(out, `"identity":{`) || strings.Contains(out, `"bot":{`) { - t.Fatalf("raw json should preserve legacy events envelope without normalized fields: %s", stdout.String()) - } -} - func TestMeetingEvents_ExecuteNDJSONIncludesMetadataRow(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig()) reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "1710000000000000000")) @@ -664,25 +630,6 @@ func TestMeetingEvents_ExecuteNDJSONIncludesMetadataRow(t *testing.T) { } } -func TestMeetingEvents_Validation_RawRejectsPretty(t *testing.T) { - runtime := newMeetingEventsRuntime() - mustSetMeetingEventsFlag(t, runtime, "meeting-id", "7628568141510692381") - mustSetMeetingEventsFlag(t, runtime, "view", "raw") - runtime.Format = "pretty" - - err := VCMeetingEvents.Validate(context.Background(), runtime) - if err == nil { - t.Fatal("expected validation error for raw pretty") - } - var ve *errs.ValidationError - if !errors.As(err, &ve) { - t.Fatalf("expected *errs.ValidationError, got %T: %v", err, err) - } - if ve.Param != "--view" { - t.Errorf("Param = %q, want %q", ve.Param, "--view") - } -} - func TestMeetingEvents_ExecuteJSON_PrunesEmptySlices(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig()) reg.Register(meetingEventsStub([]interface{}{chatReceivedEvent()}, false, "")) diff --git a/skills/lark-vc-agent/SKILL.md b/skills/lark-vc-agent/SKILL.md index 684fc7e5e..239c6d22f 100644 --- a/skills/lark-vc-agent/SKILL.md +++ b/skills/lark-vc-agent/SKILL.md @@ -72,7 +72,7 @@ metadata: - 再根据 `note_display_type`、`note_id`、`minute_token` 和用户意图,按 [`lark-vc`](../lark-vc/SKILL.md) 的产物决策读取正文、逐字稿或妙记。 - 想看参会人快照:用 `vc meeting get --with-participants`(见 [`lark-vc`](../lark-vc/SKILL.md)) 5. **默认必须使用** **`--page-all`**,除非用户明确要求“只查一页”,或确实需要控制返回体大小。 -6. 默认 `--view compact` 输出 normalized 契约:`meeting`、`identity`、`current_roster`、`events`、`has_more`、`page_token`,参会人含 `participant_type`、`role`、`is_self` 和可读 `label`。需要兼容旧事件包时用 `--view raw --format json`,raw 不支持 pretty/ndjson。 +6. 命令默认输出 normalized 事件契约:`meeting`、`identity`、`current_roster`、`events`、`has_more`、`page_token`,参会人含 `participant_type`、`role`、`is_self` 和可读 `label`;事件中的原始细节保留在 `payload/raw`。 7. 输出格式默认优先 `--format pretty`(时间线更易读,并带当前身份与当前名单标签);需要结构化处理时用 `--format json`;需要流式消费事件时用 `--format ndjson`。 8. **必须识别分页信号**:只要响应里出现 `has_more=true`、pretty 里的 `more available`,或返回了非空 `page_token`,就不能把当前结果当作完整事件流;默认应继续分页,或明确告诉用户当前只是部分结果。 9. 保留响应里的 `page_token`,下次增量拉取直接续,不要从头再拉。 @@ -106,7 +106,7 @@ MID=$(echo "$JOIN" | jq -r '.data.meeting.id') # 2. 会中轮询事件 # 默认用 --page-all 拉全当前可见事件;下次增量优先复用 page_token # 典型间隔 10-30 秒 -lark-cli vc +meeting-events --as bot --meeting-id "$MID" --page-all --view compact --format pretty +lark-cli vc +meeting-events --as bot --meeting-id "$MID" --page-all --format pretty # 3. 会后可选:进入 lark-vc 获取会议产物信息,再按 note_display_type / minute_token 决策读取 lark-cli vc +notes --meeting-ids "$MID" @@ -118,14 +118,14 @@ lark-cli vc +notes --meeting-ids "$MID" ```bash lark-cli vc +meeting-list-active --as bot --user-id --format json -lark-cli vc +meeting-events --as bot --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as bot --meeting-id --page-all --format pretty ``` 如果只是回答当前登录用户所在会议发生了什么,使用用户身份一路查: ```bash lark-cli vc +meeting-list-active --as user --format json -lark-cli vc +meeting-events --as user --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as user --meeting-id --page-all --format pretty ``` ## Shortcuts diff --git a/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md b/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md index 6c18215e5..24e2baf76 100644 --- a/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md +++ b/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md @@ -14,17 +14,14 @@ ## 命令 ```bash -# 默认用法:全量拉取当前身份可见事件,输出 normalized compact 契约 -lark-cli vc +meeting-events --as --meeting-id --page-all --view compact --format pretty +# 默认用法:全量拉取当前身份可见事件;输出 normalized 事件契约 +lark-cli vc +meeting-events --as --meeting-id --page-all --format pretty # 指定时间范围,并拉全该时间窗内当前可见事件 -lark-cli vc +meeting-events --as --meeting-id --start 2026-04-17T15:00:00+08:00 --end 2026-04-17T16:00:00+08:00 --page-all --view compact --format pretty +lark-cli vc +meeting-events --as --meeting-id --start 2026-04-17T15:00:00+08:00 --end 2026-04-17T16:00:00+08:00 --page-all --format pretty # 基于上一次保存的 page_token 继续查新增事件 -lark-cli vc +meeting-events --as --meeting-id --page-token --page-all --view compact --format pretty - -# 兼容旧事件包或调试服务端原始响应时,使用 raw escape hatch -lark-cli vc +meeting-events --as --meeting-id --page-size 20 --view raw --format json +lark-cli vc +meeting-events --as --meeting-id --page-token --page-all --format pretty ``` ## 参数 @@ -37,7 +34,6 @@ lark-cli vc +meeting-events --as --meeting-id --page-size 2 | `--page-token ` | 否 | 从指定分页游标继续拉取下一页 | | `--page-size ` | 否 | 单页模式每页大小。CLI 会自动夹紧到 `20-100`;传 `--page-all` 时固定使用 `100` | | `--page-all` | 否 | 自动分页,直到没有更多页面为止(内部有安全上限) | -| `--view compact\|raw` | 否 | 输出视图。默认 `compact` 为 agent-facing normalized 契约;`raw` 只支持 `--format json` 并保留旧事件包 | ## 核心约束 @@ -69,21 +65,21 @@ lark-cli vc +meeting-events --as --meeting-id --page-size 2 lark-cli vc +meeting-join --as bot --meeting-number 123456789 # 再查询事件 -lark-cli vc +meeting-events --as bot --meeting-id --view compact +lark-cli vc +meeting-events --as bot --meeting-id ``` 如果应用机器人已经在会中,也可以先通过 active meeting 找会: ```bash lark-cli vc +meeting-list-active --as bot --user-id --format json -lark-cli vc +meeting-events --as bot --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as bot --meeting-id --page-all --format pretty ``` 如果只是用当前登录用户发现会议: ```bash lark-cli vc +meeting-list-active --as user --format json -lark-cli vc +meeting-events --as user --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as user --meeting-id --page-all --format pretty ``` 若应用机器人已离会、未入会、或会议已经无法再判断身份,后端通常会报: @@ -106,20 +102,19 @@ lark-cli vc +meeting-events --as user --meeting-id --page-all --view compac 执行准则: -- **默认命令模板**:`lark-cli vc +meeting-events --as --meeting-id --page-all --view compact --format pretty` +- **默认命令模板**:`lark-cli vc +meeting-events --as --meeting-id --page-all --format pretty` - 如果你发现自己执行成了不带 `--page-all` 的单页查询,而响应里又出现 `has_more=true` / `more available` / 非空 `page_token`,应立刻意识到这只是部分结果。 -- 遇到上述情况,默认补救方式是继续使用返回的 `page_token` 续拉,例如:`lark-cli vc +meeting-events --as --meeting-id --page-token --page-all --view compact --format pretty` +- 遇到上述情况,默认补救方式是继续使用返回的 `page_token` 续拉,例如:`lark-cli vc +meeting-events --as --meeting-id --page-token --page-all --format pretty` - 只有在用户明确要求“就看第一页”“先不要翻页”时,才不要默认带 `--page-all` - 只要你是基于 `+meeting-events` 来回答一场**正在进行中的会议内容**,就不能直接复用上一次查询结果。无论用户是在问“现在是谁在说话”“刚刚发生了什么”“最新事件有哪些”,还是让你“总结一下这个会议讲什么”,都必须先重新执行一次 `+meeting-events`,确认拿到的是最新事件流,再回答用户。只有在用户明确要求基于某次历史快照继续分析时,才可以复用旧结果。 -### 5. compact / raw / pretty / json / ndjson 输出差异 +### 5. 输出格式差异 -- `--view compact --format json`:默认结构化契约,顶层包含 `meeting`、`identity`、`current_roster`、`events`、`has_more`、`page_token`。参会人身份统一含 `participant_type`、`role`、`is_self`、`label`。 -- `--view compact --format pretty`:输出当前身份、当前名单和逐条时间线,适合快速理解“发生了什么”。 -- `--view compact --format ndjson`:输出事件行,并带 metadata 行,适合流式消费。 -- `--view raw --format json`:保留旧 `events/has_more/page_token` 服务端事件包,不预取 identity 或 current roster。raw 不支持 pretty/ndjson。 +- `--format json`:结构化契约,顶层包含 `meeting`、`identity`、`current_roster`、`events`、`has_more`、`page_token`。参会人身份统一含 `participant_type`、`role`、`is_self`、`label`;每条事件保留 `payload/raw` 便于追溯原始细节。 +- `--format pretty`:输出当前身份、当前名单和逐条时间线,适合快速理解“发生了什么”。 +- `--format ndjson`:输出事件行,并带 metadata 行,适合流式消费。 -**选型原则**:只要目标是告诉用户“发生了什么”,默认就用 `--page-all --view compact --format pretty`;需要稳定字段给 agent 消费时用 compact JSON;只有需要兼容旧事件包时才用 raw JSON。 +**选型原则**:只在 `pretty`、`json`、`ndjson` 之间选择。目标是告诉用户“发生了什么”时,用 `--page-all --format pretty`;需要稳定字段给 agent 消费时用 `--format json`;需要流式消费时用 `--format ndjson`。 > **注意**:pretty 输出中的正文文本会做单行转义,真实换行会显示为 `\n`,避免打乱时间线布局。 @@ -136,10 +131,10 @@ lark-cli vc +meeting-events --as user --meeting-id --page-all --view compac 执行准则: -- 如果上下文已有明确 `meeting_id`,沿用该 `meeting_id` 的来源身份执行 `+meeting-events --page-all --view compact --format json`。 +- 如果上下文已有明确 `meeting_id`,沿用该 `meeting_id` 的来源身份执行 `+meeting-events --page-all --format json`。 - 如果上下文没有明确 `meeting_id`,先按用户当前意图选择身份:问“我/当前用户所在会议”用 `lark-cli vc +meeting-list-active --as user --format pretty`;问“应用机器人可见的目标用户会议”用 `lark-cli vc +meeting-list-active --as bot --user-id --format pretty`。返回多个会议时先让用户选择。 - 如果上下文只有 9 位会议号,先按当前身份执行 `+meeting-list-active` 并按 `meeting_no` 匹配;匹配到唯一会议后再查事件。不要为了总结会议而自动调用 `+meeting-join`。 -- 这类问题拿到 `meeting_id` 后,用同一身份执行 `lark-cli vc +meeting-events --as --meeting-id --page-all --view compact --format json` 拉取最新事件流。 +- 这类问题拿到 `meeting_id` 后,用同一身份执行 `lark-cli vc +meeting-events --as --meeting-id --page-all --format json` 拉取最新事件流。 - 如果事件中出现共享文档线索,例如: - `magic_share_started` - `share_doc.title` @@ -211,21 +206,21 @@ lark-cli vc +meeting-events --as user --meeting-id --page-all --view compac lark-cli vc +meeting-join --as bot --meeting-number 123456789 # 第 2 步:查询事件流 -lark-cli vc +meeting-events --as bot --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as bot --meeting-id --page-all --format pretty ``` ### 场景 1b:应用机器人已在会中,先发现 meeting_id 再读事件 ```bash lark-cli vc +meeting-list-active --as bot --user-id --format json -lark-cli vc +meeting-events --as bot --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as bot --meeting-id --page-all --format pretty ``` ### 场景 1c:当前登录用户正在会中,先发现 meeting_id 再读事件 ```bash lark-cli vc +meeting-list-active --as user --format json -lark-cli vc +meeting-events --as user --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as user --meeting-id --page-all --format pretty ``` ### 场景 2:过滤某段时间内的事件 @@ -237,7 +232,6 @@ lark-cli vc +meeting-events \ --start 2026-04-17T15:00:00+08:00 \ --end 2026-04-17T16:00:00+08:00 \ --page-all \ - --view compact \ --format pretty ``` @@ -251,7 +245,6 @@ lark-cli vc +meeting-events \ --meeting-id \ --page-token \ --page-all \ - --view compact \ --format pretty ``` diff --git a/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md b/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md index 8f6b3cc16..c6105a3bc 100644 --- a/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md +++ b/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md @@ -46,7 +46,7 @@ lark-cli vc +meeting-events --as bot --meeting-id --page-all --format prett # 方式 3:只回答当前登录用户所在会议发生了什么 lark-cli vc +meeting-list-active --as user --format json -lark-cli vc +meeting-events --as user --meeting-id --page-all --view compact --format pretty +lark-cli vc +meeting-events --as user --meeting-id --page-all --format pretty ``` ## 多会议选择