feat(vc-agent): 入会自动 provision lark-cli profile + 失败通知 owner + 沙盒改用户 opt-in - #782
Conversation
…opt-in 修 3 个问题 + 后续 live 测试暴露的 2 个: 1. 拉不进会(根因): 入会走 lark-cli --as bot --profile <larkCliProfile>,但 botmux 从不创建该 profile→缺失时静默失败卡呼叫中。daemon 入会前用已存 larkAppSecret 幂等 补建(--app-secret-stdin,不落 argv/worker/沙盒);profile list 不认 --format json (默认即 JSON);add 报 already exists 兜底当成功。 2. 入会失败 DM 会议 owner 附具体原因,不再只吞一行 WARN。 3. 沙盒(方案 B): VC consumer 默认不再强制沙盒,不开也能入会;仅当显式勾了沙盒但平台/ 后端给不了才 fail-closed(不静默降级)。evaluateVcMeetingConsumerIsolation 拆 eligible/isolated 两概念,全链路改名 managedSideEffectIsolation→ managedSideEffectEligible+sandboxIsolated;dashboard 对未开沙盒 bot 标凭证暴露风险。 4. receiver 会话 spawn 写死 sandbox=true → 方案 B 下未开沙盒的 bot 仍被强制塞沙盒启动, 环境没配 bwrap→CLI 起不来→进会但不回复。改为跟随 isolation.decision.isolated, sandbox 路径字段一并跟随。回归测试断言未开沙盒 bot 的 receiver sandbox===false。 5. dashboard 下拉直接禁选不可用的 bot(无工作目录/不支持可靠回执/显式沙盒但平台给不了), 不再等开会才报;离线/未开沙盒不禁(前者暂时、后者知情选择)。 测试: 新增 test/vc-meeting-join-profile.test.ts(11);重写 consumer-isolation 测试 (plan B 语义);flip daemon-session「unsandboxed 被拒」→「被接受+receiver sandbox===false」; 改 bootstrap/api/ui 字段名。VC 全套绿。 影响面: 仅 VC 会议 consumer 路径;evaluateVcMeetingConsumerIsolation 是 gate/filter/UI 显示,实际沙盒由 spawn 时 session.sandbox 驱动。跨平台: macOS/riff/herdr/zellij 显式 开沙盒仍 fail-closed。
|
复审结论:暂不建议合并。发现 1 个核心遗漏和 1 个 dashboard 绕过。
实际验证:
当前 worktree 保持干净,未修改 PR 代码,也未重启 live daemon。 |
复审发现两处遗留问题,一并修复: 1. eager-join 静默失败:只带 meeting_no(无 meeting.id)的邀请会先 eager join 以解析 meeting.id。此前该分支 provision/join 失败只写 一行 WARN,随后落到「no meeting id yet」静默 return——正是最初 「profile not found → bot 一直呼叫」bug 的发生地,全程无用户可见 信号(此时还没有 session,走不到 manual-invite 的 owner DM)。且 旧 `&& cfg.larkCliProfile` 守卫让「缺 profile」直接跳过整块也静默。 现改为:只要有 meeting_no 就进入该块,任何失败都 DM 会议 owner, 按 meeting_no 去重(30min TTL,成功入会即清)避免重投邀请刷屏。 2. dashboard 下拉禁选被默认值绕过:新建预设 / 套用模板都固定选 agentOptions[0](按 appId 排序首项,可能正是被 disabled 的不可用 bot),而服务端 PUT 仅校验 agentAppId 为非空字符串、无 eligibility 校验 → 不可用 bot 可被存成 consumer,进会静默不回复。现抽出单一 谓词 isAgentSelectable(下拉 disabled 与默认播种共用,杜绝漂移), 默认改选第一个 structurally-eligible bot;无 eligible 时禁用「新增 预设」与「套用模板」,不再让用户建出永不回复的预设。 测试: - 新增 3 个 daemon-session 用例(eager 无 id provision 失败 → DM owner、 重投邀请 DM 去重)+ 2 个 dashboard UI 用例(新建预设播种可用 agent 而非禁用的 [0]、无 eligible 时 Add 禁用);变异验证均 red-on-revert。 - pnpm build 通过;6 个 VC 套件 233/233 通过。 影响面:仅 VC 会议 consumer 路径。daemon 侧改动在 handleVcMeetingPush 的 meeting_invited eager-join 分支;dashboard 侧为纯前端预设编辑面。
|
复验
独立验证:
两个非阻塞测试卫生建议: |
复审两个非阻塞建议,收口测试隔离性与覆盖: - __vcMeetingAgentTest.reset() 一并 clear 新增的 vcMeetingEagerJoinFailureNotified 去重表,避免跨用例的 meeting_no 去重状态泄漏成潜在 flake(当前两个 eager 用例用不同 meeting_no 暂不撞,但补齐防回归)。 - 「无 eligible agent」用例补断言:打开模板详情后其「套用」按钮也 必须 disabled——套用模板同样会播种 agent,不能成为绕过禁选、 建出永不回复预设的第二条路径。变异验证:把该 gate 还原成旧的 length===0 形态即 red(全 disabled 列表 length=1 会误放行)。 纯测试 + 一行 reset,无生产行为变化。build ✅; vc-meeting-daemon-session + dashboard-vc-consumer-profiles-ui 143/143。
|
补充验证最新 head
未发现新增阻塞项。 |
rebase onto 最新 master(f11c0c90,含 #782 vc-agent 沙盒改用户 opt-in) 后,ensureVcMeetingReceiverSession 内 4 个 Session.sandbox* 写点的赋值 右值由硬编码 true 改为跟随 bot 配置 opt-in,归一化 AST 指纹随之漂移。 写点数量、位置与分类(session_owned_persisted / current-session-row) 均未变,本提交仅重新入册指纹并重钉 coverage 台账摘要。 Claude-Session: https://claude.ai/code/session_01AjYRKcQeHGHFxWjKjRJsP6
背景
会议智能体(VC consumer)在实际使用中发现三类问题,live 测试又暴露两处后续问题,本 PR 一并修复。核心诉求(产品决策):把是否开沙盒的选择权交还给用户——默认不强制沙盒,用户知情后可自行开启隔离。
改了什么 & 为什么
1. 拉不进会(根因,静默失败)
入会走
lark-cli vc +meeting-join --as bot --profile <larkCliProfile>,但 botmux 从不创建这个 profile——默认larkCliProfile === bot 的 appId(dashboard 自动填),却没有任何地方把它注册进 lark-cli。首次真实邀请时profile not found,bot 卡在「呼叫中」永远进不去,且失败只写一行日志。larkAppSecret幂等补建 profile(lark-cli profile add --app-secret-stdin,secret 走 stdin 不落 argv、只进 lark-cli 加密 store、在可信 daemon 进程做,不碰 worker/沙盒)。lark-cli profile list不认--format json(默认即 JSON);profile add报already exists兜底当成功。2. 入会失败可见化
入会失败不再只吞一行 WARN,而是 DM 会议 owner 附具体原因(缺 secret / profile 建失败 / 无 meeting_no 等),按会话去重不刷屏。
3. 沙盒改用户 opt-in(产品决策)
evaluateVcMeetingConsumerIsolation从「硬 require 沙盒」改为拆分两个概念:全链路改名
managedSideEffectIsolation→managedSideEffectEligible+sandboxIsolated(daemon gate / bootstrap 选择器 / dashboard API DTO / UI / i18n)。dashboard 对未开沙盒的 bot 显示凭证暴露风险提示(会议是不可信多方输入 + bot 凭证在进程 env,注入可外泄)。4. receiver 会话 spawn 写死 sandbox=true(live 暴露)
创建会议 receiver 会话时有一行写死
session.sandbox = true。上面第 3 点只改了准入门,漏了这行——未开沙盒的 bot 的 receiver 仍被强制塞沙盒启动,而其环境没为 bwrap 配好(工作目录不在白名单等)→ CLI 起不来/跑不动 → 进了会但永远不回复。改为跟随isolation.decision.isolated,sandbox 相关路径字段一并跟随。5. dashboard 下拉直接禁选不可用的 bot(live 反馈)
不能用的执行 Bot(无工作目录 / 不支持可靠回执 / 显式开了沙盒但平台给不了)直接在下拉里禁选(灰掉),而不是等开会才报错。离线是暂时的(不禁)、未开沙盒是知情选择(不禁,只标风险)。
影响面
evaluateVcMeetingConsumerIsolation是 gate / filter / UI 显示,实际沙盒由 spawn 时session.sandbox驱动。测试验证
pnpm build✅test/vc-meeting-join-profile.test.ts(11 用例,注入式覆盖 no-op / provision / missing_secret / add_failed / already-exists 兜底 / list 不可用);test/vc-meeting-consumer-isolation.test.ts(plan B 语义:未开沙盒 eligible+not isolated、显式开但不可交付 fail-closed);test/vc-meeting-daemon-session.test.ts:未开沙盒的 consumer 从「被拒」改为「被接受」,并新增断言 receiver 会话sandbox === false(拿老的写死 true 一测即红)+ 「provision 失败→跳过 join+DM owner」;vc-meeting-join-profile / vc-meeting-daemon-session / vc-meeting-consumer-isolation / vc-meeting-consumer-profile-bootstrap / dashboard-vc-consumer-profiles-api / dashboard-vc-consumer-profiles-ui→ 229 passed。switch:here + daemon:restart在飞书实测——入会正常;沙盒修复后 receiver worker 成功 spawn 并投递首轮 prompt(日志验证)。默认minutesprofile 为沉默型(仅在关键决策/待办/风险或被点名时输出),闲聊不回属设计预期。待补(follow-up,不阻塞本 PR)