Skip to content

feat(lark): 支持 AI 动态修改当前群名称 - #643

Merged
deepcoldy merged 4 commits into
deepcoldy:masterfrom
ammend:feat/lark-chat-rename-skill
Jul 29, 2026
Merged

feat(lark): 支持 AI 动态修改当前群名称#643
deepcoldy merged 4 commits into
deepcoldy:masterfrom
ammend:feat/lark-chat-rename-skill

Conversation

@ammend

@ammend ammend commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

背景

botmux 在飞书群内持续运行时,群名称无法随任务目标和阶段变化自动更新。用户需要手工改名,群名容易与当前工作状态脱节。

本 PR 新增受控的群改名能力,让用户明确要求时,或 AI 判断任务进入关键阶段时,可以由当前群内的 bot 修改当前飞书群名称。

改动内容

  • 新增内置 Skill:botmux-chat-rename
    • 用户明确要求改群名时触发
    • AI 判断群目标或关键阶段明显变化时可主动触发
    • 通过现有 botmux skill show 渐进披露机制提供完整说明
  • 新增 CLI:
botmux chat rename "新的群名称"
botmux chat rename "支付链路排障|待验证" --proactive
  • 新增 session-scoped daemon IPC 路由:/api/sessions/:sessionId/chat-rename
    • 复用现有 rotating capability / trusted-host 鉴权
    • 只能修改当前 session 所在群
    • 不支持指定其他 chatId 或切换其他 bot 身份
  • 新增飞书群改名服务:
    • 调用前确认当前 bot 在目标群内
    • 使用当前 bot 的应用身份调用 im.v1.chat.update
    • 不会在失败后遍历其他 bot 凭据重试
  • 增加名称安全校验:
    • 去除首尾空白
    • 拒绝空名称、超长名称、控制字符和不可见格式字符
    • 同名请求幂等,不重复调用飞书写接口
  • 增加 AI 主动改名防抖:
    • --proactive 请求按 bot + chat 应用 10 分钟冷却
    • 返回结构化 rate_limited 和剩余等待时间
  • 增加结构化错误:
    • not_group_chat
    • bot_not_in_chat
    • invalid_chat_name
    • permission_denied
    • rate_limited
    • lark_api_error
  • 改名成功后同步同群活跃 session 的 chatDisplayName 缓存,并记录结构化审计日志。
  • 新增需求与设计文档:
    • docs/design/2026-07-28-lark-chat-rename-skill-requirement.md

为什么这样设计

群改名是有外部副作用的写操作,因此能力被限制在当前会话和当前 bot 身份内。AI 无法传入任意群 ID,也不能借用其他 bot 的凭据,从而避免跨群或跨身份越权。

用户明确要求的改名可直接执行;AI 主动改名则需要显式传入 --proactive,并受防抖限制,避免群名随细小进度频繁变化。

影响范围

  • 平台:仅飞书/Lark;其他 IM 不受影响
  • CLI:所有底层 Agent CLI 共用同一个 botmux 命令与 daemon IPC,不依赖特定 CLI
  • 后端:PTY、tmux、zellij、riff 等会话后端共用 session-scoped 路径
  • 会话:仅 group session 可用;p2p 返回 not_group_chat
  • 多 bot:只使用当前 session 对应 bot 的身份,不做凭据回退
  • Sandbox:CLI 不读取飞书凭据,由 daemon 执行实际 API 写入
  • 现有发送、调度、workflow、群创建等路径未修改

测试验证

执行过:

NODE_OPTIONS=--max-old-space-size=4096 pnpm build
NODE_OPTIONS=--max-old-space-size=4096 pnpm exec tsc --noEmit
pnpm vitest run --project unit test/chat-rename.test.ts test/groups-store.test.ts test/ipc-slash-route.test.ts
NODE_OPTIONS=--max-old-space-size=4096 pnpm test

结果:

  • build 通过
  • TypeScript 类型检查通过
  • 群改名相关测试 36/36 通过
  • 全量 unit:11,061 通过,16 跳过
  • 全量测试有 2 个与本改动无关的环境 smoke 失败:
    • Node 24 下 /proc/<pid>/comm 返回 MainThread,既有用例预期 node
    • PID namespace helper 在全量并发运行时启动超时;单独复跑已通过

飞书实测

使用真实飞书群完成写入、读取确认和恢复:

  1. 原群名:4栋203
  2. 修改为:4栋203|botmux改名实测
  3. 重新读取群列表,确认新名称已生效
  4. 恢复为:4栋203
  5. 再次读取,确认恢复成功

两次 chat.update 均返回 ok=true, changed=true

已知边界

  • 当前 --proactive 冷却状态保存在 daemon 进程内,daemon 重启后不会保留。
  • 本 PR 暂未增加按 bot 开关;能力通过当前会话鉴权、群成员校验和 Skill 调用规则约束。

@ammend
ammend requested a review from deepcoldy as a code owner July 28, 2026 16:17

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

首次 Review(Claude)— 结论:无阻塞,1 个 P2 完整性缺口 + 若干已披露的延后项。请等申晗确认后再合并。

Head 定位:c1230cbb(基于 47dce8b2,落后当前 master 37d0fc44)。fork PR 无 CI,本地已验证。

改动逻辑(白话)

让「当前群里的 bot」用自己的应用身份改当前会话所在群的群名,是一条带外部副作用的写操作,因此从头到尾被锁在「当前会话 + 当前 bot」的边界内。链路:

  1. CLI botmux chat rename "<名字>" [--proactive]src/cli.ts cmdChat)—— 完全复刻 cmdSlash/cmdRoleSwitchfindAncestorSessionContext() 定位当前 session → findDaemon()postSessionCliIpc(..., 'chat-rename', {name, proactive})。请求体只带 name/proactive,不带 chatId/appId。
  2. daemon IPC 路由 POST /api/sessions/:sessionId/chat-renamedashboard-ipc-server.ts)—— 加进 routeHasNarrowUntrustedAuth 窄孔(沙箱/读隔离 CLI 靠本会话 rotating capability 进来,与 slash/cd/close 同款)。handler 里 chatIdlarkAppId 一律从服务端活跃会话记录 ds 里取,绝不信请求体 → 这是「改不了别的群、借不了别的 bot 身份」的结构性保证。依次校验:sessionCliIpcAuth → session 活着 → chatType==='group'(否则 not_group_chat)→ 名称规范化 → proactive 冷却 → renameChat
  3. 名称规范化core/chat-rename.ts normalizeLarkChatName)—— trim、拒空、拒超长(100 码点)、正则拒控制字符/一批不可见字符。
  4. 冷却ChatRenameCooldown,按 appId:chatId)—— 只对 --proactive 生效;record() 只在真正改名成功changed:true)后写,失败或同名 no-op 不消耗冷却。
  5. Lark 写入groups-store.ts renameChat)—— 先 isInChatbot_not_in_chat → GET 读旧名 → 旧名==新名则幂等 changed:false 且不调写接口 → im.v1.chat.update 改名(与既有 transferChatOwner 同签名)→ 错误经 classifyRenameChatError 归一为 permission_denied/lark_api_error不做跨 bot 凭据回退(注释明确说明)。
  6. 缓存同步 + 审计—— 改名成功后把同群活跃 session 的 chatDisplayName 刷成新名并落盘;[chat-rename:audit] 结构化日志记 session/chat/app/proactive/old/new,不含 token。

独立验证

  • pnpm build ✅ / tsc --noEmit ✅(exit 0)
  • 本 PR 新测试 chat-rename(4) + groups-store(21) + ipc-slash-route(11) = 36 ✅
  • 回归相关面 ipc-cd-route(12) + builtin-skills(20) + ensure-plugin-skills(8) 追加 ✅(共 68 绿)
  • CLI 接线实测:botmux chat 打印用法、chat rename 空名报用法、chat rename "测试" 正确定位 session+daemon 并 POST(现网 daemon 跑旧 canonical 无此路由故回 404,恰证接线通)
  • 合并检查:git merge-tree --write-tree origin/master HEAD exit 0(与 master 有 cli.ts/dashboard-ipc-server.ts 两文件重叠,但均为纯新增区段,自动合并干净)
  • 影响面:对 cli.ts/dashboard-ipc-server.ts 的改动纯增量(新增 switch case、新增路由、route 类型 union 追加 chat-rename、窄孔正则追加 |chat-rename),不改任何既有路径 → 跨 CLI/跨后端回归风险极低。

发现

P2(完整性,非阻塞):normalizeLarkChatName 让 3 个字符穿过名字中段,与 FR-5「拒绝换行、不可见格式控制字符」自相矛盾。
实测(差分探针):U+2028 LINE SEPARATOR、U+2029 PARAGRAPH SEPARATOR、U+FEFF ZWNBSP/BOM 出现在名字中段时被 ACCEPT(对照组 U+0085 NEL、U+200B ZWSP、U+000A LF 已正确拒绝)。.trim() 只清掉首尾的 U+2028/2029/FEFF,中段留存。U+2028/2029 是 Unicode 换行符、U+FEFF 是不可见格式符,按 PR 自己的 FR-5 应当拒绝。

  • 危害有限:调用方是生成短群名的 AI,几乎不会吐这些;即便写进去 Lark 也只是渲染成换行/空白,无损坏无安全问题。所以定 P2 不定阻塞。
  • 修复零成本、不破现有断言:正则字符类补上 U+2028、U+2029、U+FEFF(U+FEFF 未落在现有 U+FFF9-FFFB 之外的任何区间内,需显式加),并补一条断言即可。

已披露/延后项(供申晗定夺 scope,非缺陷):

  • FR-6「冷却基于持久化审计记录」→ 实现是进程内 Map,daemon 重启即清零。PR body 已在「已知边界」披露。
  • §7 kill-switch(chatRename.enabled/allowAiProactive)→ 未实现,当前能力对全 fleet 常开。PR body 已披露「暂未增加按 bot 开关」。 叠加下一条一起看。
  • 「用户明确 vs AI 主动」是荣誉制:非 --proactive 的改名无任何冷却(只受 Lark 自身限流),且挂不挂 --proactive 由 AI 自行决定。架构上 daemon 无法核实「用户真的要求了」(只有 AI 读过用户消息),所以这是设计固有边界而非 bug;但意味着当前防「刷群名」只靠 AI 自觉 + proactive 那 10 分钟冷却。blast radius 低(群名、可逆、有审计),仍建议申晗知悉。
  • FR-6「并发按 chatId 串行化」未实现:两个 proactive 若在各自 record() 前都过了 check() 会并发改名(最后写生效)。AI 发起、真并发概率低,P3。

(cache-sync 那段我一度怀疑对不上——directChatDisplayName() 对 group 恒返 undefined,且 dashboard 群名走 /api/groups 实时拉取——核实后:该 chatDisplayName 写入的 group 侧唯一消费者是 codex 原生标题种子 worker-pool.ts:2245,故此同步窄但无害,不算 bug。)


以上为 Claude 首审。@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

Choose a reason for hiding this comment

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

Codex 复审 — 结论:无阻塞,但新增确认 1 个 P2 行为缺口、1 个 P2 审计缺口;首审 Unicode P2 成立

复审 head:c1230cbb不执行合并,仍需申晗确认。

新发现

P2:冷却检查早于同名判断,主动改名重试不满足幂等契约

src/core/dashboard-ipc-server.ts:803-815 会先检查 proactive 冷却,再调用 groupsStore.renameChat();而同名判断在 src/services/groups-store.ts:98-111 的 GET 之后才发生。一次 proactive 改名成功会在 dashboard-ipc-server.ts:824 记录冷却,因此 10 分钟内对同一个名字重试会直接返回:

{"ok":false,"error":"rate_limited"}

不会按 FR-6 / AC-4 返回 ok:true, changed:false。这也意味着调用方无法把重试统一当成幂等成功。

建议把“成员校验 → 读当前名 → 同名 no-op”放到冷却前;仅在确定需要真实写入时检查/占用冷却。若顺手用 per-key mutex 串起“读名 → 冷却 → 写入”,还能一起收掉已披露的并发竞态。补一条路由级回归:proactive 成功后,同名 proactive 重试应 changed:false,不同名重试才 rate_limited

P2:实际写入的审计字段没有达到 FR-8

当前成功日志 dashboard-ipc-server.ts:830 缺 bot 自身 open_id;更新失败时,groups-store.ts:117-122 已经读到了旧名、也知道目标新名,但失败结果只返回 error/detail,最终 dashboard-ipc-server.ts:820 的审计没有 old/new。FR-8 要求实际写入记录 bot open ID、旧名、新名、触发类型和结果/飞书错误码。

建议用现有 getBotOpenId(larkAppId) 补 bot open ID,并让 update 失败结果携带 old/new(错误码最好单独字段,不只嵌在 detail 字符串里)。这是可观测性完整性问题,不影响改名本身。

对首审发现的复核

  • Unicode P2 成立:中段 U+2028U+2029U+FEFF 均被接受;U+0085U+200B 正确拒绝。建议正则补齐并加表驱动断言。
  • daemon 重启清空冷却、缺 per-bot kill-switch、显式/主动靠调用方自觉、并发未串行化:均属需求文档已写但当前实现未完成的 scope 取舍;由申晗决定本 PR 是否收口。
  • session-scoped 安全边界复核通过:请求体无法指定 chatId/appId;daemon 从活跃 session 取权威 chat/bot;rotating capability 绑定 URL session;不做跨 bot 凭据回退。
  • Workflow C0 default-deny 也能拦住新 chat 根命令,workflow subagent 不能直接触发群改名。

测试与集成验证

  • NODE_OPTIONS=--max-old-space-size=4096 pnpm build
  • NODE_OPTIONS=--max-old-space-size=4096 pnpm exec tsc --noEmit
  • 相关回归 6 files / 76 tests ✅
  • NODE_OPTIONS=--max-old-space-size=4096 pnpm test:722 files passed、1 skipped;11,076 tests passed、5 skipped ✅
  • Unicode 差分探针:U+2028/U+2029/U+FEFF 可复现放行 ✅
  • 冷却顺序探针:record 后、当前名读取前即返回 retryAfterSeconds
  • 当前 origin/master 比 PR 多 12 个提交;git merge-tree --write-tree origin/master HEAD exit 0,自动合并干净 ✅
  • fork PR 无 GitHub checks。

流程项

PR 标题目前是 Feat/lark chat rename skill,不符合仓库约定的 type(scope): 中文描述。建议改为 feat(lark): 支持 AI 动态修改当前群名称;两个 commit message 已符合规范。

@ammend

ammend commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@deepcoldy P2问题修复了,辛苦重新review下

@deepcoldy deepcoldy changed the title Feat/lark chat rename skill feat(lark): 支持 AI 动态修改当前群名称 Jul 29, 2026

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

增量复审(Claude)— 针对 4a2eec80 fix(lark): 保证群改名幂等与审计完整

作者已推新 commit(head c1230cbb4a2eec80)修掉双审提出的前两个 P2。逐条核实结论如下。

✅ P2-1(幂等 vs 冷却顺序)— 已修,且顺手关掉了 FR-6 并发缺口

  • 同名短路(groups-store.ts:124 if (fetchedOldName === newName) return {changed:false})现在先于 beforeUpdate 冷却门 → proactive 同名重试在冷却窗内也能拿到文档承诺的 {ok:true, changed:false},不再误撞 429。
  • 整条 check → 读名 → gate → 写 → record 包进新的 ChatRenameSerialQueue.run(cooldownKey)(按 appId:chatId 串行)→ 同时消掉了我首审提的 FR-6「按 chatId 串行化 / check-then-record TOCTOU」那条 P3。
  • 串行队列 throw-safe 已验证:finally { release() } 保证抛异常的 op 仍释放下一个 waiter(跑了并发脚本确认不死锁)。
  • retryAfterSeconds{...gate, oldName, newName} 透传到 429 body,未丢。
  • 一处可接受的行为变化:被限流的 proactive「改成新名」现在会先做 1 次 Lark GET(读当前名做同名判断)才返回 429,此前是 0 次 API。这是「同名幂等必须优先于冷却」的必然代价(不读名无法判定是否同名),proactive 本就 10 分钟一次,开销可忽略,不算缺陷

✅ P2-2(审计字段不完整)— 已修

  • 成功/失败日志均补 botOpenId=getBotOpenId())+ trigger=user_explicit|ai_proactive;失败日志现在也带 old/new/larkCodeRenameChatResult{ok:false} 分支扩了 oldName/newName/larkCode 字段承载。达 FR-8。

⚠️ P2-3(名称正则漏 U+2028/U+2029/U+FEFF)— 仍未修

  • 差分探针在 4a2eec80 上复测:U+2028/U+2029/U+FEFF 出现在名字中段仍被 ACCEPT(normalizeLarkChatName 本次未改动)。仍与 FR-5 自相矛盾。
  • 一如首审定性:危害极小(AI 几乎不会吐、飞书顶多渲染空白)、修零成本。非阻塞,是否现在补由作者/申晗定。

验证(在 4a2eec80 上)

  • pnpm build ✅ / tsc --noEmit ✅(exit 0)
  • 新增/更新测试全绿:chat-rename(5) + groups-store(24) + ipc-chat-rename-route(1,路由级证同名 200/changed:false、异名 429) + ipc-slash-route(11) + ipc-cd-route(12) = 53 ✅
  • 回归面追加 builtin-skills/ensure-plugin-skills/daemon-ipc-session-auth 等 35 ✅
  • renameChat 新增可选第 4 参 opts.beforeUpdate,全仓唯一调用点已适配,无破坏。
  • git merge-tree --write-tree origin/master HEAD exit 0(对当前 master 63491522 仍干净)。

结论

两个 P2 修得干净、有配套测试,还附带关掉一条 P3 并发缺口,代码质量高。唯一残留 = P2-3 Unicode(非阻塞)。 标题已按规范改为 feat(lark): 支持 AI 动态修改当前群名称

@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

Choose a reason for hiding this comment

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

Codex 增量复审 — 4a2eec80

结论:幂等/冷却与 FR-8 审计两个 P2 已正确修复;新增确认 1 个 FR-7 P2,Unicode P2 仍在。无阻塞,不执行合并,继续等申晗确认。

已核实修复

  • 同名判断现在位于 beforeUpdate 冷却门之前:proactive 成功后的同名重试返回 200 / changed:false,异名重试返回 429 / rate_limited
  • ChatRenameSerialQueue 能按 appId:chatId 串行整段 read → gate → write → record;异常路径 finally release() 可释放后继。我用首个 operation 抛错、第二个同 key 继续执行的探针复核,后继正常运行。
  • 成功/失败审计已补 botOpenIdtrigger、old/new、larkCode;update 非零返回也把这些字段带回 route。
  • PR 标题已改成规范格式。

P2:飞书写成功后,缓存落盘异常会把已成功操作回成 HTTP 500

src/core/dashboard-ipc-server.ts:832-845renameChat() 已返回 {ok:true, changed:true} 后,直接循环调用 sessionStore.updateSession(active.session)。该函数会同步执行 writeFileSync + renameSync,遇到 ENOSPC/EACCES 等会抛异常;此处没有 catch,于是异常冒到 server 总 catch(dashboard-ipc-server.ts:3612-3615):

  1. Lark 群名已经改成功;
  2. success audit 尚未执行;
  3. CLI 收到 HTTP 500,而不是成功;
  4. 后续同群 session 的缓存同步也被提前中断。

这与需求 FR-7 的明确契约相反:docs/design/2026-07-28-lark-chat-rename-skill-requirement.md:176 要求“飞书写入成功但本地缓存刷新失败,操作仍视为成功,同时记录缓存刷新告警”。

建议对每个 session 的持久化做 best-effort try/catch:记录带 sessionId 的 cache-refresh warning、继续同步其它 session,并且无论缓存写盘是否失败都继续写 success audit、返回 200 changed:true。补一条 route test:mock sessionStore.updateSession 抛错,断言响应仍为 200 且 warning 被记录。

仍在的 P2

  • normalizeLarkChatName 仍放行名字中段的 U+2028U+2029U+FEFF,与 FR-5 不一致;独立差分探针复现。仍定性为低风险、非阻塞。

验证

  • NODE_OPTIONS=--max-old-space-size=4096 pnpm build
  • NODE_OPTIONS=--max-old-space-size=4096 pnpm exec tsc --noEmit
  • 相关回归:8 files / 88 tests ✅
  • 串行队列 throw-safe 探针 ✅
  • 全量 unit:11,076 tests passed;group-join-shared-routingbeforeAll 在全量并发中发生 10s hook timeout;单独复跑 5/5 ✅(与本 PR 无关的环境/并发波动)
  • 当前 origin/master 比 PR 多 37 commits;git merge-tree --write-tree origin/master HEAD exit 0 ✅
  • GitHub checks:无。

@deepcoldy

Copy link
Copy Markdown
Owner

代修完成(Claude,申晗授权)— 35b30b71 fix(lark): 群改名缓存刷新兜底 + 补齐名称非法字符校验

申晗授权对两条非阻塞 P2 直接代修(maintainerCanModify=true,已 fast-forward 推到本 PR 分支,作者原 3 个 commit 归属不动)。逐条如下。

✅ FR-7 缓存刷新兜底(codex 增量复审提出的 P2)

  • 问题:飞书 chat.update 已成功后,同步本地会话缓存 chatDisplayName 时调 sessionStore.updateSession() 会写盘;若磁盘满/无权限(ENOSPC/EACCES)抛错,异常会冒泡出 route handler → 被 createServer 外层 catch 转成 HTTP 500。结果:飞书那步其实已改成功,却回给调用方"失败",success 审计被跳过,AI 可能重试一个已完成的改名。违设计文档 FR-7 第 176 行「飞书写成功但本地缓存刷新失败,操作仍视为成功,同时记录缓存刷新告警」。
  • 修法dashboard-ipc-server.ts):把缓存同步循环里的 updateSession 逐 session 包 try/catch,写盘失败只记 [chat-rename:audit] cache_refresh_failed ... warning,改名仍返回 200。逐 session catch(非整个循环一把)→ 一个坏 session 不会跳过其余 session 的刷新。零行为风险:只把"本地小事故"从"整单失败"降级为"记警告",与 FR-7 语义一致。

✅ FR-5 名称非法字符补齐(首审 Unicode P2)

  • 问题normalizeLarkChatName 正则漏了 U+2028(LINE SEP)、U+2029(PARA SEP)、U+FEFF(ZWNBSP/BOM),夹在名字中段能绕过校验,与 FR-5「拒绝换行、不可见格式控制字符」矛盾。
  • 修法chat-rename.ts):字符类补 

(插在 之间)+ 结尾 。纯收口。

测试

  • chat-rename.test.ts +1 用例:mid-name U+2028/U+2029/U+FEFF 均断言 invalid_chat_name(差分探针在 rebuild 后复测:三者 REJECTED、合法中文名仍 ACCEPTED、无回归)。
  • ipc-chat-rename-route.test.ts +1 路由级用例:mock renameChat 成功 + updateSession 抛 ENOSPC → 断言 仍 200 成功updateSession 被真正调用过(证 catch 生效、非跳过)。测试日志实证顺序:先 cache_refresh_failed WARN → 后 result=success

验证(在 35b30b71 上)

  • pnpm build ✅ / tsc --noEmit
  • chat-rename(6) + groups-store(24) + ipc-chat-rename-route(2) + ipc-slash-route(11) + ipc-cd-route(12) = 55 ✅
  • 回归追加 builtin-skills/ensure-plugin-skills/daemon-ipc-session-auth 35 ✅
  • git diff --check 干净;git merge-tree --write-tree origin/master HEAD exit 0(对当前 master 966a34f6
  • push 为 fast-forward,作者 28d80495/c1230cbb/4a2eec80 原样保留,我的修在最上层。

至此双审提出的 3 个 P2 全部落地。@codex 请复核这个增量 commit。仍等申晗最终确认合码。

@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

Choose a reason for hiding this comment

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

Codex 增量复审 — 35b30b71

结论:FR-7 缓存刷新兜底与 FR-5 Unicode 校验都修得正确;双审提出的 3 个 P2 至此全部关闭。本增量未发现新的代码缺陷或阻塞项。未执行合并,继续等申晗最终确认。

逐项核实

  • FR-7 部分成功语义正确。 sessionStore.updateSession() 的异常现在只在每个 active session 自己的 try/catch 内被吸收;Lark 改名成功不会被反转成 HTTP 500。catch 位于循环内部,因此一个 session 落盘失败不会阻断后续同群 session;之后仍会执行 success audit 并返回 200 / changed:true
  • 缓存刷新并非被跳过:路由测试明确让 updateSession()ENOSPC,同时断言写入确实被调用、HTTP 仍为 200。独立运行日志顺序也实证为 cache_refresh_failed WARNresult=success INFO
  • FR-5 校验补得精确。 正则只新增 U+2028U+2029U+FEFF;中段三个字符现均返回 invalid_chat_name,合法中文名仍通过,没有扩大到无关字符范围。
  • 增量仅涉及 chat-rename 自己的 validator/route 及对应测试,没有改其它 CLI、后端或既有 IPC 路由语义。commit message 也符合仓库格式。

独立验证

  • NODE_OPTIONS=--max-old-space-size=4096 pnpm build
  • NODE_OPTIONS=--max-old-space-size=4096 pnpm exec tsc --noEmit
  • 相关回归:8 files / 90 tests ✅
  • Unicode 差分探针:U+2028/U+2029/U+FEFF 全部拒绝,合法中文名通过 ✅
  • 全量 unit:722 files passed、1 skipped;11,078 tests passed、10 skipped。唯一失败仍是与本 PR 无关的 group-join-shared-routing 全量并发 beforeAll 10s timeout,隔离复跑 5/5 ✅
  • git diff --check
  • 相对最新 origin/master (966a34f6):master 侧 53 commits、PR 侧 4 commits;git merge-tree --write-tree origin/master HEAD exit 0 ✅
  • GitHub checks:无。

测试强化上有一个可选小点:FR-7 用例目前通过真实日志输出证实 WARN,但没有对 logger.warn 做 spy 断言;现有实现与主行为契约已经清楚覆盖,不作为缺陷或合并前要求。

FR-7:飞书群名写入成功后,同步本地会话缓存 chatDisplayName 时若
sessionStore.updateSession() 写盘失败(ENOSPC/EACCES),异常原本会冒泡成
HTTP 500,把「飞书那步已成功」的改名反转成失败——success 审计被跳过、
AI 可能重试已完成的改名。现改为逐 session best-effort:写盘失败只记
cache_refresh_failed warning,改名仍返回 200 成功,符合设计文档 FR-7
「飞书写成功但缓存刷新失败仍视为成功,记缓存告警」。

FR-5:normalizeLarkChatName 正则补上 U+2028/U+2029(Unicode 行/段分隔符)
与 U+FEFF(ZWNBSP/BOM)——此前这三个字符夹在名字中段能绕过校验,与
FR-5「拒绝换行、不可见格式控制字符」矛盾。

测试:
- chat-rename.test.ts 新增 mid-name U+2028/U+2029/U+FEFF 拒绝断言
- ipc-chat-rename-route.test.ts 新增 FR-7 路由级用例:mock 缓存写抛
  ENOSPC → 断言仍 200 成功且写入被真正尝试过(证 catch 生效非跳过)
- build ✅ tsc ✅ 相关 90 测试全绿;与最新 master merge-tree 干净

Co-Authored-By: Riff <noreply@riff.dev>
@deepcoldy
deepcoldy force-pushed the feat/lark-chat-rename-skill branch from 35b30b7 to ddf7ed0 Compare July 29, 2026 16:25
@deepcoldy

Copy link
Copy Markdown
Owner

采纳 codex 的测试强化建议(Claude)— 已 amend 进 ddf7ed06

codex 增量复审确认 35b30b71 两处修复均正确、3 个 P2 全部关闭,并提了一条不影响合并的测试强化建议:FR-7 路由测试可再 spy 断言 logger.warn 真的记了缓存告警。

采纳理由:FR-7 的契约本身就是「飞书写成功但缓存刷新失败仍视为成功,并记录缓存刷新告警」——只断言"写入被尝试过"是代理指标,直接断言 warning 落地才是验到规范本身。测试专属、零生产风险。

改动(test/ipc-chat-rename-route.test.ts,amend 进原 fix commit,非新增 commit):

  • import loggervi.spyOn(logger, 'warn')
  • 新断言:warnSpy 调用里存在含 cache_refresh_failed 的记录

验证(在 ddf7ed06 上):tsc ✅|chat-rename(6)+groups-store(24)+ipc-chat-rename-route(2)+ipc-slash(11)+ipc-cd(12)=55 ✅|force-push 走 --force-with-lease(remote==旧 35b30b7,安全),作者原 3 commit 归属不动|merge-tree 对 master 966a34f6 exit 0。

至此代码修复 + 测试强化全部落地。仍等申晗最终确认合码。

@deepcoldy
deepcoldy merged commit 5ac851e into deepcoldy:master Jul 29, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

✅ 已合并(申晗授权 admin-merge)

merge commit 5ac851eb,非 squash,保留 4 commit 归属(作者 28d80495/c1230cbb/4a2eec80 + 收尾 fix ddf7ed06)。

合前核实:head 无漂移(== ddf7ed06)、merge-tree --write-tree 对最新 master 96c30506 exit 0。双审提出的 3 个 P2(FR-7 缓存兜底 / FR-5 Unicode 校验 / 幂等+审计)全部关闭。

⚠️ 后续:fork PR 无 CI(本地已全绿);live 生效需 pnpm switch:here && pnpm daemon:restart(会让所有 bot 跑该 checkout,验完记得切回 canonical)。

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