feat(dashboard): Slash 命令权限可视化配置(透传 + canTalk 降权名单) - #595
Conversation
## 动机 PR #547 把 canTalkDaemonCommands 打通了后端字段 + CLI 热配置,但只能靠 /botconfig 文字命令或手改 bots.json 配置。用户希望在 Dashboard 全局配置里 可视化地配「哪些 slash 命令透传给 CLI」「哪些 daemon 命令 canTalk 可用」。 ## 方案 复用现有 per-bot 配置的单一事实源(bot-config-store 的 CONFIG_FIELDS),把两个 stringList immediate 字段接进 Dashboard bot-defaults 页新增的「Slash 命令权限」区块: - customPassthroughCommands:额外透传给底层 CLI 的 slash 命令 - canTalkDaemonCommands:把选定 daemon 命令从 canOperate 降到 canTalk(#547 引入) ## 实现(沿用 startupCommands 的成熟链路) - **dashboard-ipc-server**:bots payload 新增两字段(space-joined 字符串);新增两条 PUT 路由 /api/bot-custom-passthrough、/api/bot-cantalk-daemon-commands,走 coerceConfigValue(尊重字段自带 parseList,与 #547 修的口径一致)+ applyConfigField (写盘 + 内存热更新),空串=清除回默认 - **dashboard.ts**:两条代理路由 /api/bots/:appId/custom-passthrough、/cantalk-daemon-commands - **bot-payload / bot-defaults**:payload 解析 + BotDefaultsRow 类型补两字段 - **bot-defaults-page**:新增 SlashCommandPermissionsSection 组件(两个 textarea + 独立保存按钮),渲染在 GrantSection 旁。两个子编辑器用**独立** useEffect,避免保存 一个字段触发重渲染时清空另一个的未保存草稿 - **i18n**:中英文标签 + help 文案;canTalk 字段 help 醒目提示「/cd /restart /rename 等会改状态,降级须谨慎,建议只放 /status /help 低危命令」(呼应 #547 review 的提醒) ## 影响面 纯 Dashboard 读写路径 + IPC,未触碰权限闸判定逻辑本身(canRunDaemonCommand 不变)。 字段过滤/归一化沿用 #547 已有的 parseList,安全语义不变(canTalk 只认 daemon 命令、 passthrough 拒绝 daemon 命令)。不影响其它 CLI / 后端 / 会话类型。 ## 测试 - pnpm build 绿;dashboard-bot-payload 新增用例(两字段字符串投影 + 缺省空串 + 非字符串兜底) - 相关套件全绿:dashboard-bot-payload / dashboard-ipc / dashboard-i18n / bot-config-store / can-talk-daemon-commands / command-handler - 全量非 e2e 单测通过(仅 3 个 v3-* 进程/PID-namespace 环境相关用例失败,已对照 stash 确认为 master 基线自带、与本改动无关) - live 验证:switch:here + daemon:restart 后实测两条 PUT 端点返回正确过滤结果 (canTalk 丢弃 /compact,passthrough 丢弃 daemon /status),持久化到 bots.json 并读回; 修复了初版「保存一个字段清空另一个草稿」的 UX bug(附截图) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
deepcoldy
left a comment
There was a problem hiding this comment.
首次 review — Claude(结论:可合入,仅 1 处非阻塞 P3 打磨项)
在 PR head 9f48ebba 上 checkout + 重建全量验证,并跑了 4 维度对抗式 review(security/authz、correctness-IPC、i18n/consistency、react-ui)+ 逐条对抗验证。
改动逻辑(白话)
PR #547 打通了两个 per-bot 字段但只能靠 /botconfig 文字命令或手改 bots.json。本 PR 把它们搬进 Dashboard「bot 默认」页做可视化编辑:
- customPassthroughCommands —— 内置白名单之外,额外原样透传给底层 CLI 的 slash 命令
- canTalkDaemonCommands —— 把选定 daemon 命令从「仅管理员(canOperate)」降到「能对话即可用(canTalk)」
链路是已上线 startupCommands 管线的忠实克隆:GET 把数组投影成空格拼接串 → textarea → PUT → coerceConfigValue(尊重字段自带 parseList 过滤)→ applyConfigField(写 bots.json + 内存热更新)。空串=清除回默认。权限闸判定本身(canRunDaemonCommand)零改动。
验证结果
pnpm build绿(PR 树)- 相关 6 套件 347 tests 全绿(dashboard-bot-payload / dashboard-ipc / dashboard-i18n / bot-config-store / can-talk-daemon-commands / command-handler)
- i18n 11 个 key 在 zh+en 双语均有定义,无裸 key 泄漏
- 安全维度 clean:两条新 PUT 路由不在
PUBLIC_READ_PATHS,decideDashboardAuth已在上游 401 未认证请求,与所有兄弟 mutation 路由同一信任边界;filter 语义保持(canTalk 只认 daemon 命令、passthrough 拒绝 daemon 命令);输入过滤后为空(如把/compact填进 canTalk 框)→coerceConfigValue返回empty→ 400 报错,而非静默清除,fail-loud 正确 - correctness/IPC 维度 clean:GET 投影 / PUT 回包 / textarea 读取字段名三方一致;空串清除路径正确;
findConfigField大小写匹配无误 - 双 useEffect 抗草稿互清设计成立:
patchBot每次保存产生新 bot 对象,但两个 effect 各自 key 到自己那个字段的字符串值,保存一个不会重置另一个未保存草稿(React 依赖浅比较跳过未变字段)
对抗验证:1 confirmed / 1 rejected
✅ P3(非阻塞・纯打磨)—— 共享 StatusSpan 反馈错位
SlashCommandPermissionsSection 用单个共享 status state(L2895),并把唯一一个 <StatusSpan> 渲染在两个子区块之下(L2978)。所有兄弟 section(StartupCommands L2882、LaunchShell、Env)都把各自的 StatusSpan 内联渲染在自己按钮旁的 .actions div 里。结果:保存上面的 passthrough 字段时,「✓ 已保存」的提示出现在下面 canTalk 字段的下方,视觉上归属错了。
- 验证结论:结构性事实全部属实且可复现;但 finding 原稿把危害夸大为「误导用户以为降权已保存 → 草稿被静默丢弃」——该安全叙事不成立:双 effect 设计保证保存 passthrough 不会丢 canTalk 草稿,save 本身不丢数据、不静默回退权限。净剩=纯 cosmetic 反馈错位,与所有兄弟 section 不一致。
- 建议(非阻塞):把两个子区块各自内联一个 StatusSpan(对齐兄弟范式),或在成功文案里带上字段名。
❌ rejected(判定 invalid)—— 「共享 busy 触发 double-submit」
finding 的核心区分前提「每个兄弟 section 都用独立布尔 busy,所以这种跨清除只此一家」属事实错误:AutoStartSection(L1291)、CardBehaviorSection(L1949) 都用同款 string-keyed busy 模式——这是既有成熟范式,非本 PR 引入的异常。且后果无害:两个 save 打不同端点、写不同独立 config key,payload 在调用时已捕获,applyConfigField 幂等。判定 invalid。
结论:改动低风险、克隆成熟链路、测试充分,可合入。唯一 P3 为纯 UI 打磨,不阻塞。 交 @codex 复审;未获申晗确认前不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
Codex 复审(head 9f48ebba)
结论:代码可合入,无阻塞项;未获申晗确认前不合码。
独立核对了 Dashboard 鉴权、browser → dashboard proxy → daemon IPC → coerceConfigValue / applyConfigField 全链路,以及 customPassthroughCommands / canTalkDaemonCommands 的过滤、清空、写盘和内存热更新语义。权限闸 canRunDaemonCommand 未改,新 PUT 路由仍位于 Dashboard token gate 和 daemon route-bound HMAC 后;未发现权限绕过或跨 CLI / 后端 / 会话类型回归。
实际验证:
pnpm build:通过pnpm vitest run test/dashboard-bot-payload.test.ts test/dashboard-ipc.test.ts test/dashboard-i18n.test.ts test/bot-config-store.test.ts test/can-talk-daemon-commands.test.ts test/command-handler.test.ts:6 files / 347 tests 全绿- 额外直接启动 IPC server 调两条新 PUT:GET 回填、合法项归一化、非法项过滤后
400 empty、空串清除、bots.json 写盘与 registry 热更新均符合预期 - 当前
origin/master仅多出 #594 release workflow 改动;PR mergeable,CI build 绿
P3(非阻塞)共享状态不只是视觉错位,还会互相覆盖失败提示
SlashCommandPermissionsSection 共用一个 status 和一个 string-keyed busy,同时两个按钮只禁用自己的 key。两项保存可并发:若 canTalk 保存失败而 passthrough 稍后成功,后者会把共享状态覆盖为“✓ 已保存”,且唯一的 StatusSpan 又位于 canTalk 区块下方,用户会误以为失败的降权名单已保存。反向完成顺序也会用一个失败覆盖另一个成功。
这仍是 fail-closed,草稿不会被双 effect 清掉,也没有跨字段写盘冲突,因此不升级为阻塞项;但“后果无害 / 纯 cosmetic”不够准确。建议两个子区块各自维护 status / busy,并把 StatusSpan 内联到对应按钮旁。
PR 规范项(非代码阻塞)
这是 Dashboard UI 改动,但 PR 的截图段落目前只写“见飞书 review 消息”。按仓库规范,建议把截图直接附到 GitHub PR 描述,方便脱离飞书的 reviewer 查看。
动机
PR #547(已合)把
canTalkDaemonCommands打通了后端字段 + CLI 热配置,但只能靠/botconfig文字命令或手改bots.json。这个 follow-up 把它和早已在 daemon 支持、却一直没进 Dashboard 的customPassthroughCommands一起,做成 Dashboard 可视化配置——正是「哪些 slash 命令透传到 CLI、哪些命令 canTalk 权限的人可用」的可视化诉求。方案
复用 per-bot 配置的单一事实源(
bot-config-store的CONFIG_FIELDS),把两个 stringList immediate 字段接进 bot-defaults 页新增的「Slash 命令权限」区块。链路完全沿用成熟的startupCommands模式。实现
coerceConfigValue(尊重字段自带parseList,与 feat(permissions): canTalkDaemonCommands — 把选定 daemon 命令从 canOperate 降到 canTalk #547 修的口径一致)+applyConfigField(写盘 + 内存热更新),空串=清除回默认SlashCommandPermissionsSection(两个 textarea + 独立保存按钮)。两个子编辑器用独立useEffect,避免保存一个字段触发重渲染时清空另一个的未保存草稿(初版有此 bug,已修并 live 复验)影响面
纯 Dashboard 读写 + IPC,未触碰权限闸判定逻辑本身(
canRunDaemonCommand不变)。字段过滤/归一化沿用 #547 已有的parseList(canTalk 只认 daemon 命令、passthrough 拒绝 daemon 命令),安全语义不变。不影响其它 CLI / 后端 / 会话类型。测试
pnpm build绿;dashboard-bot-payload新增用例(字符串投影 / 缺省空串 / 非字符串兜底)v3-*进程/PID-namespace 环境相关用例失败,已 stash 对照确认为 master 基线自带、与本改动无关)switch:here+daemon:restart后实测两条 PUT 端点返回正确过滤结果(canTalk 丢弃/compact,passthrough 丢弃 daemon/status),持久化到bots.json并读回截图
新增「Slash 命令权限」区块(中文,已保存态)— 截图见飞书 review 消息。
🤖 Generated with Claude Code