Skip to content

[Feat] Add grant request card for blocked mentions#276

Merged
deepcoldy merged 3 commits into
deepcoldy:masterfrom
nil-err:codex/grant-card-on-mention-miss
Jun 22, 2026
Merged

[Feat] Add grant request card for blocked mentions#276
deepcoldy merged 3 commits into
deepcoldy:masterfrom
nil-err:codex/grant-card-on-mention-miss

Conversation

@nil-err

@nil-err nil-err commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR fixes the silent failure path for bot-to-bot handoffs in groups when an external bot @mentions the current bot but has not yet been granted talk access.

Previously, the human-message path already had a self-service /grant request card: if a person explicitly @mentioned the bot but failed the canTalk gate, BotMux would notify the owner with an approval card. The foreign-bot path used a separate gate, however. If the sender was not a known same-deployment peer, was not in this bot's oncall chat, and did not match chatGrants or globalGrants, the dispatcher simply returned without any visible feedback. That made bot-to-bot collaboration look broken until an owner manually ran /grant @bot.

What Changed

  • Added an autoGrantRequestCards bot config field.
    • Default behavior is enabled for backward-compatible discoverability.
    • false explicitly disables automatic grant request cards and keeps the old silent-drop behavior.
  • Reused the existing grant request card flow for blocked external bot @mentions.
    • The card is still sent only to the owner path.
    • It uses the existing pending/deny throttling, so repeated blocked @mentions do not spam cards.
    • It grants talk-only access through the existing grant_chat / grant_global actions.
  • Kept the authorization model unchanged.
    • The blocked message is not routed into a CLI session.
    • A granted sender receives only canTalk access.
    • Sensitive operations such as /restart, /close, terminal write access, and config mutation remain governed by allowedUsers / owner permissions.
  • Added a Dashboard control in Bot Profiles -> Authorization & Quota.
    • New label: Auto grant card for blocked @mentions / 未授权 @ 自动弹授权卡.
    • Toggling it updates grant prefs through the existing dashboard proxy and daemon IPC path.
  • Updated bots-json docs in both zh/en to document the new field.
  • Adjusted grant request wording from user-specific language to sender/object language so the card also reads correctly for bot senders.

Root Cause

BotMux had two different inbound routing paths:

  1. User-originated group messages used checkGroupMessageAccess(). When a user explicitly @mentioned the bot but failed canTalk, the dispatcher called maybeSendGrantRequestCard().
  2. Bot-originated group messages used the foreign-bot gate. Unknown external bots were intentionally blocked unless they were a known peer, oncall participant, chatGrant, or globalGrant. That gate returned silently, so owners never saw an actionable authorization prompt.

This PR makes the foreign-bot gate use the same grant request card mechanism before returning, while preserving the gate itself.

User / Developer Impact

  • Owners get a visible, clickable authorization path when an external bot tries to hand work to this bot without prior grant.
  • Multi-bot collaboration becomes easier to diagnose: an unauthorized handoff no longer looks like a trigger failure.
  • Teams that prefer strict silence for unknown senders can disable the behavior per bot from Dashboard or by setting autoGrantRequestCards: false in bots.json.

Validation

  • corepack pnpm vitest run test/event-dispatcher.test.ts test/bot-registry-grant.test.ts test/dashboard-bot-payload.test.ts
    • 145 tests passed.
  • PATH=/tmp/botmux-corepack-bin:$PATH pnpm build
    • TypeScript compile and dashboard bundle passed.
  • git diff --check
    • Passed.

Notes

The first push to upstream deepcoldy/botmux was denied for this SSH identity, so the branch is pushed from the nil-err/botmux fork and this PR targets deepcoldy/botmux:master.

@nil-err nil-err force-pushed the codex/grant-card-on-mention-miss branch from df51537 to b524390 Compare June 22, 2026 12:54
@nil-err nil-err changed the title [codex] Add grant request card for blocked mentions [Feat] Add grant request card for blocked mentions Jun 22, 2026
@nil-err nil-err marked this pull request as ready for review June 22, 2026 14:00
@nil-err nil-err requested a review from deepcoldy as a code owner June 22, 2026 14:00
Review follow-up for deepcoldy#276:

- grant-prefs-store:新增 autoGrantRequestCards 写回/删除/默认/局部 patch/catch
  回退的 round-trip 测试(此前 0 覆盖,兄弟字段有 6 例)。
- event-dispatcher 测试:beforeEach 重置真实 grant-pending 表(消除跨用例
  节流状态泄漏),并新增「同一 bot+群重复 @Blocked 只发一张卡」的节流去重测试。
- card-builder escapeMd:转义集合补 `<`/`>`,防 attacker 可控名字(如外部 bot 的
  app 名)在 lark_md 卡片正文注入字面 `<at id=…></at>` 伪 mention。
- event-dispatcher:申请卡名字解析增加 observed-bots 花名册兜底——外部 bot 发送方
  不在自己消息的 mentions 里,原实现只能给 owner 显示裸 open_id。
- i18n zh:card.grant.body_request 「对象」改「发送方」对齐英文 Sender、消歧。
修 Codex 抓的节流/失败恢复缺口:

- maybeSendGrantRequestCard 在 replyMessage 失败时 clearPending()——原来只 log,
  pending 残留会把该发送方节流压死、owner 永远看不到卡片,只能等 daemon 重启或别的
  target 触发全表 prune 才恢复。清掉后下次 @ 会重试发卡。
- isThrottled 对 pending 增加 stale 窗口回收(与 denied 冷却回收同构):超过
  STALE_PENDING_MS 的废弃 pending 本 key 即时删除并放行。原实现对 pending 恒返 true,
  单一发送方反复 @、无其它 grant 活动触发 prune 时,过 24h 也不会自然恢复。
- 补两条守护测试:grant-pending「同 key stale pending 无需跨 key sweep 即回收」、
  event-dispatcher「发卡失败后下次 @Blocked 会重试」(均在去掉对应修复时失败)。
@deepcoldy deepcoldy merged commit 0482a30 into deepcoldy:master Jun 22, 2026
@nil-err nil-err deleted the codex/grant-card-on-mention-miss branch June 23, 2026 01:47
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.

2 participants