Skip to content

feat(lark): 支持最终回复私密审核发布 - #1416

Open
xgp01 wants to merge 1 commit into
deepcoldy:masterfrom
xgp01:feat-private-reply-review
Open

xgp01 wants to merge 1 commit into
deepcoldy:masterfrom
xgp01:feat-private-reply-review

Conversation

@xgp01

@xgp01 xgp01 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

变更

  • 新增 per-bot privateReplyReview 配置,默认关闭。
  • 开启后,群聊/话题群的普通最终回复先发送给审核人,审核人点击卡片按钮后再公开到原群或原话题。
  • 覆盖 botmux send --response-kind final 和 daemon final_output 兜底两个公共出口。
  • 增加持久化 publication record、nonce 校验、稳定公开 UUID、重复点击幂等、过期/丢弃处理。
  • Dashboard Bot Defaults 增加配置入口,并补充 bots-json 中英文文档。
  • 单聊保持现有直接回复行为;实时状态卡、CoT、手动 /card 不受该开关影响。

影响面

  • 影响 Lark 最终回复发送路径:botmux send 和 daemon fallback。
  • 对所有 CLI 生效,因为逻辑挂在公共发送出口。
  • 默认关闭,存量 bot 行为不变。
  • 群聊/话题群支持审核;p2p、VC managed delivery、doc comment、HTTP sink 等非普通最终回复路径不接入。
  • private review 与 final feedback 互斥,仅在本次回复确实进入审核时禁用 feedback。

验证

  • NPM_CONFIG_CACHE=/private/tmp/botmux-npm-cache npx --yes bun@1.4.2 run test -- test/private-reply-review.test.ts test/private-reply-review-config.test.ts test/cli-send-hook-context.test.ts test/bridge-final-output-retry.test.ts test/dashboard-bot-payload.test.ts test/card-prefs-auto-start.test.ts
    • 6 files, 176 tests passed
  • NPM_CONFIG_CACHE=/private/tmp/botmux-npm-cache npx --yes bun@1.4.2 run build
    • passed
  • git diff --check
    • passed

Closes #1360

@xgp01
xgp01 requested a review from deepcoldy as a code owner September 16, 2026 03:22
@deepcoldy

Copy link
Copy Markdown
Owner

你好 @xgp01 ,这个 PR 的自动评审群已建好:飞书评审群,首次 review 与复审正在群内进行。不过你目前不在我们的自动拉群名单里,暂时没有把你拉进群。如果希望直接在群里参与讨论,麻烦把你的 GitHub 账号和飞书信息补录到这份名单文档:https://bytedance.larkoffice.com/wiki/WJ1nwWbtxi89erkNGNbcgkt9nUe ,补好后后续复审我们会自动把你拉进群。本条为自动流程消息,感谢贡献!

Co-authored-by: TRAE CLI <traecli@bytedance.com>
@xgp01
xgp01 force-pushed the feat-private-reply-review branch from b962516 to 4526776 Compare September 16, 2026 08:53
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,设计文档写得很清楚,实现整体也很完整(nonce 落盘前哈希、幂等发布 UUID、audience/admin 双重校验都考虑到了)。下面是自动评审的初步意见,按建议处理顺序列出,最终以维护者审阅为准

评审基线:已在本地 rebase 到最新 origin/masterdc7b4e636),10 个文件冲突已解,bun run build 通过,本 PR 自带的 7 个测试文件 376/376 全绿。


F1(设计级,建议先决策):与主干「统一回复卡」的投递模型冲突

src/cli.ts 中最终回复的发送分成两条路:

const replyRecord = canUseReplyCard ? replyStore.read(replyKey) : undefined;
if (replyRecord && replyKey) {
  // 统一回复卡分支:直接 replyStore.update(...) 公开投递
} else {
  const stagedMessageId = await maybeStageFinalReview(cardJson);  // 私密审核只在这里
  ...
}

私密审核的 staging 只存在于 else 分支。当 bot 配了 replyCardMode: unified(或 final-only)时,走上面那条分支的最终回复会直接公开发出,不经过审核,也没有任何告警

需要说明的是,这个绕过是有条件的,不是全量失效:命中上面那条分支的门是 canUseReplyCard(cli.ts:10643),它是 12 个合取条件,其中任意一条为假(正文含 <at …> 标签、--mention 了非提问者、--quote / --no-quotesend --into 等)都会让 replyRecord 为 undefined,从而回到 else、正常私密暂存。所以实际表现是:同一个 bot、同一个 turn,最终回复私不私密取决于这条 final 恰好长什么样——这种不可预期比稳定失效更难排查。

另外,把 staging 补进 unified 分支恐怕也救不回语义:TurnReplyCardStore.update()const visible = record.mode === 'unified' || ...,统一卡从第一次 progress 起就已经公开了,最终答案再私密,这个 turn 也已经不是私密的了。

建议方向(二选一,但不建议维持现状):

  1. 配置校验直接拒绝这个组合(fail closed) —— 倾向这个作为最小修复。⚠️ 注意 final-onlysrc/bot-registry.ts:3681 会被 desugar 成 replyCardMode: 'unified',所以校验要在归一化之后的 mode 上做,只拒字面量 'unified' 会让 final-only 从闸下漏过去。
  2. 若要真正支持组合,建议单开设计讨论,明确统一卡下「私密」的语义边界。

现网影响有限:目前所有 bot 都是 legacy 模式,该路径暂不可达,所以这更像是「先把闸关上」而非线上故障。


F2(改断言):一处既有测试需要同步

src/cli.ts:10702 的条件新增了 !privateReviewStaged

if (effectiveResponseKind === 'final' && !customCard && !pureVideoSend
    && !vcMeetingManagedSendOrigin && !privateReviewStaged && messageId) {

主干 #1341b78d842ab)中有测试按原字符串断言这一行,rebase 到最新主干后会红。功能上没问题,改断言即可。


F3(补一个负向测试):nonce 校验缺覆盖

src/services/private-reply-review.ts 的发布闸:

if (record.nonceHash !== nonceHash(input.nonce)) return { ok: false as const, reason: 'expired' };

把这行改成恒真短路后,相关 10 个用例仍然全绿;作为对照,把下面的 allowed 改成 true 会立刻红 1 个——说明 audience/admin 这道主闸是有覆盖的,缺的只是 nonce 这层。nonce 属于 CSRF 式的纵深防御,所以量级是 minor,但补一个负向用例(错误 nonce → { ok: false, reason: 'expired' })成本很低,建议和 F1 放在同一轮修改里一起补上。


两个非阻断的小建议

  • private-reply-publications 记录里存了完整答案正文,目前没看到清理逻辑,长期会累积。
  • 话题群 + fallback: 'drop' 的组合下,最终答案会被静默丢弃(投递循环里话题群跳过 ephemeral,fallback 非 dmcontinue)。如果这是预期行为,建议在文档或配置校验里显式说明。

以上为自动评审的初步意见,可能有误判,最终以维护者审阅为准。F1 涉及设计取舍,建议先确认方向再动手。辛苦了!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(lark): 支持 oncall 回复审核后公开

2 participants