diff --git a/shortcuts/vc/skill_docs_test.go b/shortcuts/vc/skill_docs_test.go index e06c92a0cf..86f7a5f891 100644 --- a/shortcuts/vc/skill_docs_test.go +++ b/shortcuts/vc/skill_docs_test.go @@ -53,6 +53,17 @@ func TestVCSearchIdentityDocsMatchAuthTypes(t *testing.T) { } } +func TestVCMeetingLeaveDocsMatchBotOnlyAuthTypes(t *testing.T) { + if len(VCMeetingLeave.AuthTypes) != 1 || VCMeetingLeave.AuthTypes[0] != "bot" { + t.Fatalf("VCMeetingLeave.AuthTypes = %v, want [bot]", VCMeetingLeave.AuthTypes) + } + + reference := readSkillDoc(t, "skills/lark-meeting/references/lark-vc-agent-meeting-leave.md") + if !strings.Contains(reference, "仅支持 `bot` 身份") { + t.Error("lark-vc-agent-meeting-leave.md must state that +meeting-leave only supports bot identity") + } +} + // TestVCBotShortcutsIdentityDocsMatchAuthTypes pins that the VC shortcuts this // PR opened to bot (`+detail`, `+recording`, `+meeting-countdown`) are all declared bot-capable in // code and documented as such in their lark-meeting references. @@ -84,6 +95,32 @@ func TestVCBotShortcutsIdentityDocsMatchAuthTypes(t *testing.T) { } } +func TestVCMeetingJoinDocsRouteCalendarStart(t *testing.T) { + skill := readSkillDoc(t, "skills/lark-meeting/SKILL.md") + scene := readSkillDoc(t, "skills/lark-meeting/scenes/live-meeting-attend.md") + reference := readSkillDoc(t, "skills/lark-meeting/references/lark-vc-agent-meeting-join.md") + + if !strings.Contains(reference, "# vc +meeting-join:发起日程会议或加入会议") { + t.Error("lark-vc-agent-meeting-join.md must identify Calendar meeting starts") + } + for _, want := range []string{ + "机器人发起日程会议或参与会议", + "让应用机器人发起日程会议或加入会议", + } { + if !strings.Contains(skill, want) { + t.Errorf("SKILL.md must document %q", want) + } + } + for name, content := range map[string]string{ + "live-meeting-attend.md": scene, + "lark-vc-agent-meeting-join.md": reference, + } { + if !strings.Contains(content, "应用机器人发起会议、参会与会中互动") { + t.Errorf("%s must route application-bot meeting starts", name) + } + } +} + func TestMeetingArtifactSceneDelegatesToDomainOwners(t *testing.T) { scene := readSkillDoc(t, "skills/lark-meeting/scenes/query-meeting-and-artifacts.md") for _, target := range []string{ diff --git a/shortcuts/vc/vc_meeting_join.go b/shortcuts/vc/vc_meeting_join.go index c0264eacba..584240551a 100644 --- a/shortcuts/vc/vc_meeting_join.go +++ b/shortcuts/vc/vc_meeting_join.go @@ -47,7 +47,7 @@ var VCMeetingJoin = common.Shortcut{ Description: "Join a meeting by meeting number (bot join)", Risk: "write", Scopes: []string{"vc:meeting.bot.join:write"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "meeting-number", Required: true, Desc: "meeting number to join"}, @@ -63,9 +63,6 @@ var VCMeetingJoin = common.Shortcut{ if !validMeetingNumber(mn) { return errs.NewValidationError(errs.SubtypeInvalidArgument, "--meeting-number must be exactly 9 digits, got %q", mn).WithParam("--meeting-number") } - if meetingJoinAction(runtime) == meetingJoinActionStart && !runtime.As().IsBot() { - return errs.NewValidationError(errs.SubtypeInvalidArgument, "--action start requires --as bot").WithParam("--action") - } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { diff --git a/shortcuts/vc/vc_meeting_leave.go b/shortcuts/vc/vc_meeting_leave.go index 959c7c90ac..b659fb205f 100644 --- a/shortcuts/vc/vc_meeting_leave.go +++ b/shortcuts/vc/vc_meeting_leave.go @@ -20,7 +20,7 @@ var VCMeetingLeave = common.Shortcut{ Description: "Leave a meeting by meeting ID", Risk: "write", Scopes: []string{"vc:meeting.bot.join:write"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "meeting-id", Required: true, Desc: "meeting ID to leave"}, diff --git a/shortcuts/vc/vc_meeting_test.go b/shortcuts/vc/vc_meeting_test.go index f1d398c9bd..35ff79d4df 100644 --- a/shortcuts/vc/vc_meeting_test.go +++ b/shortcuts/vc/vc_meeting_test.go @@ -216,7 +216,7 @@ func TestBuildMeetingJoinBody_TrimsCallIDWhitespace(t *testing.T) { func TestMeetingJoin_Validate_MissingNumber(t *testing.T) { f, _, _, _ := cmdutil.TestFactory(t, defaultConfig()) // cobra MarkFlagRequired should reject missing --meeting-number - err := mountAndRun(t, VCMeetingJoin, []string{"+meeting-join", "--as", "user"}, f, nil) + err := mountAndRun(t, VCMeetingJoin, []string{"+meeting-join", "--as", "bot"}, f, nil) if err == nil { t.Fatal("expected error when --meeting-number is missing") } @@ -266,18 +266,17 @@ func TestMeetingJoin_Validate_Valid(t *testing.T) { } } -func TestMeetingJoin_Validate_StartRequiresBot(t *testing.T) { - f, _, _, _ := cmdutil.TestFactory(t, defaultConfig()) - - err := mountAndRun(t, VCMeetingJoin, []string{ - "+meeting-join", "--as", "user", - "--meeting-number", "123456789", - "--action", "start", - "--dry-run", - }, f, nil) - - if err == nil || !strings.Contains(err.Error(), "--action start requires --as bot") { - t.Fatalf("start action error = %v", err) +func TestMeetingJoinAndLeave_BotOnly(t *testing.T) { + for _, shortcut := range []struct { + name string + authTypes []string + }{ + {name: "+meeting-join", authTypes: VCMeetingJoin.AuthTypes}, + {name: "+meeting-leave", authTypes: VCMeetingLeave.AuthTypes}, + } { + if len(shortcut.authTypes) != 1 || shortcut.authTypes[0] != "bot" { + t.Errorf("%s AuthTypes = %v, want [bot]", shortcut.name, shortcut.authTypes) + } } } @@ -289,7 +288,7 @@ func TestMeetingJoin_DryRun(t *testing.T) { f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig()) err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "123456789", "--password", "pw123", - "--dry-run", "--as", "user", + "--dry-run", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -434,7 +433,7 @@ func TestMeetingJoin_Execute_Success(t *testing.T) { err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "123456789", - "--format", "json", "--as", "user", + "--format", "json", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -489,7 +488,7 @@ func TestMeetingJoin_Execute_WithPassword_CapturesBody(t *testing.T) { err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "987654321", "--password", "s3cret", - "--format", "json", "--as", "user", + "--format", "json", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -529,7 +528,7 @@ func TestMeetingJoin_Execute_PrettyOutput(t *testing.T) { err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "123456789", - "--format", "pretty", "--as", "user", + "--format", "pretty", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -556,7 +555,7 @@ func TestMeetingJoin_Execute_PrettyOutput_NoMeetingInfo(t *testing.T) { err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "123456789", - "--format", "pretty", "--as", "user", + "--format", "pretty", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -580,7 +579,7 @@ func TestMeetingLeave_Execute_PrettyOutput(t *testing.T) { err := mountAndRun(t, VCMeetingLeave, []string{ "+meeting-leave", "--meeting-id", "69999999", - "--format", "pretty", "--as", "user", + "--format", "pretty", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -602,7 +601,7 @@ func TestMeetingJoin_Execute_APIError(t *testing.T) { err := mountAndRun(t, VCMeetingJoin, []string{ "+meeting-join", "--meeting-number", "123456789", - "--as", "user", + "--as", "bot", }, f, &bytes.Buffer{}) if err == nil { t.Fatal("expected error for API failure") @@ -618,7 +617,7 @@ func TestMeetingJoin_Execute_APIError(t *testing.T) { func TestMeetingLeave_Validate_MissingID(t *testing.T) { f, _, _, _ := cmdutil.TestFactory(t, defaultConfig()) - err := mountAndRun(t, VCMeetingLeave, []string{"+meeting-leave", "--as", "user"}, f, nil) + err := mountAndRun(t, VCMeetingLeave, []string{"+meeting-leave", "--as", "bot"}, f, nil) if err == nil { t.Fatal("expected error when --meeting-id is missing") } @@ -661,7 +660,7 @@ func TestMeetingLeave_DryRun(t *testing.T) { f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig()) err := mountAndRun(t, VCMeetingLeave, []string{ "+meeting-leave", "--meeting-id", "69999999", - "--dry-run", "--as", "user", + "--dry-run", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -694,7 +693,7 @@ func TestMeetingLeave_Execute_Success(t *testing.T) { err := mountAndRun(t, VCMeetingLeave, []string{ "+meeting-leave", "--meeting-id", "69999999", - "--format", "json", "--as", "user", + "--format", "json", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -725,7 +724,7 @@ func TestMeetingLeave_Execute_TrimsMeetingID(t *testing.T) { err := mountAndRun(t, VCMeetingLeave, []string{ "+meeting-leave", "--meeting-id", " 69999999 ", - "--format", "json", "--as", "user", + "--format", "json", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -750,7 +749,7 @@ func TestMeetingLeave_Execute_APIError(t *testing.T) { }) err := mountAndRun(t, VCMeetingLeave, []string{ - "+meeting-leave", "--meeting-id", "69999999", "--as", "user", + "+meeting-leave", "--meeting-id", "69999999", "--as", "bot", }, f, &bytes.Buffer{}) if err == nil { t.Fatal("expected error for API failure") diff --git a/skills/lark-meeting/SKILL.md b/skills/lark-meeting/SKILL.md index 55d7a4f199..caf124eb30 100644 --- a/skills/lark-meeting/SKILL.md +++ b/skills/lark-meeting/SKILL.md @@ -1,7 +1,7 @@ --- name: lark-meeting version: 1.0.0 -description: "飞书视频会议:查询会议记录与会议产物(纪要/逐字稿/妙记)、妙记搜索/上传/下载/编辑、机器人参与会议;查询进行中的会议、实时会议内容(发言/聊天/共享文档)问答(会上/会里)、发送会中聊天/表情;基于 meeting_id、meeting_no、event_id、note_id、minute_token、vc-node-id 或妙记 URL 查询相关信息。预约会议、忙闲和会议室管理走 lark-calendar。" +description: "飞书视频会议:查询会议记录与会议产物(纪要/逐字稿/妙记)、妙记搜索/上传/下载/编辑、机器人发起日程会议或参与会议;查询进行中的会议、实时会议内容(发言/聊天/共享文档)问答(会上/会里)、发送会中聊天/表情;基于 meeting_id、meeting_no、event_id、note_id、minute_token、vc-node-id 或妙记 URL 查询相关信息。预约会议、忙闲和会议室管理走 lark-calendar。" metadata: requires: bins: ["lark-cli"] @@ -105,7 +105,7 @@ lark-cli vc +meeting-events --as --meeting-id --p - [查询妙记及其产物](scenes/query-minutes-and-artifacts.md):已有妙记 URL / `minute_token`,或按标题、所有者、参与者搜索妙记;读取总结、待办、章节、关键词、逐字稿,下载原始音视频,或查询关联智能纪要。 - [生成和修改妙记、管理妙记权限](scenes/create-and-edit-minutes.md):将本地音视频生成妙记、逐字稿、总结、待办或章节;修改妙记标题、总结、待办、关键词或说话人;申请妙记权限,或查看、分配妙记协作者权限。 - [查询智能纪要及关联产物](scenes/query-note-and-artifacts.md):已有 `note_id`、智能纪要 Docx URL/token,或需要查询纪要正文、逐字稿、妙记和共享文档等关联产物。 -- [应用机器人参会与会中互动](scenes/live-meeting-attend.md):完整编排应用机器人的活跃会议发现、发起或加入、邀请、事件拉取、会议截图、文本/表情/倒计时互动、结束会议和明确授权后的离会。 +- [应用机器人发起会议、参会与会中互动](scenes/live-meeting-attend.md):完整编排应用机器人的活跃会议发现、发起或加入、邀请、事件拉取、会议截图、文本/表情/倒计时互动、结束会议和明确授权后的离会。 - [会中事件与会中互动](scenes/live-meeting-interact.md):在不触发新的入会/离会操作时,使用用户身份或已在会中的应用身份查询活跃会议、查看发言/聊天/共享内容、按需读取当前会议画面,或发送文本/表情、操作倒计时。 ## 命令参考 @@ -121,7 +121,7 @@ lark-cli vc +meeting-events --as --meeting-id --p | `vc +meeting-message-send` | 发送会中文本消息或表情 | [lark-vc-meeting-message-send](references/lark-vc-meeting-message-send.md) | | `vc +meeting-screenshot` | 获取视频会议截图 | [lark-vc-meeting-screenshot](references/lark-vc-meeting-screenshot.md) | | `vc +meeting-countdown` | 设置、延长、提前结束或关闭会中倒计时 | [lark-vc-meeting-countdown](references/lark-vc-meeting-countdown.md) | -| `vc +meeting-join` | 让应用机器人加入会议 | [lark-vc-agent-meeting-join](references/lark-vc-agent-meeting-join.md) | +| `vc +meeting-join` | 让应用机器人发起日程会议或加入会议 | [lark-vc-agent-meeting-join](references/lark-vc-agent-meeting-join.md) | | `vc +meeting-invite` | 以应用机器人邀请指定用户或全部合格日程参会人 | [lark-vc-agent-meeting-invite](references/lark-vc-agent-meeting-invite.md) | | `vc +meeting-end` | 让当前 Host 应用机器人结束会议 | [lark-vc-agent-meeting-end](references/lark-vc-agent-meeting-end.md) | | `vc +meeting-leave` | 让应用机器人离开会议 | [lark-vc-agent-meeting-leave](references/lark-vc-agent-meeting-leave.md) | diff --git a/skills/lark-meeting/references/lark-vc-agent-meeting-join.md b/skills/lark-meeting/references/lark-vc-agent-meeting-join.md index 6a644089a3..870125d02a 100644 --- a/skills/lark-meeting/references/lark-vc-agent-meeting-join.md +++ b/skills/lark-meeting/references/lark-vc-agent-meeting-join.md @@ -1,7 +1,7 @@ -# vc +meeting-join +# vc +meeting-join:发起日程会议或加入会议 -通过 9 位会议号让应用机器人加入一场正在进行的视频会议。这是一次**写操作**,会实际让应用机器人加入会议。 +通过 9 位会议号让应用机器人发起符合条件的日程会议,或加入一场正在进行的视频会议。这是一次**写操作**,会实际让应用机器人发起或加入会议。 本 skill 对应 shortcut:`lark-cli vc +meeting-join`(调用 `POST /open-apis/vc/v1/bots/join`)。 @@ -93,4 +93,4 @@ lark-cli vc +meeting-join --as bot --meeting-number 123456789 --action start - 执行成功后,立即记录返回的 `meeting.id`,用于后续 `+meeting-leave` / `+meeting-events`。 ## 相关场景 -- [应用机器人参会与会中互动](../scenes/live-meeting-attend.md) +- [应用机器人发起会议、参会与会中互动](../scenes/live-meeting-attend.md) diff --git a/skills/lark-meeting/references/lark-vc-agent-meeting-leave.md b/skills/lark-meeting/references/lark-vc-agent-meeting-leave.md index d0e5f889cf..5fec73a915 100644 --- a/skills/lark-meeting/references/lark-vc-agent-meeting-leave.md +++ b/skills/lark-meeting/references/lark-vc-agent-meeting-leave.md @@ -25,9 +25,9 @@ lark-cli vc +meeting-leave --as bot --meeting-id 69xxxxxxxxxxxxx28 `--meeting-id` 必须是会议的长数字 ID,通常由 `+meeting-join --as bot` 返回体中的 `meeting.id` 提供,也可从应用身份 `+meeting-list-active --as bot --user-id ` 返回体中的 `meeting_id` 获取。**传 9 位会议号会失败**。 -### 2. 优先使用 bot 身份 +### 2. 仅支持 bot 身份 -这是应用机器人离会能力,使用与入会或 active meeting 发现相同的 `--as bot`。只能让当前身份自己离会,无法强制移出其他参会人。 +这是应用机器人离会能力,该 shortcut 仅支持 `bot` 身份,必须使用 `--as bot`。只能让当前身份自己离会,无法强制移出其他参会人。 ### 3. 当前身份必须在会议中 diff --git a/skills/lark-meeting/scenes/live-meeting-attend.md b/skills/lark-meeting/scenes/live-meeting-attend.md index e7d615e1a0..ba966e865f 100644 --- a/skills/lark-meeting/scenes/live-meeting-attend.md +++ b/skills/lark-meeting/scenes/live-meeting-attend.md @@ -1,4 +1,4 @@ -# 应用机器人参会与会中互动 +# 应用机器人发起会议、参会与会中互动 编排应用机器人的完整会中流程:发现已在参加的会议,或在用户明确授权后发起或加入会议;随后拉取会中事件、发送文本或会中表情、操作倒计时,并仅在用户明确要求时结束会议或离会。