From c3c8dbbab2700723986f7c86778a546390d5914a Mon Sep 17 00:00:00 2001 From: --git_token Date: Sat, 9 May 2026 08:10:02 +0800 Subject: [PATCH] auto backup 2026-05-09 08:10:02 --- .vala_skill_hashes | 2 +- memory/.dreams/events.jsonl | 1 + memory/.dreams/short-term-recall.json | 126 ++++- memory/2026-05-08-1754.md | 40 ++ memory/2026-05-08-1805.md | 43 ++ memory/2026-05-08.md | 37 ++ .../飞书反馈_2026-05-08.xlsx | Bin 0 -> 6730 bytes scripts/daily_feedback_report.sh | 4 +- scripts/sync_feedback_minutely.sh | 6 +- scripts/sync_feishu_feedback_wrapper.sh | 5 +- scripts/sync_group_to_sheet.py | 2 +- scripts/sync_wechat_feedback_minutely.py | 2 +- scripts/sync_wechat_feedback_minutely.sh | 6 +- .../sync_feishu_feedback.cpython-312.pyc | Bin 39184 -> 57876 bytes .../scripts/sync_feishu_feedback.py | 447 +++++++++++------- 15 files changed, 533 insertions(+), 188 deletions(-) create mode 100644 memory/2026-05-08-1754.md create mode 100644 memory/2026-05-08-1805.md create mode 100644 memory/2026-05-08.md create mode 100644 output/daily_feedback/飞书反馈_2026-05-08.xlsx diff --git a/.vala_skill_hashes b/.vala_skill_hashes index 5b29689..63cbca4 100644 --- a/.vala_skill_hashes +++ b/.vala_skill_hashes @@ -14,4 +14,4 @@ user-feedback-collector c0320451bf7ea0ce3d8ceaa603ae0a7b55c373c048363a5142258a4c user-feedback-data-source a95eb9142f3019fd193c46f89147dc7e0bf01dfe250202565a86f8bc52f37b13 feishu-group-msg-sync 085f95a5b89fec3b6a627da25d66ffeeb0be430098387739a64f7903f0ee88d4 user-feedback-processor 61783a8e9f03a973c187b359a87749ad1993dc71f8364b0a853d8b3ff64c75e8 -feishu-feedback-sync b046dd9f252ba6672acfac66aa732442cded924f0cd3c70e46cba5c6e300af6d +feishu-feedback-sync 878cad61ebd9d480eac6d1146a556031f02580c75841d4173f58afb076af7c34 diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index 13cc865..f5c2543 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -6,3 +6,4 @@ {"type":"memory.recall.recorded","timestamp":"2026-05-07T02:22:52.564Z","query":"用户反馈问题汇总 写入知识库 append overwrite 文档模式 feishu-feedback-sync","resultCount":6,"results":[{"path":"memory/2026-05-06.md","startLine":61,"endLine":86,"score":1},{"path":"memory/2026-04-30.md","startLine":116,"endLine":142,"score":1},{"path":"memory/2026-04-30.md","startLine":177,"endLine":205,"score":1},{"path":"memory/2026-04-30.md","startLine":198,"endLine":224,"score":1},{"path":"memory/2026-04-30.md","startLine":134,"endLine":166,"score":1},{"path":"memory/2026-04-30.md","startLine":73,"endLine":102,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-07T02:55:43.649Z","query":"问题分类 分类规则 版本更新 声音音频 UI显示 启动运行 语音识别 关卡内容 priority_classifier","resultCount":6,"results":[{"path":"memory/2026-05-06.md","startLine":61,"endLine":86,"score":1},{"path":"memory/2026-04-30.md","startLine":116,"endLine":142,"score":1},{"path":"memory/2026-04-30.md","startLine":177,"endLine":205,"score":1},{"path":"memory/2026-04-30.md","startLine":198,"endLine":224,"score":1},{"path":"memory/2026-04-30.md","startLine":134,"endLine":166,"score":1},{"path":"memory/2026-04-30.md","startLine":73,"endLine":102,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-07T03:19:39.321Z","query":"小葵小葵群 chat_id 发消息 bot","resultCount":1,"results":[{"path":"memory/2026-04-18.md","startLine":1,"endLine":5,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-08T10:25:44.365Z","query":"MySQL database connection credentials lark_group_message","resultCount":4,"results":[{"path":"memory/2026-04-17.md","startLine":1,"endLine":23,"score":1},{"path":"memory/2026-04-23.md","startLine":28,"endLine":55,"score":1},{"path":"memory/2026-04-30.md","startLine":95,"endLine":120,"score":1},{"path":"memory/2026-04-23.md","startLine":47,"endLine":72,"score":1}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 0863336..0797c41 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-05-07T03:19:39.321Z", + "updatedAt": "2026-05-08T10:25:44.365Z", "entries": { "memory:memory/2026-04-18.md:1:5": { "key": "memory:memory/2026-04-18.md:1:5", @@ -244,6 +244,130 @@ "loading慢/数据丢失", "安君仪/毋益飞/王胤鑫" ] + }, + "memory:memory/2026-04-17.md:1:23": { + "key": "memory:memory/2026-04-17.md:1:23", + "path": "memory/2026-04-17.md", + "startLine": 1, + "endLine": 23, + "source": "memory", + "snippet": "# 2026-04-17 工作日志 ## 飞书群消息同步改造(李若松要求) ### 变更内容 - **存储从飞书表格改为 MySQL**:新建 `vala_test.lark_group_message` 表,结构参考 `wechat_group_message` - **同步频率**:从每6小时改为每4小时 - **数据范围**:2026.4.1 起的「内容测试问题反馈」群消息 - **数据库账户**:chatbot(test环境,仅对 lark_group_message 有写入权限) ### 完成事项 1. ✅ 创建 `lark_group_message` 表(唯一键 message_id 防重复) 2. ✅ 编写新同步脚本 `scripts/sync_lark_group_to_mysql.py`(基于原有 sync_group_to_sheet.py 改造) 3. ✅ 首次全量同步完成:172 条记录(2026-04-01 ~ 2026-04-17),含文本134条、图片17条、视频10条、富文本9条、表情2条 4. ✅ crontab 定时任务已替换:旧的每6小时飞书表格同步 → 新的每4小时MySQL同步 5. ✅ 更新 secrets.md 记录 chatbot 账户 6. ✅ 更新 user-feedback-collector SKILL.md 反馈数据源信息 ### 文件变更 - 新增:`scripts/sync_lark_group_to_mysql.py`(核心同步脚本) - 新增:`scripts/run_lark_group_sync.s", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-08T10:25:44.365Z", + "lastRecalledAt": "2026-05-08T10:25:44.365Z", + "queryHashes": [ + "cc0dd7ef50d7" + ], + "recallDays": [ + "2026-05-08" + ], + "conceptTags": [ + "vala-test.lark-group-message", + "wechat-group-message", + "2026.4.1", + "lark-group-message", + "message-id", + "sync-group-to-sheet.py", + "secrets.md", + "user-feedback-collector" + ] + }, + "memory:memory/2026-04-23.md:28:55": { + "key": "memory:memory/2026-04-23.md:28:55", + "path": "memory/2026-04-23.md", + "startLine": 28, + "endLine": 55, + "source": "memory", + "snippet": "- 创建脚本:`scripts/sync_feishu_full_history.py` - 同步时间范围:2026-04-10 至今 - 成功处理 57 条消息(包含文本、图片、视频等多种类型) - 所有媒体文件已上传到 COS - 数据已全部写入数据库 4. **定时任务配置** - 每 4 小时自动同步「内容测试问题反馈」群消息 - crontab: `0 */4 * * * cd /root/.openclaw/workspace-xiaokui/skills/feishu-group-msg-sync/scripts && python3 sync_group_to_mysql.py` 5. **文档更新** - `feishu-group-msg-sync/SKILL.md`: 说明数据库存储方案和引用回复支持 - `user-feedback-collector/SKILL.md`: 更新飞书反馈数据源为 MySQL - `feishu-group-msg-sync/references/query_examples.md`: 提供引用回复查询示例 ### 技术要点 - **引用回复识别**: 从消息 `body.quote` 字段提取被引用消息的 `message_id` - **去重机制**: 使用 `UNIQUE KEY (chat_id, message_id)` 避免重复记录 - **数据一致性**: 使用 `ON DUPLICATE KEY UPDATE` 处理重复消息 - **媒体文", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-08T10:25:44.365Z", + "lastRecalledAt": "2026-05-08T10:25:44.365Z", + "queryHashes": [ + "cc0dd7ef50d7" + ], + "recallDays": [ + "2026-05-08" + ], + "conceptTags": [ + "sync-group-to-mysql.py", + "feishu-group-msg-sync/skill.md", + "user-feedback-collector/skill.md", + "body.quote", + "message-id", + "chat-id", + "创建", + "脚本" + ] + }, + "memory:memory/2026-04-30.md:95:120": { + "key": "memory:memory/2026-04-30.md:95:120", + "path": "memory/2026-04-30.md", + "startLine": 95, + "endLine": 120, + "source": "memory", + "snippet": "| iOS组件无音频+Loading慢/数据丢失 | 胡陈辰→安君仪/毋益飞/王胤鑫 | 11 | | 网络问题(4/29) | Ann | 2 | ### 飞书群 4/25-4/27 数据查询结果 - 查询 MySQL `lark_group_message` 2026-04-25 ~ 2026-04-27 数据 - 结果:0 条,该群此时间段无消息记录 ### 反馈同步 Skill 创建 [刘新玉] 将飞书问题反馈同步流程封装为 `feishu-feedback-sync` skill,并计划注册定时任务。 #### Skill 文件 - `skills/feishu-feedback-sync/SKILL.md` — 完整技能文档 - `skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py` — 核心同步脚本 - `scripts/sync_feishu_feedback_wrapper.sh` — 定时任务包装脚本 #### Skill 功能 1. 从 MySQL `lark_group_message` 查询近 N 天数据 2. 写入知识库电子表格(按天分 sheet) 3. **反馈对话链排序**:按引用关系将同一问题讨论聚合呈现 #### 策略2:推断缺失引用关系 [刘新玉] 问题:很多消息有关联但没有 `quote_message_id`(飞书 API 的 `root_id`/`parent_id` 未采集) **推断规则(按优先级)**: 1. **@提及匹配**:消息中 @了某人", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-08T10:25:44.365Z", + "lastRecalledAt": "2026-05-08T10:25:44.365Z", + "queryHashes": [ + "cc0dd7ef50d7" + ], + "recallDays": [ + "2026-05-08" + ], + "conceptTags": [ + "网络", + "loading慢/数据丢失", + "安君仪/毋益飞/王胤鑫", + "4/29", + "4/25-4/27", + "lark-group-message", + "feishu-feedback-sync", + "quote-message-id" + ] + }, + "memory:memory/2026-04-23.md:47:72": { + "key": "memory:memory/2026-04-23.md:47:72", + "path": "memory/2026-04-23.md", + "startLine": 47, + "endLine": 72, + "source": "memory", + "snippet": "- **数据一致性**: 使用 `ON DUPLICATE KEY UPDATE` 处理重复消息 - **媒体文件处理**: 下载飞书媒体文件并上传到腾讯 COS,记录可访问 URL ### 验证结果 - ✅ 数据库表结构已更新(3 个新字段) - ✅ 历史数据已同步(57 条消息) - ✅ 定时任务已配置 - ✅ 文档已更新 - ℹ️ 当前数据中无引用回复消息(quote_message_id 都是 NULL),说明这段时间内群消息都是直接发送的 ### 查询能力 支持以下查询场景: - 查询某条消息的所有回复 - 查询完整对话链(原消息 + 所有回复) - 统计热门话题(被回复最多的消息) - 按时间、发送人、关键词筛选 ### 文件清单 - `scripts/alter_lark_group_message.sql` - 数据库表结构升级脚本 - `scripts/sync_feishu_full_history.py` - 完整历史数据同步脚本 - `skills/feishu-group-msg-sync/scripts/sync_group_to_mysql.py` - 增量同步脚本 - `skills/feishu-group-msg-sync/references/query_examples.md` - 查询示例文档", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-08T10:25:44.365Z", + "lastRecalledAt": "2026-05-08T10:25:44.365Z", + "queryHashes": [ + "cc0dd7ef50d7" + ], + "recallDays": [ + "2026-05-08" + ], + "conceptTags": [ + "quote-message-id", + "数据", + "一致性", + "使用", + "duplicate", + "key", + "update", + "处理" + ] } } } diff --git a/memory/2026-05-08-1754.md b/memory/2026-05-08-1754.md new file mode 100644 index 0000000..2acecee --- /dev/null +++ b/memory/2026-05-08-1754.md @@ -0,0 +1,40 @@ +# Session: 2026-05-08 17:54:20 GMT+8 + +- **Session Key**: agent:xiaokui:feishu:direct:ou_088ee79216826be4a24af44f7268f880 +- **Session ID**: 6215a8b7-9dcf-436a-afbb-a0f56b70d246 +- **Source**: feishu + +## Conversation Summary + +user: https://makee-interactive.feishu.cn/wiki/Ih0VwOhd4io22Pkm3fGcTHb6nRg?fromScene=spaceOverview&from=navigation 在这节点下 新建一个 "世界" 文档 +assistant: 首先获取目标节点信息,确认知识空间ID和节点类型。 +assistant: 节点是目录,确认可用。现在在其下创建"世界"文档。 +assistant: 已创建完成。新文档"世界"位于"小葵"节点下: + +**链接:** https://makee-interactive.feishu.cn/wiki/TktpwWS9FilRJskvdwBcjnebnog +user: 好的 现在在文档中写入一句 "人生几回伤往事" +assistant: 已写入。"世界"文档中现在有了「人生几回伤往事」。 +user: 好的 现在删除这句话 +assistant: 已删除。"世界"文档中那句话已经没了。 +user: 好 现在删除这个文档 +assistant: 已删除。"世界"文档已从"小葵"节点下移除。 +user: 给我讲个鬼故事 +assistant: 老李是个夜班保安,工作是巡视这座刚建好的写字楼。 + +这天半夜,他按惯例巡到顶层 14 楼。这是个还没装修好的大开间,手电筒的光柱扫过去,只有几根光秃秃的水泥柱子。他转了一圈,正准备顺着安全通道下楼,腰间的对讲机突然响了。 + +是监控室值班的小王,声音听起来有点抖:“李哥,你……你身后那个人是谁啊?大半夜的,怎么还穿件红衣服?” + +老李心里“咯噔”一下,猛地转过身。身后空空荡荡,连只老鼠都没有。 + +他舒了口气,按下对讲机骂道:“小兔崽子,大半夜别开这种玩笑,我这儿哪有人!” + +对讲机那头沉默了几秒钟,随后传来一阵刺耳的静电“嘶嘶”声。紧接着,一个陌生的、沙哑的女人声音从对讲机里传了出来,还带着诡异的笑意: + +“他没开玩笑呀,我确实不在你身后。” + +老李浑身的汗毛全竖了起来。他突然感觉到脖子后面一阵阴冷的风,余光里,一双惨白的手正慢慢从他自己的肩膀上伸出来,指甲上涂着刺眼的红油漆。 + +那女声贴着他的耳朵,轻声说道: + +“我在你背上呢。” diff --git a/memory/2026-05-08-1805.md b/memory/2026-05-08-1805.md new file mode 100644 index 0000000..8a71ca6 --- /dev/null +++ b/memory/2026-05-08-1805.md @@ -0,0 +1,43 @@ +# Session: 2026-05-08 18:05:25 GMT+8 + +- **Session Key**: agent:xiaokui:feishu:direct:ou_9d4df593d0419d705274947c5cec5ada +- **Session ID**: 72de7c24-c2da-49fd-b783-bb82f556f676 +- **Source**: feishu + +## Conversation Summary + +user: lark_group_message 数据中,message_id为om_x100b50ed0ab29080b3efb880aa8d17d,有媒体文件,但https://makee-interactive.feishu.cn/wiki/TVivwmzqXiW3YakDUzucFMRenvf 这个5月8日的文档中没有 +user: 小葵 你在跟我聊天吗 +user: 小葵 你在跟我聊天吗 +assistant: 新玉姐,我在的!刚才的消息可能有延迟。让我来查一下你说的这条数据。 +assistant: 找到这条消息了。让我看看它所属的对话链,以及知识库文档里的内容。 +assistant: 现在让我查看知识库5月8日的文档内容和同步脚本的状态。 +assistant: `TVivwmzqXiW3YakDUzucFMRenvf` 是「内容测试问题反馈」电子表格。让我检查5月8日的数据在里面有没有、以及汇总文档的情况。 +assistant: 有5月8日的sheet!让我读取它的内容,看看这条消息是否在里面。 +assistant: 消息**确实**在5月8日电子表格中(第9行)!让我再看看汇总文档里有没有对应的归纳。 +assistant: 找到根因了!同步脚本在**早上10:00**运行,但这条消息是**下午15:44**才发的——时序倒置。让我确认汇总文档的状态。 +assistant: 现在我完全理 +user: 小葵 你在吗 +assistant: 新玉姐,我在!刚才在处理你前面说的问题。已经查清楚了,给你总结一下: + +--- + +### 关于 `om_x100b50ed0ab29080b3efb880aa8d17d`(李应瑛的图片消息) + +**✅ 数据库里有** — 李应瑛 5月8日 15:44 发的图片(内容:132hotfix测试包卡住的截图) + +**✅ 电子表格(内容测试问题反馈)里也有** — `TVivwmzqXiW3YakDUzucFMRenvf` 的 `2026-05-08` sheet 第9行,有媒体URL + +**⚠️ 但为什么你可能没看到?** 有两个原因: + +1. **对话链排序后位置变了** — 排序算法把这条图片消息排到了对话链末尾(第9行),而不是按时间顺序排在第2行。图片被放在了整段对话的最后。 + +2. **汇总文档刚才生成** — `2026-05-08 问题反馈` 子文档在 18:03 才跑完(数据也是下午才发的,早上10点定时任务跑时还没这些数据)。汇总里只归纳了文字"卡在如上界面",没有单独提图片。 + +--- + +要我帮你**重新跑一次同步**,让排序和归纳更完整吗?或者直接看原始对话链也可以。 +user: [assistant turn failed before producing content] 这句话什么意思 翻译成中文 +assistant: 新玉姐,不好意思——你上一条消息后面我的回复生成失败了(显示 `assistant turn failed`),所以我没看到你说的是哪句话。 + +能再发一下那段文字吗?我帮你翻译成中文。 diff --git a/memory/2026-05-08.md b/memory/2026-05-08.md new file mode 100644 index 0000000..58175e2 --- /dev/null +++ b/memory/2026-05-08.md @@ -0,0 +1,37 @@ +# 2026-05-08 工作日志 + +## 凭证审计与修复 + +- 全面审计 xiaokui 工作区所有脚本的飞书凭证引用,修复 10+ 处 `xiaoyan` → `xiaokui` 错误凭证 +- 修复 crontab 分组错误:4 个 xiaokui 任务被误放在 xiaoyan 注释块下 +- 核心文件:`skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py`、`scripts/sync_wechat_feedback_minutely.sh/py`、`scripts/sync_feedback_minutely.sh`、`scripts/sync_group_to_sheet.py`、`scripts/daily_feedback_report.sh` + +## 对话链排序 bug 修复 + +- **根因**:`try_merge_orphan_clusters()` 中 `big_cluster_ids.remove(best_cid)` 行导致大簇吸收一个孤儿后被移除候选列表,后续孤儿无法合并 +- **影响**:孤立消息(同发送者连续发言、无引用关系)被排到对话链末尾,对话链不完整 +- **修复**:删除该行,大簇可吸收多个孤儿 +- **验证**:5月8日李应瑛 "132hotfix的测试包打不开" → 图片 → "卡在如上界面" 三条消息现在正确按时间排列 + +## 问题描述症状关键词扩展 + +扩展了 `generate_problem_description()` 的症状检测模式: + +| 新增模式 | 关键词 | 描述生成 | +|---------|--------|---------| +| 打不开/无法进入 | 打不开、无法进入、进不去、启动失败 | 提取目标(测试包/APP)+ 版本号 | +| 卡在界面 | 卡在、卡住、停在.*界面、无响应 | 提取卡住的具体界面名 | +| 版本号异常 | 版本.*不对、还是.*版本、显示.*版本 | 提取新旧版本号对比 | +| 测试包/热更 | 测试包、hotfix、热更.*失败、打包.*不了 | 测试包/热更包无法获取 | + +同时修复: +- location_str 构建时过滤 "未知" 占位符 +- 兜底文本清洗:去除 @提及、URL、emoji([流泪]等)、在句中标点处智能截断 + +## 对话信息输出逻辑说明(给刘新玉) + +汇总文档中"对话信息"列: +- 消息顺序 = sort_threads() 排序结果(根消息按时间 + 递归展开子回复) +- 🚩 报告 = cluster_msgs[0] 的发送者首次出现 +- ✅ = cluster_msgs[-1] 的发送者 + 最后一条 +- 文本截断 80 字符、媒体追加 📎 链接、纯图显示 [图片] diff --git a/output/daily_feedback/飞书反馈_2026-05-08.xlsx b/output/daily_feedback/飞书反馈_2026-05-08.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..625206091429f699b83653a2f34be82182d2b695 GIT binary patch literal 6730 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 XrK5p`+Cl&T@KC2TYKlLl{Pp%fK9LAG literal 0 HcmV?d00001 diff --git a/scripts/daily_feedback_report.sh b/scripts/daily_feedback_report.sh index f657f07..ef696b5 100755 --- a/scripts/daily_feedback_report.sh +++ b/scripts/daily_feedback_report.sh @@ -37,8 +37,8 @@ else fi # 4. 发送飞书消息(Bot身份) -APP_ID=$(jq -r '.apps[0].appId' /root/.openclaw/credentials/xiaoyan/config.json) -APP_SECRET=$(jq -r '.apps[0].appSecret' /root/.openclaw/credentials/xiaoyan/config.json) +APP_ID=$(jq -r '.apps[0].appId' /root/.openclaw/credentials/xiaokui/config.json) +APP_SECRET=$(jq -r '.apps[0].appSecret' /root/.openclaw/credentials/xiaokui/config.json) TOKEN=$(curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \ -H "Content-Type: application/json" \ -d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" \ diff --git a/scripts/sync_feedback_minutely.sh b/scripts/sync_feedback_minutely.sh index d3e6a51..c120c13 100755 --- a/scripts/sync_feedback_minutely.sh +++ b/scripts/sync_feedback_minutely.sh @@ -27,7 +27,7 @@ log "=== 同步任务开始 ===" # 1. 读取源表格所有数据 log "读取源表格数据..." -SOURCE_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +read --spreadsheet-token "$SOURCE_SPREADSHEET_TOKEN" --range "$SOURCE_SHEET_ID!A:D" --as bot) +SOURCE_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +read --spreadsheet-token "$SOURCE_SPREADSHEET_TOKEN" --range "$SOURCE_SHEET_ID!A:D" --as bot) SOURCE_DATA=$(echo "$SOURCE_RAW" | jq -r '.data.valueRange.values | .[1:] | map(select(.[0] != null and .[0] != ""))') if [ -z "$SOURCE_DATA" ] || [ "$SOURCE_DATA" == "null" ]; then @@ -72,7 +72,7 @@ log "共获取到 $ROW_COUNT 条反馈记录,按时间倒序排列完成" # 3. 清空目标表格原有数据(保留表头) log "清空目标表格原有数据..." EMPTY_DATA=$(printf '[]%.0s' {1..200} | jq -s '.') -LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +write \ --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ --range "$TARGET_SHEET_ID!A2:O201" \ --values "$EMPTY_DATA" \ @@ -83,7 +83,7 @@ echo "$SORTED_DATA" > "$TMP_DATA" END_ROW=$((ROW_COUNT + 1)) log "写入数据到目标表格..." -LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +write \ --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ --range "$TARGET_SHEET_ID!A2:O$END_ROW" \ --values "$(cat $TMP_DATA)" \ diff --git a/scripts/sync_feishu_feedback_wrapper.sh b/scripts/sync_feishu_feedback_wrapper.sh index f6c3730..4a25abe 100755 --- a/scripts/sync_feishu_feedback_wrapper.sh +++ b/scripts/sync_feishu_feedback_wrapper.sh @@ -14,8 +14,11 @@ log() { log "=== 每日全量分发开始 ===" +# 计算昨天的日期,分发昨天的全量问题 +YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) + cd /root/.openclaw/workspace-xiaokui -python3 skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py --days 1 --steps 7 --dispatch-mode all >> "$LOG_FILE" 2>&1 +python3 skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py --date "$YESTERDAY" --steps 7 --dispatch-mode all >> "$LOG_FILE" 2>&1 log "=== 每日全量分发结束 ===" exit 0 diff --git a/scripts/sync_group_to_sheet.py b/scripts/sync_group_to_sheet.py index ed9febd..6079505 100755 --- a/scripts/sync_group_to_sheet.py +++ b/scripts/sync_group_to_sheet.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone, timedelta CHAT_ID = "oc_fabff7672e62a9ced7b326ee4a286c26" # 内容测试问题反馈群ID SPREADSHEET_TOKEN = "E8vFsCmPBhT4SCtNmnJchqeJnJe" # 目标电子表格token SHEET_ID = "7bce8f" # sheet页ID -LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaoyan" # Bot凭证目录 +LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaokui" # Bot凭证目录 LAST_SYNC_FILE = "/tmp/last_feedback_sync_time" COS_BASE_PATH = "feedback" # COS存储路径前缀 diff --git a/scripts/sync_wechat_feedback_minutely.py b/scripts/sync_wechat_feedback_minutely.py index de53076..2cc6d79 100644 --- a/scripts/sync_wechat_feedback_minutely.py +++ b/scripts/sync_wechat_feedback_minutely.py @@ -13,7 +13,7 @@ LOG_FILE = "/var/log/sync_wechat_feedback.log" LAST_SYNC_ID_FILE = "/root/.openclaw/workspace-xiaokui/data/last_wechat_sync_id" TARGET_SPREADSHEET_TOKEN = "AOxbsifk3hybRZteGowcMxNnnqc" TARGET_SHEET_ID = "f17380" -LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaoyan" +LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaokui" # 数据库配置 DB_CONFIG = { diff --git a/scripts/sync_wechat_feedback_minutely.sh b/scripts/sync_wechat_feedback_minutely.sh index 3bfce25..b9715c9 100755 --- a/scripts/sync_wechat_feedback_minutely.sh +++ b/scripts/sync_wechat_feedback_minutely.sh @@ -96,7 +96,7 @@ log "发现 $NEW_COUNT 条新增微信反馈数据,最大ID:$NEW_MAX_ID" # 3. 读取目标表格现有数据(飞书+微信) log "读取目标表格现有数据..." -TARGET_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +read --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" --range "$TARGET_SHEET_ID!A2:O" --as bot) +TARGET_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +read --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" --range "$TARGET_SHEET_ID!A2:O" --as bot) TARGET_DATA=$(echo "$TARGET_RAW" | jq '.data.valueRange.values | map(select(.[0] != null and .[0] != ""))') if [ "$TARGET_DATA" == "null" ] || [ -z "$TARGET_DATA" ]; then @@ -120,14 +120,14 @@ END_ROW=$((ROW_COUNT + 1)) log "写入合并后的数据,共 $ROW_COUNT 条..." # 先清空原有数据 EMPTY_DATA=$(printf '[]%.0s' {1..200} | jq -s '.') -LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +write \ --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ --range "$TARGET_SHEET_ID!A2:O201" \ --values "$EMPTY_DATA" \ --as bot >/dev/null 2>&1 # 写入新数据 -LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaokui lark-cli sheets +write \ --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ --range "$TARGET_SHEET_ID!A2:O$END_ROW" \ --values "$(cat $TMP_DATA)" \ diff --git a/skills/feishu-feedback-sync/scripts/__pycache__/sync_feishu_feedback.cpython-312.pyc b/skills/feishu-feedback-sync/scripts/__pycache__/sync_feishu_feedback.cpython-312.pyc index 20d9dd1ebef8cce1c3f4f0a47c3b63ff3e950561..c0cb53da84beeda670a80aabcd4f6271957d6ed5 100644 GIT binary patch delta 24804 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 delta 8529 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 50: - summary = summary[:47] + "..." + # 清洗文本:去除 @ 提及、URL、媒体标记、emoji、多余空白 + summary = root_text if root_text else all_text + summary = re.sub(r'@\S+', '', summary) # 去 @ 提及 + summary = re.sub(r'https?://\S+', '', summary) # 去 URL + summary = re.sub(r'\[Image:[^\]]+\]', '', summary) # 去图片标记 + summary = re.sub(r'\[流泪\]|\[笑哭\]|\[捂脸\]|\[旺柴\]|\[加油\]', '', summary) # 去常见 emoji + summary = re.sub(r'\s+', ' ', summary).strip() # 合并空白 + if not summary: + summary = "未知问题" + elif len(summary) > 60: + # 在合适位置截断:优先在标点处断开 + truncated = summary[:60] + break_point = max(truncated.rfind(','), truncated.rfind('。'), truncated.rfind('?'), truncated.rfind('!')) + if break_point > 20: + summary = truncated[:break_point] + "等" + else: + summary = truncated[:57] + "..." symptoms.append(summary) symptom_str = "".join(symptoms) @@ -585,8 +672,10 @@ def summarize_cluster(cluster_msgs, idx, priority_info=None): def clean_text(t): if not t: return "" + t = t.replace('\n', ' ').replace('\r', ' ') # 去换行,防止破坏表格 t = re.sub(r'\[Image:[^\]]+\]', '', t) t = re.sub(r'https?://\S+', '', t) + t = re.sub(r'\s+', ' ', t) # 合并多余空白 return t.strip() # 收集关键信息供 AI 归纳 @@ -636,7 +725,11 @@ def summarize_cluster(cluster_msgs, idx, priority_info=None): text = text[:77] + "..." info_parts.append(text) if media_url: - info_parts.append(f"📎 [文件]({media_url})") + # 图片格式直接用图片标签,飞书可点击预览;其他文件保留链接格式 + if media_url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): + info_parts.append(f"![图片]({media_url})") + else: + info_parts.append(f"📎 [文件]({media_url})") if not info_parts: info_parts.append("[图片]") dialogue_info = " | ".join(info_parts) @@ -780,7 +873,8 @@ def generate_summary(clusters, cluster_order, skip_priority=False): idx = 0 for vc in valid_clusters: idx += 1 - summary = summarize_cluster(vc["msgs"], idx, vc.get("priority_info")) + sorted_msgs = sort_cluster_msgs(vc["msgs"]) + summary = summarize_cluster(sorted_msgs, idx, vc.get("priority_info")) lines.append(summary) lines.append("") @@ -796,7 +890,7 @@ def get_tenant_token(cred_dir=None): """获取 Bot 的 tenant_access_token。cred_dir 可选指定凭证目录。""" import urllib.request if cred_dir is None: - cred_dir = os.environ.get("LARKSUITE_CLI_CONFIG_DIR", "/root/.openclaw/credentials/xiaoyan") + cred_dir = os.environ.get("LARKSUITE_CLI_CONFIG_DIR", "/root/.openclaw/credentials/xiaokui") config = json.load(open(os.path.join(cred_dir, "config.json"))) app_id = config["apps"][0]["appId"] app_secret = config["apps"][0]["appSecret"] @@ -836,14 +930,26 @@ def list_child_nodes(): def create_child_doc(title): """在「用户反馈问题汇总」下创建子文档,返回 obj_token""" - import urllib.request + import urllib.request, time token = get_tenant_token() url = f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{SUMMARY_SPACE_ID}/nodes" + + # 提取标题中的日期,生成sort_tag(时间戳越大排序越靠前) + sort_tag = int(time.time()) # 默认当前时间 + try: + # 标题格式如:2026-05-08 问题反馈 + date_str = title.split(" ")[0] + dt = datetime.strptime(date_str, "%Y-%m-%d") + sort_tag = int(dt.timestamp()) + except: + pass + body = json.dumps({ "obj_type": "docx", "node_type": "origin", "parent_node_token": SUMMARY_PARENT_NODE, - "title": title + "title": title, + "sort_tag": sort_tag }).encode() req = urllib.request.Request(url, data=body, headers={ "Authorization": f"Bearer {token}", @@ -857,10 +963,10 @@ def create_child_doc(title): return None -def update_summary_doc_as_children(day_sections): +def update_summary_doc_as_children(day_summaries): """ 将各日期的归纳结果写入「用户反馈问题汇总」的子文档中。 - day_sections: dict, key=日期字符串(如'2026-05-06'), value=该日的归纳 markdown + day_summaries: dict, key=日期字符串(如'2026-05-06'), value={'summary': str, 'has_p0': bool} 逻辑: 1. 列出已有子文档 @@ -870,29 +976,34 @@ def update_summary_doc_as_children(day_sections): env = get_env() existing_nodes = list_child_nodes() - for day in sorted(day_sections.keys(), reverse=True): + # 文件锁:防止多进程并发创建同一天的子文档 + lock_path = "/tmp/xiaokui_summary_create.lock" + + for day in sorted(day_summaries.keys(), reverse=True): title = f"{day} 问题反馈" - content = day_sections[day] + content = day_summaries[day] - # 双重检查:重新列一次子节点,避免并发创建重复 - existing_nodes = list_child_nodes() - if title in existing_nodes: - obj_token = existing_nodes[title]["obj_token"] - print(f" 📝 更新子文档: {title}") - else: - obj_token = create_child_doc(title) - if not obj_token: - # 创建失败可能是并发冲突,再查一次 - existing_nodes = list_child_nodes() - if title in existing_nodes: - obj_token = existing_nodes[title]["obj_token"] - print(f" 📝 已存在,使用已有子文档: {title}") - else: - continue + # 加锁:整个检查+创建+写入流程串行化,彻底避免并发冲突 + with open(lock_path, 'w') as lock_f: + fcntl.flock(lock_f, fcntl.LOCK_EX) + existing_nodes = list_child_nodes() + if title in existing_nodes: + obj_token = existing_nodes[title]["obj_token"] + print(f" 📝 更新子文档: {title}") else: - print(f" ➕ 创建子文档: {title}") + obj_token = create_child_doc(title) + if obj_token: + print(f" ➕ 创建子文档: {title}") + else: + # 创建失败可能是并发冲突,再查一次 + existing_nodes = list_child_nodes() + if title in existing_nodes: + obj_token = existing_nodes[title]["obj_token"] + print(f" 📝 已存在,使用已有子文档: {title}") + else: + continue - # 写入内容 + # 写入内容(锁内已保证obj_token唯一,不会并发写入) result = subprocess.run( [CLI, "docs", "+update", "--doc", obj_token, "--as", "bot", "--mode", "overwrite", @@ -904,139 +1015,52 @@ def update_summary_doc_as_children(day_sections): d = json.loads(result.stdout) if d.get("ok"): print(f" ✅ {title} 写入成功") + + # 重点校验:防止内容重复/空文档 + check_pass = False + for retry in range(2): # 最多重试2次 + check_result = subprocess.run( + [CLI, "docs", "+fetch", "--doc", obj_token, "--as", "bot"], + env=env, capture_output=True, text=True, timeout=10 + ) + try: + check_data = json.loads(check_result.stdout) + saved_content = check_data['data']['markdown'] + dup_count = saved_content.count('## 今日问题归纳') + + if dup_count > 1: + print(f" ⚠️ 检测到内容重复(共{dup_count}次),第{retry+1}次重试写入") + subprocess.run( + [CLI, "docs", "+update", "--doc", obj_token, "--as", "bot", + "--mode", "overwrite", + "--markdown", content], + env=env, capture_output=True, text=True, timeout=15 + ) + elif len(saved_content.strip()) < 100: + print(f" ⚠️ 检测到文档为空/内容过短,第{retry+1}次重试写入") + subprocess.run( + [CLI, "docs", "+update", "--doc", obj_token, "--as", "bot", + "--mode", "overwrite", + "--markdown", content], + env=env, capture_output=True, text=True, timeout=15 + ) + else: + print(f" ✅ {title} 校验通过:长度{len(saved_content)},无内容重复") + check_pass = True + break + except Exception as e: + print(f" ⚠️ 校验出错,重试: {str(e)[:80]}") + if not check_pass: + print(f" ❌ {title} 多次重试后仍校验失败") else: print(f" ⚠️ {title} 写入失败: {result.stdout[:100]}") except Exception: print(f" ⚠️ {title} 写入异常: {result.stderr[:100]}") - print(f" 📂 共处理 {len(day_sections)} 个日期的子文档") + print(f" 📂 共处理 {len(day_summaries)} 个日期的子文档") return True -def main(): - parser = argparse.ArgumentParser(description="飞书问题反馈同步(五步完整流程)") - parser.add_argument("--days", type=int, default=3, help="同步最近 N 天(默认 3)") - parser.add_argument("--dry-run", action="store_true", help="仅预览不写入") - parser.add_argument("--skip-priority", action="store_true", help="跳过步骤5优先级判定") - parser.add_argument("--skip-dispatch", action="store_true", help="跳过步骤7问题分发") - parser.add_argument("--dispatch-mode", type=str, default="all", choices=["all", "p0"], help="分发模式:all=发全部, p0=仅发P0级问题(默认 all)") - parser.add_argument("--steps", type=str, default="1-7", help="执行步骤范围,如 '1-3'(仅写入表格)或 '4-7'(归纳+分类+分发)") - args = parser.parse_args() - - end_date = date.today() - start_date = end_date - timedelta(days=args.days - 1) - - print(f"📊 查询 {start_date} ~ {end_date} 数据...") - # end_date 是当天不包含,所以用 end_date + 1 天作为上界 - next_day = (end_date + timedelta(days=1)).strftime("%Y-%m-%d") - rows = query_messages(start_date.strftime("%Y-%m-%d"), next_day) - - # 按天分组 - groups = defaultdict(list) - for r in rows: - day = r[6][:10] - groups[day].append(r) - - if not groups: - print("✅ 无新数据,无需同步") - return - - print(f"📋 数据分布:{dict((d, len(ms)) for d, ms in groups.items())}") - - if args.dry_run: - print("\n🔍 Dry run,预览前 5 条:") - for day, msgs in sorted(groups.items()): - print(f"\n [{day}] ({len(msgs)} 条)") - for m in msgs[:3]: - print(f" {m[6]} | {m[1]} | {str(m[3])[:50]}") - return - - existing = get_existing_sheets() - print(f"📑 现有 sheet:{list(existing.keys())}") - - total_written = 0 - day_summaries = {} # 收集所有天的归纳结果 - for day in sorted(groups.keys()): - msgs = groups[day] - - # 获取或创建 sheet - if day in existing: - sheet_id = existing[day] - print(f"\n🔄 [{day}] 已有 sheet {sheet_id},覆盖写入") - else: - print(f"\n➕ [{day}] 创建 sheet") - sheet_id = create_sheet(day) - print(f" 新建 sheet_id = {sheet_id}") - - # 对话链排序(含推断引用) - sorted_msgs, clusters, cluster_order = sort_threads(msgs) - print(f" 排序完成:{len(sorted_msgs)} 条") - - # 根据 --steps 参数决定是否执行步骤4-6 - run_steps = args.steps - do_summary = "4" in run_steps or "5" in run_steps or "6" in run_steps or "7" in run_steps - - if do_summary: - # 步骤 4:问题归纳 - # 步骤 5:优先级判定(默认启用,--skip-priority 可跳过) - summary, has_content = generate_summary(clusters, cluster_order, skip_priority=args.skip_priority) - if has_content: - priority_count = summary.count('优先级:') if not args.skip_priority else 0 - print(f" 归纳完成:{summary.count('### 问题')} 个问题") - if not args.skip_priority: - print(f" 步骤5 优先级判定完成:{priority_count} 个问题已评定优先级并排序") - print(f" (归纳内容见下方)") - print(summary) - day_summaries[day] = summary - else: - print(" ⚠️ 无有效问题簇可归纳") - - # 写入表格(步骤2) - success = write_sheet(sheet_id, sorted_msgs) - if success: - total_written += len(sorted_msgs) - print(f" ✅ 写入 {len(sorted_msgs)} 条") - else: - print(f" ❌ 写入失败") - - # 统一写入「用户反馈问题汇总」文档(增量更新,保留已有日期内容)— 步骤4-6 - if day_summaries and do_summary: - update_summary_doc_as_children(day_summaries) - - # 步骤7:问题分发(发送到群聊 + @相关人) - if do_summary and not args.skip_dispatch: - dispatch_mode = args.dispatch_mode - print(f"\n📨 步骤7:问题分发(模式: {dispatch_mode})...") - for day, info in day_summaries.items(): - # 检查当天归纳中是否有 P0 问题 - has_p0 = "⚠️ P0级" in info["summary"] - if dispatch_mode == "p0" and not has_p0: - print(f" [{day}] 无P0问题,跳过分发") - continue - dispatch_summary_to_chat(day, info["summary"], p0_only=(dispatch_mode == "p0")) - - print(f"\n🎉 同步完成,总计写入 {total_written} 条") - - -if __name__ == "__main__": - main() - - -# ============================================================ -# 步骤 7:问题分发(发送到群聊 + @相关人) -# ============================================================ - -# 分发目标群配置 -DISPATCH_CHAT_ID = "oc_4171a2188f2554522a4309f2d7c27753" # 「小葵小葵」问题反馈群 - -# P0问题默认@的人(user_id) -P0_NOTIFY_USERS = [ - "eggbg21g", # 毋益飞 -] - -# Bot身份: 使用 xiaokui(小葵)的凭证发消息 -DISPATCH_CRED_DIR = "/root/.openclaw/credentials/xiaokui" - def dispatch_summary_to_chat(day_label, summary_text, p0_only=False): """ @@ -1170,3 +1194,76 @@ def dispatch_summary_to_chat(day_label, summary_text, p0_only=False): print(f" ⚠️ 分发异常: {e}") return False +def main(): + parser = argparse.ArgumentParser(description="飞书问题反馈同步(五步完整流程)") + parser.add_argument("--days", type=int, default=3, help="同步最近 N 天(默认 3)") + parser.add_argument("--date", type=str, default=None, help="只处理指定日期(格式 YYYY-MM-DD),覆盖 --days 参数") + parser.add_argument("--dry-run", action="store_true", help="仅预览不写入") + parser.add_argument("--skip-priority", action="store_true", help="跳过步骤5优先级判定") + parser.add_argument("--skip-dispatch", action="store_true", help="跳过步骤7问题分发") + parser.add_argument("--dispatch-mode", type=str, default="all", choices=["all", "p0"], help="分发模式:all=发全部, p0=仅发P0级问题(默认 all)") + parser.add_argument("--steps", type=str, default="1-7", help="执行步骤范围,如 '1-3'(仅写入表格)或 '4-7'(归纳+分类+分发)") + args = parser.parse_args() + + if args.date: + start_date = datetime.strptime(args.date, "%Y-%m-%d").date() + end_date = start_date + else: + end_date = date.today() + start_date = end_date - timedelta(days=args.days - 1) + + print(f"📊 查询 {start_date} ~ {end_date} 数据...") + # end_date 是当天不包含,所以用 end_date + 1 天作为上界 + next_day = (end_date + timedelta(days=1)).strftime("%Y-%m-%d") + rows = query_messages(start_date.strftime("%Y-%m-%d"), next_day) + + # 按天分组 + groups = defaultdict(list) + for r in rows: + day = r[6][:10] + groups[day].append(r) + + if not groups: + print("✅ 无新数据,无需同步") + return + + print(f"📋 数据分布:{dict((d, len(ms)) for d, ms in groups.items())}") + + if args.dry_run: + print("\n🔍 Dry run,预览前 5 条:") + for day, msgs in sorted(groups.items()): + print(f"\n [{day}] ({len(msgs)} 条)") + for m in msgs[:3]: + print(f" {m[6]} | {m[1]} | {str(m[3])[:50]}") + return + + existing = get_existing_sheets() + print(f"📑 现有 sheet:{list(existing.keys())}") + + total_written = 0 + day_summaries = {} # 收集所有天的归纳结果 + for day in sorted(groups.keys()): + msgs = groups[day] + + # 获取或创建 sheet + if day in existing: + sheet_id = existing[day] + print(f"\n🔄 [{day}] 已有 sheet {sheet_id},覆盖写入") + else: + print(f"\n➕ [{day}] 创建 sheet") + sheet_id = create_sheet(day) + print(f" 新建 sheet_id = {sheet_id}") + + # 对话链排序(含推断引用) + sorted_msgs, clusters, cluster_order = sort_threads(msgs) + print(f" 排序完成:{len(sorted_msgs)} 条") + + # 根据 --steps 参数决定是否执行步骤4-6 + run_steps = args.steps + do_summary = "4" in run_steps or "5" in run_steps or "6" in run_steps or "7" in run_steps + + if do_summary: + # 步骤 4:问题归纳 + +if __name__ == "__main__": +main()