Anarlog 的 Vexa v0.12.18 Google Meet 行为矩阵:准入/运行时分类器与生命周期终止原因全解
2026/9/16 16:25:24 网站建设 项目流程

Anarlog 的 Vexa v0.12.18 Google Meet 行为矩阵:准入/运行时分类器与生命周期终止原因全解

【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog

导读

本文以 Anarlog 企业版 Google Meet Worker(enterprise/google-meet-worker)仓库中固定版本 Vexav0.12.18的行为矩阵(behavior matrix)文档为核心,系统拆解其准入(admission)与运行时(runtime)状态分类器、14 个生命周期(lifecycle)场景及其供应商无关的终止原因(terminal reason)语义。读完本文,你将掌握 Google Meet 会议机器人在"加入→被拒→等待→入会→被移除→断网→会议结束"等全链路状态下的判定逻辑、宽限期(grace)参数、可重试语义,以及这些行为是如何通过可回放(replayable)的 JSON 快照进行自动化验证的。

一、矩阵文档的定位与设计初衷

1.1 Pinned Reference:固定版本的"行为契约"

MATRIX.md 开头明确了这份矩阵的基准:

Pinned reference: Vexav0.12.18, commit1b62993e7e97c6ee04a5dcb116f7749ec74169df.

Vexa 是一个开源的会议录制/转录机器人项目,Anarlog 在 enterprise Google Meet Worker 中采纳了其准入与运行时分类器的行为,并将其以归一化(normalized)快照的形式固化为可回放的 fixture。关键点在于文档自己强调的:

These fixtures are replayable snapshots of normalized Anarlog admission/runtime classifiers. They do not require Vexa internals at test time.

也就是说,测试时完全不依赖 Vexa 的内部实现,只需要这些归一化后的快照数据即可驱动 Anarlog 自己的分类器。这种做法把"第三方行为"沉淀为"可复现的测试资产",避免测试因上游变化而漂移。

从实现上看,分类器代码在文件头也明确标注了来源(见 admission.rs 与 runtime.rs 首行注释:Adapted for Anarlog from Vexa v0.12.18),并在 THIRD_PARTY_NOTICES.md 与 VEXA-LICENSE 中保留了完整的第三方版权与许可说明。

1.2 fixture 的目录结构

fixture 快照被组织在 fixtures/vexa-v0.12.18 下:

  • admission/:准入阶段的页面状态快照(6 个);
  • runtime/:入会并开始捕获后的页面状态快照(10 个);
  • scenarios.json:跨越多个快照、按时间轴回放的生命周期场景(14 个);
  • MATRIX.md:行为矩阵文档本身。

每个快照 JSON 都是一个"页面探针(probe)"输出的归一化结果,例如 admission/host-denied.json:

{ "id": "host-denied", "kind": "admission", "elapsed_ms": 0, "snapshot": { "waiting_room_visible": true, "consent_prompt_visible": false, "explicit_denial_indicator": "denied your request", "ambiguous_error_indicator": null, "visible_recaptcha_challenge": false, "participant_tile_labels": [], "self_name_nodes": 0, "visible_admission_controls": 0 }, "expected": { "outcome": "rejected", "reason": "host_denied", "indicator": "denied your request" } }

顶层expected字段声明了该快照应当被分类出的结果,测试直接拿它做断言基准。

二、行为矩阵总览:16 个 fixture 快照

2.1 准入阶段(admission,6 个)

FixtureVexa 模块Anarlog outcomeTerminal reasonRetryable
admission/host-denied.jsonjoin/src/googlemeet/admission.tsRejected HostDeniedadmission_deniedno
admission/waiting-room.jsonjoin/src/googlemeet/admission.tsWaitingForAdmission
admission/admitted.jsonjoin/src/googlemeet/join.tsAdmitted
admission/captcha-unsolved.jsonjoin/src/googlemeet/admission.tsRejected CaptchaUnsolvedauthentication_failedno
admission/error-page.jsonjoin/src/googlemeet/admission.tsRejected ErrorPageprovider_errorno
admission/consent.jsonjoin/src/googlemeet/admission.tsConsentRequired

准入阶段覆盖了三种"终态拒绝"(HostDenied / CaptchaUnsolved / ErrorPage)与三种"非终态等待"(WaitingForAdmission / Admitted / ConsentRequired)。其中终态拒绝分别映射为admission_deniedauthentication_failedprovider_error,且全部不可重试——它们代表 Google 侧的确定性拒绝,重试没有意义。

2.2 运行时阶段(runtime,10 个)

FixtureVexa 模块Anarlog outcomeTerminal reasonRetryable
runtime/removed.jsonjoin/src/googlemeet/removal.tsRemovedremoved_from_meetingno
runtime/meeting-ended.jsonjoin/src/googlemeet/removal.tsMeetingEndedmeeting_endedno
runtime/network-lost.jsonjoin/src/googlemeet/removal.tsNetworkLost after gracenetwork_lostyes
runtime/active.jsongmeet-capture/src/gmeet-capture.tsActive
runtime/silence.jsongmeet-capture/src/pcm-capture.tsActive (no tiles besides bot)no_one_joinedafter graceyes
runtime/nobody-joined.jsongmeet-capture/src/pcm-capture.tsActive until empty-room graceno_one_joinedyes
runtime/overlapping-speakers.jsongmeet-capture/src/gmeet-speakers.tsActive with two named tiles
runtime/speaker-renamed.jsongmeet-capture/src/gmeet-speakers.tsActive with renamed tile
runtime/unresolved-speaker.jsongmeet-capture/src/gmeet-speakers.tsActive with chrome chrome-ui tile
runtime/long-duration.jsongmeet-capture/src/gmeet-capture.tsActive after two hours

注意runtime/silence.json的含义:机器人已入会(有 self tile)但没有任何其他参会者画面,此时短时仍判Active,只有当空场宽限期(empty-room grace)耗尽后才转换为no_one_joined,且可重试——网络瞬断或无人入会属于环境问题,重新调度会话是合理的。

三、准入分类器(AdmissionClassifier)源码级解析

行为矩阵中的 6 个 admission fixture 正是 admission.rs 中AdmissionClassifier::classify的输入输出契约。

3.1 快照结构AdmissionSnapshot

AdmissionSnapshot(admission.rs)包含 8 个字段,全部来自浏览器页面探针:

字段类型含义
waiting_room_visiblebool等待室(waiting room)是否可见
consent_prompt_visiblebool同意/授权弹窗是否可见
explicit_denial_indicatorOption<String>明确的拒绝文案(如 "denied your request")
ambiguous_error_indicatorOption<String>模糊错误文案(如 "Try again")
visible_recaptcha_challengebool是否出现 reCAPTCHA 人机验证
participant_tile_labelsVec<String>参与者画面上的名字标签列表
self_name_nodesusize页面上自身名字节点数量
visible_admission_controlsusize可见的准入控制组件数量

其中real_participant_tiles()会过滤掉 Google Meet 的 "visual_effects" / "Backgrounds and effects" 这类装饰性标签,避免把特效预览误判为真实参会者——这一点有专门单测effects_preview_is_not_a_real_participant验证(admission.rs 测试模块)。

3.2 分类决策顺序

classify(admission.rs)按确定性优先级依次判定,一旦命中即返回:

  1. 显式拒绝 > 一切:只要explicit_denial_indicator存在,直接Rejected(HostDenied)。即使同时出现 reCAPTCHA 与等待室副本也照样以拒绝为准(单测explicit_host_denial_wins_over_captcha_and_stale_waiting_copy覆盖)。
  2. 模糊错误 + reCAPTCHA:进入"验证码宽限期"(DEFAULT_CAPTCHA_GRACE = 120s)。宽限期内返回CaptchaChallenge { remaining }持续等待,超过 120 秒仍未解决则Rejected(CaptchaUnsolved)。单测captcha_suppression_expires_instead_of_polling_forever验证了"宽限到期前 1 秒仍在等待、到期即拒绝"的边界。
  3. 模糊错误(无验证码)Rejected(ErrorPage),对应页面渲染出错。
  4. 等待室可见WaitingForAdmission,机器人滞留等待室。
  5. 同意弹窗可见ConsentRequired,需要用户/管理员授权。
  6. 存在准入信号(真实参与者画面、自身名字节点、准入控件任一非零):Admitted
  7. 以上皆非Unknown,等待下一次探针。

单测waiting_and_consent_guards_suppress_lobby_false_positives还保证了:即使等待室/同意弹窗场景下出现了准入控件或参与者标签,也不会被误判为Admitted

3.3 fixture 与宽限期的对应

admission/captcha-unsolved.jsonelapsed_ms120000(即 120 秒),正好等于DEFAULT_CAPTCHA_GRACE——fixture 通过elapsed_ms字段模拟"验证码已超时",从而让测试在不真实等待 2 分钟的情况下验证 CaptchaUnsolved 的终态。

四、运行时分类器(RuntimeClassifier)源码级解析

运行时阶段的 10 个 fixture 对应 runtime.rs 中的RuntimeClassifier::classify(runtime.rs)。

4.1 快照结构与判定优先级

RuntimeSnapshot结构更简单:removal_indicatormeeting_ended_indicatorconnection_problem_indicator三个 Option 文案 +participant_tile_labels/self_name_nodes/visible_meeting_controls三个活跃信号。判定顺序为:

  1. 被移除 > 会议结束 > 断网removal_indicator(如 "you were removed")优先于meeting_ended_indicator("meeting ended"),再优先于connection_problem_indicator("reconnecting")。单测explicit_removal_wins_over_ended_and_connection_copy验证了这一优先级。
  2. 断网宽限期DEFAULT_CONNECTION_GRACE = 30s。出现connection_problem_indicator后 30 秒内返回ConnectionInterrupted { remaining }(网络恢复则回到 Active),超过 30 秒才升级为NetworkLost。单测transient_reconnection_has_a_bounded_grace_period精确验证了"30 秒整时切换为 NetworkLost"。
  3. 活跃信号判定:与准入类似,self_name_nodes/visible_meeting_controls/ 非装饰性参与者标签任一存在即Active
  4. 未知状态不能无限轮询DEFAULT_RUNTIME_UNKNOWN_GRACE = 30s,连续 30 秒没有任何信号则进入StateLost(单测unknown_runtime_state_cannot_poll_forever覆盖)。

4.2 两个关键 fixture 的对照

  • runtime/network-lost.jsonelapsed_ms = 30000(30 秒),connection_problem_indicator = "reconnecting",正好越过连接宽限期,得出NetworkLost。这与DEFAULT_CONNECTION_GRACE一一对应。
  • runtime/silence.jsonelapsed_ms = 0,只有自身 tile(self_name_nodes: 1),没有其他人——立即判Active,空场宽限期在生命周期层处理(见下一节)。

五、生命周期场景:scenarios.json 与 14 个终止原因

scenarios.json 把上述快照按时间轴串成多步场景,通过 lifecycle.rs 的WorkerLifecycle状态机回放,最终断言供应商无关的终止原因。矩阵文档给出的额外场景表:

ScenarioTerminal reasonRetryable
everyone-left-after-participantseveryone_leftno
silence-then-nobody-joinedno_one_joinedyes
host-ended-after-long-capturemeeting_endedno
captcha-unsolvedauthentication_failedno
error-page-before-joinprovider_errorno
stopped-by-requeststopped_by_requestno

这 6 个场景是"组合行为"而非新的分类器快照:它们复用 admission/runtime fixture,通过WorkerLifecycle的组合逻辑产生更复杂的结果。例如everyone-left-after-participants的步骤序列是:launch → admitted → capture_started → overlapping-speakers(两人在线)→ nobody-joined(全员离开),最终判定everyone_left且不可重试。

5.1 生命周期中的空场宽限期

WorkerLifecycle定义了两个与"空会议室"相关的默认宽限期(lifecycle.rs):

  • DEFAULT_NOBODY_JOINED_GRACE = 10 * 60(10 分钟):入会后一直没有人加入 →no_one_joined可重试
  • DEFAULT_EVERYONE_LEFT_GRACE = 2 * 60(2 分钟):曾有过其他参与者、随后全部离开 →everyone_left不可重试

WorkerLifecycle内部用saw_other_participantsempty_since跟踪"是否见过其他人"与"空场起始时间",从而区分no_one_joined(从未有人来)与everyone_left(有人来过又走光)两种语义完全不同的结局。

5.2 供应商无关的终止原因枚举

生命周期层把所有可能的结局归一化为统一的TerminalReasonKind(crates/meeting-capture/src/lifecycle.rs),它通过#[serde(rename_all = "snake_case")]序列化为矩阵中列出的字符串,是整个 worker 与上层控制面通信的"公共语言":

枚举变体序列化值语义
MeetingEndedmeeting_ended会议结束
StoppedByRequeststopped_by_request上层主动停止
AdmissionDeniedadmission_denied主持人拒绝入会
AdmissionTimeoutadmission_timeout等待室超时
NoOneJoinedno_one_joined一直无人入会
EveryoneLefteveryone_left全员离开
RemovedFromMeetingremoved_from_meeting被移出会议
RecordingPermissionDeniedrecording_permission_denied录制权限被拒
InvalidMeetinginvalid_meeting会议无效
AuthenticationFailedauthentication_failed认证失败(含验证码未过)
CapacityExceededcapacity_exceeded容量超限
NetworkLostnetwork_lost网络中断
ProviderErrorprovider_error提供商错误(含错误页)
WorkerExitedworker_exited工作进程崩溃退出
Unknownunknown未知原因

每个TerminalReason携带kind、可选messageretryable标志(crates/meeting-capture/src/lifecycle.rs)。BotState状态机则约束了合法的状态迁移(Queued → Launching → WaitingForAdmission/Joined → Capturing → Completed/Failed),并强制"终态必须携带终止原因、非终态不得携带",任何非法迁移都会返回TransitionError

六、测试验证:fixture_replay.rs 如何回放矩阵

fixture_replay.rs 是矩阵文档的"可执行版本",包含两个核心测试:

6.1 回放全部分类器快照

replays_every_vexa_classifier_fixture遍历fixtures/vexa-v0.12.18/admissionruntime目录下的全部 JSON,先断言数量不少于 12 个(保证矩阵 fixture 被完整提交),然后对每个快照:

  1. elapsed_ms = 0classify一次(模拟初始观察);
  2. 再以started + elapsed_ms的时间点classify第二次(模拟宽限期后的观察);
  3. 将两次结果与expected字段断言:admittedAdmissionOutcome::AdmittedwaitingWaitingForAdmissionconsentConsentRequiredrejected→ 检查reasonindicator;runtime 侧则断言active/removed/meeting_ended/network_lost及对应的 indicator 文案。

6.2 回放生命周期场景

replays_lifecycle_scenarios_to_provider_neutral_terminal_reasons逐个执行scenarios.json中的场景,把steps翻译为WorkerLifecycle的方法调用:

  • launchlaunch_started()
  • admission/runtime→ 加载对应 fixture 并调用observe_admission()/observe_runtime()elapsed_ms > 0时先做一次初始观察)
  • admission_timeoutadmission_timed_out()
  • worker_exitedworker_exited(message)
  • stt_unavailablestt_unavailable(message)
  • stopped_by_requeststopped_by_request()

每一步产生的CaptureEventPayload::Lifecycle(transition)会记录终止原因,场景结束时断言lifecycle.state()failed/completed/canceled中的期望值,且最后一条reason.kindretryable与场景声明完全一致。

scenarios.json中每个场景都定义了expected_stateexpected_terminalretryable三个断言目标,例如:

{ "id": "network-lost-after-grace", "expected_state": "failed", "expected_terminal": "network_lost", "retryable": true, "steps": [ { "action": "launch" }, { "action": "admission", "fixture": "admission/admitted.json" }, { "action": "capture_started" }, { "action": "runtime", "fixture": "runtime/network-lost.json" } ] }

14 个场景覆盖了 happy path(host-ended-meetingmeeting_ended/completed)、各类失败(host-denied-before-joinadmission_denied/failed)、可重试故障(worker-crash-after-joinworker_exited/retryable、stt-outageprovider_error/retryable)以及主动停止(stopped-by-requeststopped_by_request/completed)等边界情况。

七、如何扩展矩阵与复现验证

如果你需要新增一个 Google Meet 行为场景,可以遵循仓库内既有的三步流程:

  1. 采集快照:在 admission_probe.js、runtime_probe.js(被ADMISSION_PROBE_EXPRESSION/RUNTIME_PROBE_EXPRESSION编译期内嵌)的基础上,通过 CDP 页面求值得到归一化snapshot
  2. 登记矩阵:在fixtures/vexa-v0.12.18/admissionruntime下新增 JSON(含id/kind/elapsed_ms/snapshot/expected),需要多步组合时在scenarios.json增加场景条目;
  3. 跑测试验证:执行cargo test(测试入口为 fixture_replay.rs),回放测试会自动拾取新 fixture 并断言分类结果;完整的在线验证路径可参考 live_google_meet.rs 与 reliability_gate.rs。

整体而言,这份行为矩阵的设计精髓在于:把外部第三方(Vexa)的复杂页面行为,转化为不依赖其内部实现的归一化快照 + 确定性分类器 + 状态机场景回放,让 Anarlog 的 Google Meet Worker 在离线、可复现、可审计的前提下,稳定地把"页面上发生了什么"翻译成上层控制面能够理解的、供应商无关的终止原因与重试策略。

【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询