Skip to content

feat(dashboard): Slash 命令权限可视化配置(透传 + canTalk 降权名单) - #595

Merged
deepcoldy merged 1 commit into
masterfrom
feat/dashboard-slash-command-config
Jul 26, 2026
Merged

feat(dashboard): Slash 命令权限可视化配置(透传 + canTalk 降权名单)#595
deepcoldy merged 1 commit into
masterfrom
feat/dashboard-slash-command-config

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

动机

PR #547(已合)把 canTalkDaemonCommands 打通了后端字段 + CLI 热配置,但只能靠 /botconfig 文字命令或手改 bots.json。这个 follow-up 把它和早已在 daemon 支持、却一直没进 Dashboard 的 customPassthroughCommands 一起,做成 Dashboard 可视化配置——正是「哪些 slash 命令透传到 CLI、哪些命令 canTalk 权限的人可用」的可视化诉求。

方案

复用 per-bot 配置的单一事实源(bot-config-storeCONFIG_FIELDS),把两个 stringList immediate 字段接进 bot-defaults 页新增的「Slash 命令权限」区块。链路完全沿用成熟的 startupCommands 模式。

实现

影响面

纯 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 并读回

截图

新增「Slash 命令权限」区块(中文,已保存态)— 截图见飞书 review 消息。

🤖 Generated with Claude Code

## 动机

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 deepcoldy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

首次 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_PATHSdecideDashboardAuth 已在上游 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 复审;未获申晗确认前不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 查看。

@deepcoldy
deepcoldy merged commit 2621850 into master Jul 26, 2026
1 check passed
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.

1 participant