src/vc-agent/(18 文件)是飞书视频会议智能体的核心库:让 bot 真实入会、消费会中事件(转写/聊天/参会人/共享)、并通过文本或实时语音在会中输出。它是 CLI(botmux vc-agent,见 cli)与 daemon 共用的底层;daemon 侧还有约 24 个 src/services/vc-meeting-*.ts 存储/协议模块(runtime-store、consumer-profile、delivery-hub、send-policy、action-gate 等)承载长期状态,不在本目录内。

归一化事件模型(types.ts)

所有来源的会中事件统一为 NormalizedVcMeetingItem 联合类型,6 种活动:participant_joined / participant_left / chat_received / transcript_received / magic_share_started / magic_share_ended。每条带 source(polling|push)、meetingId、itemKey(去重键)、occurredAtMs。输出封装为 VcMeetingStatePayload(format: 'botmux.vc-meeting.v1'),转写项含 sentenceId / revision / isFinal 支持增量修订。

两种事件来源

polling(拉) — polling-source.ts:shell 调用 lark-cli vc ... --as bot(要求 lark-cli ≥ 1.0.66,checkLarkCliVersion 做 semver 门):

  • joinMeetingAsBot:vc +meeting-join --meeting-number [--password] 真实入会,返回 meetingId;
  • fetchMeetingEventsAsBot:vc +meeting-events --meeting-id [--page-token|--start/--end] --page-all,同步子进程带超时上限防挂死;
  • sendMeetingTextMessageAsBot:会中文本消息。 结果经 normalizer.ts normalizeVcMeetingEvents 归一化(容忍多种字段路径与时间格式)。

push(推) — push-source.ts:解析 daemon 长连接收到的 4 个飞书事件:vc.bot.meeting_invited_v1(被邀请入会)、vc.bot.meeting_activity_v1(会中活动)、vc.bot.meeting_ended_v1(会议结束)、vc.meeting.participant_meeting_joined_v1(user 身份事件),产出 VcMeetingPushContext。这些事件的订阅在 setup 开放平台自动化阶段完成(VC_MEETING_APP_EVENTS/USER_EVENTS,见 setup-flow),缺失时 vcListenerEventGateError 保存门防止 listener 带病运行。所需 scope 在 verify-permissions.ts 的 VC_MEETING_FEATURE_SCOPES / VC_MEETING_REALTIME_VOICE_SCOPES。

会中状态机(meeting-state.ts)

VcMeetingSessionState = ingestion(pageToken / lastSeenEventTime / 空轮计数 / 轮序号)+ dedup(recentEventIds、seenItemIds、transcriptBySentenceId,均 50k 上限 LRU)。管线:beginVcPollingPass → ingestNormalizedVcMeetingItems(itemKey 去重丢重)→ 转写走 upsertTranscriptMeetingItem(按 sentenceId + revision 更新)→ collectStableTranscriptItems:稳定窗口机制,转写在连续 N 轮 poll 无变化才下发,避免把实时修订中的半句发给下游。--page-token-mode 支持 time-window(按时间回溯 lookback)与 incremental(纯 pageToken)两种翻页策略。

交互卡片(cards.ts)

会中人在飞书侧的决策界面,全部为 interactive 卡片 JSON:入会确认卡(confirm:pending/started/declined/expired)、消费者卡(consumer:listenOnly 旁听 vs agent 接管二选一)、崩溃恢复卡、listener 重新入会卡、输出审核卡(output review:文本/语音发出前人工批准,sentText/sentVoice/rejected/superseded)。卡片回调由 daemon 的 card.action.trigger 通道消费(见 cards)。

实时语音(realtime/,12 文件)

bot 在会中「说话」的通道:endpoint.ts 获取 websocket 端点 → transport.ts WebSocket 二进制传输(10MB payload 上限)→ protocol.ts + protobuf.ts(手写 protobuf wire 编解码,不依赖 protobufjs)→ session.ts RealtimeVoiceSession(start 握手 / speak(text) 服务端 TTS / stop)。音频参数(types.ts):24kHz 单声道 pcm_s16le、默认 100ms 帧;pacer.ts 按帧节拍发送,audio-source.ts PCM→帧批转换。所需权限即上文 realtime voice scopes。

CLI 子命令(src/cli/vc-agent.ts)

子命令作用
tat-gateTAT 读门验证:bot 身份能否真实读到 transcript/chat 条目(门期间需有人说话/发聊天),fail 时打印 lark-cli 原始错误摘要
pollP0 轮询桥:周期拉事件 → 状态机 → NDJSON 输出稳定 meeting-state;支持 --once、空轮软关闭、退避重试
request-output会议接收者会话请求文本/语音输出:先提交自己的 daemon(/api/vc-meetings/action-request,绑定轮换 worker 能力与 live marker),非接收者会话回退旧 /api/vc-meetings/output-request
speak直接 TTS 入会:仅当会议 voiceOutputPolicy=allow,否则需 --operator-override(人工 dogfood);文本 ≤200 字

设计上把「直接发声」锁死在 operator 场景,模型驱动的输出必须走 request-output 的审核/策略链(daemon 侧 send-policy / action-gate)。

相关:cli、cards、voice、setup-flow