Skip to content

feat(clone): 支持一键克隆机器人 - #972

Merged
deepcoldy merged 3 commits into
deepcoldy:masterfrom
MarcWebber:codex/clone-bot
Aug 29, 2026
Merged

feat(clone): 支持一键克隆机器人#972
deepcoldy merged 3 commits into
deepcoldy:masterfrom
MarcWebber:codex/clone-bot

Conversation

@MarcWebber

@MarcWebber MarcWebber commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

改动

  • Dashboard 克隆时先选择模板;CLI 支持 botmux clone <机器人名> [--name <新名称>]
  • 复用现有创建 App 流程;名称留空时生成 源名称-copy-时间戳
  • 保留目标 App 凭证与 owner,只复制行为配置;不复制展示名、授权/账本等实例态。

影响

仅影响新 clone 入口;普通 setup、CLI adapters、PTY/Tmux 与既有会话不变。

验证

  • pnpm exec vitest run test/event-dispatcher.test.ts test/dashboard-bot-onboarding.test.ts --reporter=dot:2 files / 352 passed。
  • pnpm build:通过。
  • pnpm daemon:restart:5 个 Bot 与 Dashboard 均 online。

截图

clone-template-picker

@MarcWebber
MarcWebber requested a review from deepcoldy as a code owner August 22, 2026 13:59
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR!方向很实用,整体实现也挺干净。以下是自动评审跑出来的初步意见,供参考,最终以维护者审阅为准

先说好的部分:owner 身份边界的设计是对的cloneBotConfig{...target, ...source} 合并、再把身份字段(larkAppId/larkAppSecret/brand/allowedUsers/ownerOpenId)从 target 恢复,其中「target 没有就连 source 的一起 delete」这一步是关键——克隆时 target 是刚建好的新 bot 记录、不带 ownerOpenId,所以源的 ownerOpenId 必被删掉,不会跨应用泄漏。env 渠道也安全(sanitizePerBotEnv 会丢弃 BOTMUX*/__OWNER_OPEN_IDapplySessionOwnerEnv 之后再注入冻结);botOpenId/botName 属运行时状态不落盘,也带不出来。读代码是成立的。

有两点建议在合入前处理:

1)缺回归测试(建议必补)。 仓库 CLAUDE.md 对「创建 App 的入口」有一条明文要求:必须有覆盖 managed-Agent 场景的回归测试(源 app 的 BOTMUX_OWNER_OPEN_ID=ou_* 创建 target app,并验证真人 owner 在目标 Bot 下的 canOperate)。src/setup/owner-identity.ts 头注也专门警告过:只做格式校验的「姊妹路径」会在所有现存测试仍绿的情况下重新引入同一类 owner lockout。这个 PR 是第三个创建 App 的入口,目前 test/ 里对 cloneBotConfig / cmdClone / cloneSourceAppId 是零覆盖。建议补一条测试把上面的边界钉死——现在靠读代码成立,但没测试锁住,后续改动很容易悄悄破掉。

2)实例态字段被整体克隆(建议同 PR 修一行)。 {...source} 会把运行时的授权/账本态一起拷过去:oncallChatsallowedChatGroupschatGrantsglobalGrantsquotaStategrantExpiryStatesessionGroupchatReplyModesnoCardChats 等。这些引用的是源应用的 chat_id / open_id / 用量账本,对新应用没有意义,也和函数 docstring 自己承诺的「只克隆行为配置」不符。安全上不是跨身份泄漏(canOperate 不读这些字段,升不了管理权),但 oncallChats 值得注意:chat_id 是租户级共享的,新 bot 日后若被拉进源 bot 绑定过 oncall 的群,群成员会免 /grant 直接对新 bot talk + 自动开工——属于「静默的意外扩权」;chatGrants/quotaState 里的源 open_id/账本在新 app 则是死条目。建议把这些实例态并入排除名单,只克隆真正的行为配置(cli/model/sandbox/skills/feedback/env 等),顺便让上面那条测试把排除名单也钉住。

另有两个小问题(可选):

  • Dashboard 克隆模式下,表单里填的 cliId/workingDir/model 会在克隆后被源值静默覆盖(bot 被重读),用户填了却不生效。建议克隆模式下预填/禁用这些表单项,或明确提示「以源 Bot 为准」。
  • displayName 也被克隆,会导致名册里两个 bot 同名难以区分;另外 CLI(复用源 owner)与 Dashboard(用新扫码/登录身份)两条入口的 owner 结果语义不一致,建议在文档里说明一下。

再次感谢贡献 🙏 以上仅为自动评审的初步意见,具体是否调整、如何取舍,请以维护者的审阅结论为准。

@deepcoldy

Copy link
Copy Markdown
Owner

克隆时,让填一个不重复的名字会比较好?

@MarcWebber

Copy link
Copy Markdown
Contributor Author

已按评审意见更新并 rebase 最新 master

  • 克隆名称支持自定义;留空生成 源名称-copy-时间戳
  • 不再复制 displayName 及授权/账本等实例态;补充 managed owner 在目标 Bot 下 canOperate 的回归。

相关测试 352 passed,pnpm build 通过。

@deepcoldy

Copy link
Copy Markdown
Owner

感谢快速跟进!新提交(333527003)很好地处理了上一轮的两点主要意见,自动复审已确认:

  • 实例态不再被克隆cloneBotConfig 排除名单扩到覆盖 oncallChats / allowedChatGroups / chatGrants / globalGrants / quotaState / grantExpiryState / sessionGroup / chatReplyModes / noCardChats / displayName,现在只克隆行为配置。
  • 补上了 owner 边界回归测试:新增的 managed-Agent 用例把「源 ou_ 只在等于当前会话已认证 owner 时才经 normalizeManagedOwnerEntries 转成跨应用 on_、其余 ou_ 全过滤、真人 owner 在新 Bot 下 canOperate=true 而源 ou_ 为 false」这条链路钉死了。我们做了反向变异验证(去掉 ou_ 过滤 / 删排除项 / 不从 target 恢复 allowedUsers),三种改动都会让该测试变红——测试确实有牙,正好对上 CLAUDE.md 对创建 App 入口的强制要求。
  • 副本命名(--name,留空 源名-copy-时间戳 截断到 64 字符;Lark SDK 路径明确拒绝自定义名)也处理得当。

一个完全可选的小清理(不阻断合入):排除名单目前还漏了两个同类的按-chat_id 字段——defaultOncallAutoboundChats(源 chat_id 的 append-only「已自动绑定」账本)和 chatFeedbackPolicies(按源 chat_id 建 key)。二者都会被 {...source} 带进新应用;影响比上面那批更轻,也无 owner/权限泄漏,唯一实际后果是新 Bot 日后进入与源共享的群时,defaultOncallAutoboundChats 会让该群的自动 oncall 绑定被误判为「已花掉」而不触发。既然这轮正好在收紧克隆状态,若愿意可顺手把这两个字段也加进排除名单、并在测试的 instanceKeys 里补上(两边各两行);不补也不构成阻断理由。

另有一个 UX 小项(可 fast-follow):Dashboard 克隆模式下,表单里填的 cliId / workingDir / model 会在克隆后被源值静默覆盖,建议克隆模式下预填/禁用这些表单项或提示「以源 Bot 为准」。

再次感谢 🙏 以上仍为自动评审的初步意见,是否调整、以及最终合入,请以维护者的审阅结论为准。

@deepcoldy
deepcoldy merged commit 89acd4d into deepcoldy:master Aug 29, 2026
deepcoldy added a commit that referenced this pull request Aug 29, 2026
一键克隆机器人(#972)合入后的三处收尾。

1. 克隆排除名单补上两个按-chat_id 的实例态字段:
   - defaultOncallAutoboundChats:源应用 chat_id 的 append-only「已自动绑定」
     账本,带进新应用会让共享群的自动 oncall 绑定被误判成「已花掉」而不触发。
   - chatFeedbackPolicies:按源 chat_id 建 key 的反馈策略,对新应用是死条目。
   同时把排除名单与身份字段提为导出常量 CLONE_EXCLUDED_KEYS /
   CLONE_IDENTITY_KEYS,测试直接引用,避免测试另抄一份后与实现漂移。

2. Dashboard 克隆弹窗按源 Bot 预填 cliId / 工作目录 / model。此前表单显示通用
   默认值而后端克隆会用源 Bot 覆盖,用户填了却被静默丢弃。

3. 修复克隆后工作目录被默认值遮蔽:源 Bot 只有 workingDir(setup add 建出来
   的常见形态)时,表单默认 fixed 形态会让目标带上 defaultWorkingDir '~',而
   后端取目录是 defaultWorkingDir ?? workingDir,'~' 排在前面把源仓库目录遮蔽
   掉,新会话直接在家目录启动。改为按源自身的目录形态预填:源有
   defaultWorkingDir 走 fixed,否则有 workingDir 走 card,都没有则不臆造。
   为此 bot-payload / BotDefaultsRow 补出 workingDir 字段,并把「源配置行 →
   表单预填值」抽成纯函数 cloneSourceDefaultsFrom。

影响面:仅克隆入口(Dashboard 预填与共享的排除名单)及其测试。owner 身份边界
与 cloneBotConfig 的身份恢复逻辑未改动;普通「添加机器人」流程、CLI clone
路径、会话与后端逻辑均不变。

验证:bun run build 通过;对当前 master 的合并树上 dashboard-clone-prefill /
dashboard-bot-payload / event-dispatcher / setup-owner-identity /
bot-config-editor / dashboard-bot-onboarding / bot-config-store /
bot-talk-parity 八套 541 测试全绿,并逐条确认克隆相关用例确实执行而非跳过。
反向变异覆盖全链路四个环节,均如期变红:删 payload 的 workingDir 透传、
映射退回一律 fixed、去掉 dirMode 透传、从排除名单删字段、身份恢复清单去掉
allowedUsers。其中排除名单同时用字面量用例钉死内容、用常量驱动行为断言,
避免「删清单条目会连带删掉断言」的自指盲区。
@github-actions

Copy link
Copy Markdown

🚀 Released in v3.18.7

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