feat:支持使用自定义的AI卡片模版 - #550
Conversation
meng93
left a comment
There was a problem hiding this comment.
@ziyilin 感谢贡献 🙏 整体方向(自定义模板 + 用户反馈记录)有价值。已对照当前 main(注:你的 base 落后 30 个 commit,建议先 rebase)逐条核对,下面是分级建议。修复后我们当天合入 🚀
🔴 P0(必须修复)
reply-dispatcher.ts:710移除sendFallbackErrorMessage是范围外回退
diff 中将非 QPS 错误分支的await sendFallbackErrorMessage('sendMessage', err.message);删掉,改成"等下一次 partial 重试"。这一改动与 PR 标题(自定义模板 + 反馈)无关,且 partial 重试不一定会发生(流结束就再没机会),用户会直接没感知。请:- 还原此行;或
- 拆出独立 PR 并附说明("closeStreaming/finishAICard 已能兜底"需给出代码路径证据)
🟡 P1(建议修复后合入)
-
card.ts删除staticMsgContent: ""写入需要保留
原代码在streamAICard/finishAICard都把staticMsgContent: ""写空。本 PR 直接删掉,对依赖该字段做静态展示的默认模板(02fcf2f4-...schema)存在静默行为变化风险。请保留:- 默认模板路径(
contentKey === DEFAULT_CARD_TEMPLATE_KEY)下继续写入staticMsgContent: "" - 自定义模板路径下不写(避免污染用户自定义字段)
- 默认模板路径(
-
PR 描述中的
USER_FEEDBACK.md实际未提交
描述里写"新增反馈功能文档",但gh pr view --json files中没有这个文件。请补到docs/USER_FEEDBACK.md(与MULTI_AGENT_SETUP.md等保持一致)。 -
TOPIC_CARD 解析失败回包
{}与现有 TOPIC_ROBOT 风格不一致
现存 TOPIC_ROBOT 错误路径回包{ success: true }(见 mainconnection.ts:563)。新 TOPIC_CARD 解析失败回{},建议至少补success: false字段或对齐到与正常路径同构的最小响应(cardData.cardParamMap: {}),避免钉钉侧不必要的重试判定。 -
TOPIC_CARD listener 没有跟随 cleanup
虽然现状的 TOPIC_ROBOT 也没清理(属于历史问题),但既然新增了一个 listener,建议借这次把 cleanup 一起补上:保留 listener 引用,cleanup 中 off 掉。否则monitorSingleAccount任何重入路径都会叠加。 -
测试覆盖偏窄
13 个 case 全部针对card-session-registry,PR 真正的高风险逻辑没覆盖:- TOPIC_CARD 回调(actionId 解析、自定义 actionId 生效、点踩原因)
card.ts自定义contentKey替换路径
建议至少补 3~4 个 callback 单测(可 mockclient+ 直接断言响应结构)。
-
cardLikeVar命名不清
Var表义模糊。建议改为cardLikeStateKey或cardFeedbackStatusKey,让配置文件读起来自解释。 -
OpenClaw session JSONL 直写存在跨仓耦合
appendFile直接写到~/.openclaw/agents/.../sessions/*.jsonl,强耦合 OpenClaw 的目录约定与 schema。短期 OK,但建议:- 在
docs/USER_FEEDBACK.md明确写明这是"插件对 OpenClaw 私有目录的约定写入" - 中期跟进 OpenClaw 提供 custom-event API 后切换
- 在
🟢 P2(优化项,可后续 follow-up)
- 并发写入注释偏弱:
appendFile在快速点击场景下与 OpenClaw session writer 的潜在竞争,建议加一次"短时连点"压测;或写入前判断尾部是否为\n避免空行。 card-session-registry内存 Map 无上限:建议加 size 监控日志或硬上限(如 50k)+ LRU 淘汰。- TS
any用得偏多:callbackData/parsedContent建议抽DingtalkCardCallbackPayload类型。 _getRegistryForTesting暴露给所有 consumer:可改用 vitest 的模块 mock,避免在生产代码里出现测试专用 API。recordFeedbackToSession内部已 try/catch 包住,connection.ts里的.catch()永远不会触发:可移除冗余.catch()handler。- 24h TTL 隐藏行为:建议在
docs/USER_FEEDBACK.md写明"机器人重启或卡片超过 24h 后的反馈不会落库"。
辛苦 @ziyilin 跟进 P0 1 条 + P1 7 条;P2 可以本 PR 一起做,也可以 follow-up。改完打个招呼,当天合入 🚀
Add TOPIC_CARD Stream callback handler for custom AI Card templates, supporting like/dislike feedback. Introduces 3 optional config fields (cardLikeActionId, cardDislikeActionId, cardLikeVar) so different card templates can customize callback action IDs and variable names.
c46fa86 to
99dd48f
Compare
|
@meng93 感谢 review!所有 P0 + P1 已修复,合并为一个commit。逐条对应如下: 🔴 P0
🟡 P1
额外修复A:消息拆分后反馈丢失问题问题:当 AI 回复内容超长时,dingtalk-connector 会将消息拆分为多条(通过 修复方案:
额外修复 B:进程重启场景下反馈丢失(顺带响应 P2 #14)问题:原实现注册表只在内存里。dingtalk-connector 一旦重启(升级、OOM、宿主重启……),所有已发出去但用户尚未点击的卡片,再收到点赞/点踩时都查不到 session 映射,反馈全部丢失。 ** 修复**:
验证
关于 P2 项目P2(#9-#13)作为 follow-up 跟进,不在本 PR 范围内。如有需要可以另开 issue 追踪。 |
|
你好,请问这里的实现用的是ai卡片自带的功能区按钮吗,还是自定义的按钮。 |
Summary
cardTemplateId、cardTemplateKey替换内置模板,使不同业务场景可以使用各自的卡片样式cardLikeActionId、cardDislikeActionId、cardLikeVar),适配不同卡片模板的按钮定义解决issue:#540
变更内容
1. 自定义 AI 卡片模板支持(af86a20)
问题:之前 AI 卡片模板 ID 硬编码为
02fcf2f4-5e02-4a85-b672-46d1f715543e.schema,内容字段名固定为msgContent,无法使用自定义卡片模板。方案:新增 5 个可选配置项(均有默认值,不影响已有配置),将模板 ID、内容字段名、回调按钮 ID 全部参数化。
新增配置项
cardTemplateIdstring02fcf2f4-...schemacardTemplateKeystringmsgContentcardLikeActionIdstringai_res_likecardDislikeActionIdstringai_res_dislikecardLikeVarstringlike配置位置:
openclaw.json中的dingtalk-connector插件配置。支持两种配置模式:单账号模式(直接在插件顶层配置):
{ "dingtalk-connector": { "clientId": "xxx", "clientSecret": "xxx", "cardTemplateId": "your-custom-template-id.schema", "cardTemplateKey": "content", "cardLikeActionId": "thumbs_up", "cardDislikeActionId": "thumbs_down", "cardLikeVar": "feedbackStatus" } }多账号模式(在
accounts[].config中配置,可按账号设置不同模板):{ "dingtalk-connector": { "accounts": [ { "clientId": "xxx", "clientSecret": "xxx", "config": { "cardTemplateId": "your-custom-template-id.schema", "cardTemplateKey": "content" } } ] } }代码变更
src/services/messaging/card.ts:将硬编码的AI_CARD_TEMPLATE_ID改为DEFAULT_AI_CARD_TEMPLATE_ID,createAICardForTarget、streamAICard、finishAICard均从 config 读取cardTemplateId和cardTemplateKeysrc/config/schema.ts:在DingtalkSharedConfigShape中新增 5 个 optional 字段openclaw.plugin.json:在顶层和 accounts 两处 JSON Schema 中注册新字段src/core/message-handler.ts:移除本文件中重复定义的AI_CARD_TEMPLATE_ID和AICardStatus常量(已统一到card.ts)src/reply-dispatcher.ts:移除非 QPS 错误时的立即降级逻辑,改为等待下一次 partial 更新重试TOPIC_CARD 回调处理器
在
src/core/connection.ts中新增 TOPIC_CARD Stream 回调监听器:cardPrivateData.actionIds判断是点赞还是点踩cardLikeActionId/cardDislikeActionId/cardLikeVar构造响应dislike_reason和custom_dislike_reason参数socketCallBackResponse响应回调(finally 块保证无论异常都响应,避免钉钉超时重试)2. 用户反馈记录到 Session(c46fa86)
问题:用户的点赞/点踩行为仅在卡片上生效,无法在会话历史中留存,统计插件无法获取用户满意度数据。
方案:新增 card-session 内存映射注册表,在卡片创建时注册
cardInstanceId → sessionKey映射,在回调触发时查找映射并将反馈追加到 session JSONL 文件。数据流
JSONL 条目格式
统计插件通过
type === "custom" && customType === "user-feedback"过滤反馈条目。点赞:
{ "type": "custom", "customType": "user-feedback", "data": { "like": 1, "userId": "194584", "cardInstanceId": "card_1777440689892_pwm21ymr", "source": "dingtalk-card" }, "id": "12bbd507", "parentId": null, "timestamp": "2026-04-29T05:31:41.309Z" }点踩(含原因):
{ "type": "custom", "customType": "user-feedback", "data": { "like": -1, "userId": "194584", "cardInstanceId": "card_1777440689892_pwm21ymr", "source": "dingtalk-card", "dislikeReasons": ["回答不准确", "太啰嗦"], "customDislikeReason": "没有给出具体代码" }, "id": "c80d4b7c", "parentId": null, "timestamp": "2026-04-29T05:32:24.593Z" }重复反馈
用户可以反复点赞或点踩同一张卡片,每次操作追加新条目。统计时以同一
(cardInstanceId, userId)的最后一条为准(群聊中多用户可对同一卡片独立反馈)。核心模块
src/services/card-session-registry.ts(新增):内存注册表(Map<cardInstanceId, {sessionKey, agentId}>,24h TTL + 30min 自动清理)+recordFeedbackToSession()异步写入src/reply-dispatcher.ts:新增sessionKey参数,在两处卡片创建路径调用registerCardSession()src/core/message-handler.ts:将已有sessionKey变量传递给createDingtalkReplyDispatcher()src/core/connection.ts:在 TOPIC_CARD 回调中 fire-and-forget 调用recordFeedbackToSession()文件变更
openclaw.plugin.jsonsrc/config/schema.tssrc/core/connection.tssrc/core/message-handler.tssrc/reply-dispatcher.tssrc/services/messaging/card.tssrc/services/card-session-registry.tstests/card-feedback/card-feedback.test.tsUSER_FEEDBACK.md测试
vitest run tests/card-feedback/card-feedback.test.ts)npx tsdown通过注意事项