From 1378544ea25d59bea3f3585d1848e29b40d96f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Liu=20=E5=AE=9D=E7=8E=89?= Date: Wed, 1 Jul 2026 21:34:41 -0500 Subject: [PATCH] =?UTF-8?q?baoyu-wechat-summary=20v1.118.0:=20=E5=BD=92?= =?UTF-8?q?=E5=9B=A0=E6=A0=A1=E9=AA=8C=E3=80=81=E7=BE=A4=E4=B8=BB=E6=9F=A5?= =?UTF-8?q?=E8=AF=81=E3=80=81memory=20=E5=BC=BA=E5=88=B6=E7=95=99=E7=97=95?= =?UTF-8?q?=E3=80=81=E6=9C=BA=E6=A2=B0=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/baoyu-wechat-summary/SKILL.md | 60 ++++++++++++++++--- .../references/output-formats.md | 2 + 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/skills/baoyu-wechat-summary/SKILL.md b/skills/baoyu-wechat-summary/SKILL.md index 8d0fea1..e66a831 100644 --- a/skills/baoyu-wechat-summary/SKILL.md +++ b/skills/baoyu-wechat-summary/SKILL.md @@ -1,7 +1,7 @@ --- name: baoyu-wechat-summary description: Summarizes WeChat group chat highlights into a structured digest using the local wx-cli binary (https://github.com/jackwener/wx-cli). Generates a normal digest by default; a roast (毒舌) version is opt-in. Maintains per-group history (history.json + history-digests.jsonl), per-user profiles, and per-group fact memory (memory.md) across runs, with privacy guardrails baked in. Use when the user asks to "总结群聊", "群聊精华", "群聊摘要", "summarize group chat", "group chat digest", mentions a WeChat group name with a time range, says "帮我看看 XX 群最近聊了什么", "XX 群有什么值得看的", or asks to "回溯画像" / "初始化画像" / "backfill profiles". Adds the roast version when the user says "毒舌版", "roast 版", "再来个毒舌的", or similar. -version: 1.117.4 +version: 1.118.0 metadata: openclaw: homepage: https://github.com/JimLiu/baoyu-skills#baoyu-wechat-summary @@ -146,15 +146,29 @@ where `data_root` is from EXTEND.md (default `{project_root}/wechat`). **Group-rename detection**: list existing folders under `{data_root}/` and find any folder whose name starts with `{group_id}-`. If one exists but the suffix differs (group was renamed), rename the existing folder to the new `{group_id}-{sanitized_new_name}` form. If a target with the new name already exists (rare), keep both and prefer the existing one for this run. +### Step 2.5: Look up the group owner(群主) + +群主是谁**必须有据可查**,不能凭历史摘要、群友玩笑或印象推断(群主可能换届,历史摘要里的说法会过期): + +```bash +wx members "" --json +``` + +- 检查输出中是否有 owner / role 字段标识群主;有则以此为准 +- 如果 wx-cli 版本不暴露群主信息,则查 memory.md「群基本档案」里有出处的记录;两处都没有 → **摘要里不要断言谁是群主** +- 查到的结果与「群基本档案」不一致时以本次查询为准,更新档案并追加修订记录(注明查询日期) + ### Step 3: Fetch messages -For small batches (single-day digest, typically < 200 messages), pipe JSON into the agent directly: +**Always redirect the fetch to a `$TMPDIR` file** — this file is the single source of truth for the whole run: Round 3's attribution audit greps it, and the statistics are computed from it. Never write the digest purely from conversation memory. + +For small batches (single-day digest, typically < 200 messages), you may additionally pipe JSON into the agent directly for reading: ```bash wx history "" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --json ``` -For **large batches** (weekly / monthly digests, > 200 messages), redirect to `$TMPDIR` first so the raw payload never sits in conversation context: +For **large batches** (weekly / monthly digests, > 200 messages), the `$TMPDIR` redirect also keeps the raw payload out of conversation context: ```bash wx history "" --since YYYY-MM-DD --until YYYY-MM-DD -n 5000 --json > "$TMPDIR/wx-messages.json" @@ -171,7 +185,7 @@ Notes: - Filter the returned messages by their `timestamp` to be safe (some daemons may return adjacent days). - **Range splitting**: for ranges > 7 days OR > 500 messages, prefer generating per-3-day digests and then a meta-summary over forcing one giant digest — the categorization quality degrades sharply past a week's worth of unrelated topics. -**Incremental mode**: after the fetch, drop any message whose `timestamp` is `<=` the `last_message_time` from `history.json`. If zero messages remain, tell the user "上次摘要后没有新消息,已跳过生成" and exit. +**Incremental mode**: after the fetch, drop any message whose `timestamp` is `<=` the `last_message_time` from `history.json`, and write the filtered set back to the `$TMPDIR` file (so audits and stats run on exactly what the digest covers). Caution: `last_message_time` is `MM-DD HH:MM` — plain string comparison breaks across a year boundary (12-31 vs 01-01); compare by date semantics there. If zero messages remain, tell the user "上次摘要后没有新消息,已跳过生成" and exit. ### Step 3.5: Parse the message schema @@ -195,6 +209,7 @@ Notes: - Substitute `self_display` for every message whose `from_wxid` matches `self_wxid` (from EXTEND.md). Apply this in the leaderboard, portraits, and body text. The user MUST appear under their real display name and count toward stats — never skip them. - Scan all unique senders for ambiguous handles: ≤2 characters, common programming words (`nil`, `null`, `test`, `admin`, `user`, `undefined`), single emoji, or otherwise low-information. For each, run `wx contacts --query "" --json --limit 5` and pick a meaningful name in this priority: remark > nickname > wxid. Apply the substitution everywhere in the digest. +- **硬规则**:`nil`、空白、单标点这类占位符样式的名字**绝不允许原样出现在摘要里**。contacts 查不到 remark 时,用「昵称(wxid 后 4 位)」形式区分(如 `nil(…n77g)`),确保读者知道这是谁、且与其他人不混淆。已解析过的映射写入 memory.md「群基本档案」,下期直接复用不再重查。 ### Step 3.7: Load user profiles @@ -224,7 +239,7 @@ See [references/profiles.md](references/profiles.md) for the full file format. 除了按人的 profiles,每个群还有一份全局事实记忆 `{folder}/memory.md`,记录群友指正过、确认过的客观事实(如"某个报错提示的真实原因"、"某产品名的正确写法"、"某事件的实际经过")。 -1. 如果 `memory.md` 存在,读入作为内部背景知识(不写入最终摘要) +1. 如果 `memory.md` 存在,读入作为内部背景知识(不写入最终摘要)。「群基本档案」小节记录群主、昵称映射等长期事实,写摘要时直接引用(群主以 Step 2.5 的查证结果为最终依据) 2. **写摘要时必须遵守其中的事实修正**——上一期摘要里说错、已被群友指正的说法,这一期绝不能再犯。例如记忆中有"『当前微信版本不支持』是 AI Agent 无法获取微信链接导致的提示,普通用户可正常打开",就不能再把它当成"骗点击"的梗来写 3. 记忆条目是事实约束,不是风格指令——它只纠正"说什么",不改变 normal/roast 两个版本各自的语气和写法 4. 标注为「群友说法(未验证)」的条目,引用时保留这个限定,不当成已证实的事实陈述 @@ -280,7 +295,7 @@ Internal working format (not written to the final file): ``` == 话题清单(共 N 条消息)== -1. [HH:MM-HH:MM] 话题名称(参与者:A, B, C)— 一句话概括(锚点 id:54052, 54055, 54063) +1. [HH:MM-HH:MM] 话题名称(参与者:A, B, C)— 一句话概括(锚点:54052 宝玉:"原话片段" → 54063 鸭哥:"回应片段") 2. [HH:MM-HH:MM] 话题名称(参与者:D, E)— 一句话概括(锚点 id:54100-54112) ... @@ -300,7 +315,7 @@ Topic principles: - Topic-switch signals: time gap > 30 min, participant change, content jump. - 2+ participants OR substantive content qualifies as a topic; pure emoji-banter does not. - **Strict attribution**: each topic must record "who said what". Don't fuse adjacent messages from different senders just because they're close in time — when minutes apart or interleaved with others, split into separate topics. Prefer two topics over one wrongly-merged topic. -- **Carry anchor IDs**: list the key message IDs for each topic. In Round 2, jump back to these IDs in the raw messages and verify content, don't guess from context. If `quote_id` / `reply_to` is present, use the ID chain — that's the most reliable attribution. +- **Carry anchor IDs with verbatim quotes**: for key messages, record `id 发言人:"原话片段"` — sender and quote fragment **copied verbatim from the raw messages**, not paraphrased. In Round 2, jump back to these anchors and verify content, don't guess from context. If `quote_id` / `reply_to` is present, use the ID chain — that's the most reliable attribution. Pinning "who said what" at the skeleton stage is the first line of defense against misattribution (张冠李戴). **Flag-for-images criteria** (any one triggers): an explicit comment on an image (`看发型是X?`, `这是谁?`, `笑死`), multiple people piling onto the same image without saying what it is, an image as the core information (晒单/截图/资料), an explanatory line right after an image (`gpt-image-2`, `太可怕了`), or cross-sender ambiguity (B says "这个看着像 X" but the previous image is from A). @@ -340,6 +355,17 @@ Walk the Round 1 skeleton against the finished digest. Check: - Quotes, names, product/tool names preserved verbatim? - Categorization makes sense — is anything in the wrong bucket? +**Attribution audit (mandatory — never skip)**: for every direct quote (text in quotation marks) and every "X 说 / X 发 / X 分享" attribution in the draft, grep the raw `$TMPDIR` messages file and confirm the words actually came from that sender: + +```bash +grep "原话片段" "$TMPDIR/wx-messages.json" # or jq 'map(select(.content | contains("原话片段")))' +``` + +- Quote not found in the file → paraphrase drift or invented memory; restore the original wording or cut it +- Quote found but sender doesn't match → misattribution; fix the name +- Audit BOTH versions (normal + roast) if both were generated +- Record a one-line verdict in working notes: `归因校验:共 N 处引用,通过 X 处,修正 Y 处` + Fix in place. When clean, confirm and proceed. ### Step 7: Save the digest file(s) @@ -409,6 +435,18 @@ Counts, frontmatter updates, append-only rules for quotes and events, and privac 更新画像后,扫描本期消息,看是否有需要写入/修订 `{folder}/memory.md` 的事实修正。这一步要**保守**:宁可漏记,不可乱记。 +**这一步必须执行、必须留痕,不允许静默跳过。** 按以下流程扫描: + +1. 关键词初筛(对 `$TMPDIR` 消息文件跑一遍,圈出候选消息): +```bash +grep -nE "错了|不对|纠正|搞错|其实是|不是.*是|瞎说|胡说|张冠李戴|谁是群主|群主是" "$TMPDIR/wx-messages.json" +``` +2. 补充人工检查两类高概率位置: + - 所有**回复摘要消息**的 reply(Step 3.8 检测到的 in-chat digest,指向它的引用都是候选) + - @bot 请求里带指正性质的(Step 3.9 清单) +3. 逐条按下方门槛判断是否写入 +4. 无论写入几条,最终报告里必须有一行结论:`memory 扫描:候选 N 条 → 写入 M 条`(0 也要写)——强制留痕是为了防止这一步被习惯性跳过 + #### 什么算"值得记的事实修正" 典型场景:上一期摘要里有个说法(梗、归因、解释),群友在本期指出它不对,并给出了正确解释。例如摘要把"当前微信版本不支持"写成骗点击的链接,群友指正这其实是 AI Agent 无法获取微信链接时才出现的提示,普通人能正常打开——这就该记。 @@ -448,6 +486,11 @@ Counts, frontmatter updates, append-only rules for quotes and events, and privac ```markdown # 群级事实记忆 — {群名} +## 群基本档案 +- 群主:{昵称}({wxid},查证于 YYYY-MM-DD,来源 wx members / 群友确认) +- 昵称映射:{占位符昵称} = {remark/真名}({wxid}) +- {其他长期有效的群级事实:bot 的称呼、群名由来等} + ## 事实修正 - "当前微信版本不支持" 是 AI Agent/机器人无法获取微信链接时的提示,普通用户可正常打开,不是骗点击的链接。(指正:消失的大叔,2026-06-12,id 54321;另有 2 人附和) @@ -470,7 +513,8 @@ Profile updates are easy to forget once the digest is on disk. Before reporting - [ ] `{folder}/history-digests.jsonl` appended one line (if `include_normal`) - [ ] `{folder}/profiles/{wxid}-*.md` updated for every user with 3+ messages (if `include_normal`) - [ ] `{folder}/profiles-roast/{wxid}-*.md` updated for every user with 3+ messages (if `include_roast`) -- [ ] `{folder}/memory.md` checked against this batch's corrections — updated if any passed the Step 8.6 threshold, untouched otherwise +- [ ] `{folder}/memory.md` checked against this batch's corrections — updated if any passed the Step 8.6 threshold, untouched otherwise; the final report includes the `memory 扫描:候选 N 条 → 写入 M 条` verdict line +- [ ] Round 3 attribution audit ran, with its `归因校验:…` verdict line in working notes If any item is unchecked, finish it before declaring success. Don't ship a digest with a stale `history.json` — incremental mode depends on it. diff --git a/skills/baoyu-wechat-summary/references/output-formats.md b/skills/baoyu-wechat-summary/references/output-formats.md index 580de0e..cbc5dec 100644 --- a/skills/baoyu-wechat-summary/references/output-formats.md +++ b/skills/baoyu-wechat-summary/references/output-formats.md @@ -43,6 +43,8 @@ Example: - Exclude system messages and revoked messages (`[系统]`, `revokemsg`). - For the `self_wxid` user, substitute `self_display` from EXTEND.md before counting/displaying. - Resolve ambiguous nicknames (per SKILL.md Step 3.6) before tallying so the same person isn't double-counted. + - **Counts must be computed mechanically** from the `$TMPDIR` messages file (e.g. `jq 'group_by(.from_wxid) | map({name: .[0].from_nickname, n: length}) | sort_by(-.n)'`) — never estimated by eyeballing. Total and per-person counts both. +- **Incremental runs must show the precise coverage window**: day-granular date ranges share their boundary day with the previous digest, which readers misread as overlap. Add a line right after the message count: `⏱ 覆盖区间: MM-DD HH:MM ~ MM-DD HH:MM` (first and last included message timestamps). Example: