feat(xiaohongshu): add notification reply skill with online reply-state checks

This commit is contained in:
2026-03-05 18:33:51 +08:00
parent 8f9fc6d7f7
commit 5ffead5f0a
3 changed files with 1814 additions and 0 deletions
@@ -0,0 +1,332 @@
---
name: xiaohongshu-reply-notifications
description: "Automatically handle unreplied XiaoHongShu (小红书) items in 通知-评论和@ by opening each note detail, collecting full comment context, conditionally liking high-quality comments, and posting contextual replies with strict login gating and online reply-state checks."
---
# Xiaohongshu Reply Notifications
## Workflow
1. Confirm login state and account identity.
2. Open `通知` and switch to `评论和@`.
3. Discover all candidate cards and loop through them (newest to oldest).
4. Enter each note detail page and determine whether the target is unreplied.
5. Evaluate comment quality and conditionally like the target comment.
6. Generate contextual reply text with accuracy-confidence gate.
7. Verify online replied-state for target thread and apply runtime dedupe.
8. Execute reply according to reply mode.
9. Verify result and continue until candidates are exhausted or early-exit triggers.
10. Return compact batch report and per-item details.
## Shared Session Policy
- Use a per-thread Playwright CLI session:
- Auto session policy: `tools/pw` derives one Playwright session per `CODEX_THREAD_ID` (fallback: `PLAYWRIGHT_SESSION_OWNER` or explicit `--session`).
- Use `/Users/xd/java/xhs/tools/pw` as the only browser entrypoint.
- Prefer the default auto session; pass `--session <name>` only when explicit isolation is required.
## Network Preflight Gate (Required)
- Before running this flow, run:
- `tools/pw --help`
- If it fails with network sandbox signatures (`ENOTFOUND`, `EAI_AGAIN`, `ETIMEDOUT`, `ECONNREFUSED`, or `registry.npmjs.org` fetch failure):
- rerun the same command with escalation (`sandbox_permissions=require_escalated`).
- Do not continue the notification-reply flow until preflight succeeds.
Quick run:
```bash
python3 scripts/run_reply_notifications.py \
--mode safe \
--max-items 10
```
Batch live run:
```bash
python3 scripts/run_reply_notifications.py \
--mode live \
--max-items 20 \
--early-stop-non-actionable 20
```
## Reply-State Truth Source
- Use online thread state as source of truth:
- check target `parent_comment_id` in note detail
- expand replies with limits
- if your own account reply is visible, mark `already_replied`
- Do not rely on local persisted “already handled” files.
- Runtime dedupe is memory-only for current run:
- `target_key = note_id|parent_comment_id`
- `dedupe_key = note_id|parent_comment_id|reply_hash`
- only prevents duplicate sends in the same process run.
## 1) Login Gate (Required)
- Open `https://www.xiaohongshu.com` automatically.
- Confirm login by both signals:
- left sidebar has `我`
- `我` links to `/user/profile/...`
- If not logged in:
- stop immediately
- tell user to log in first
- do not continue any reply actions in this turn.
## 2) Enter Notification Scope
- Open `https://www.xiaohongshu.com/notification` (or click left sidebar `通知`).
- Confirm URL contains `/notification`.
- Click `评论和@` tab.
- Never operate in `赞和收藏` or `新增关注` for this skill.
## 3) Discover Candidates (All Unreplied Flow)
Goal: process all unreplied items in `评论和@`.
Loop behavior:
- Scroll notification list to load more cards.
- Continue while new actionable cards are still discovered.
- Stop when no new cards appear in two consecutive scroll rounds.
- Safety cap:
- `max_scan_cards = 300` per run (to avoid endless scrolling).
Early-exit optimization (newest -> oldest list):
- Because `评论和@` is ordered from new to old, allow early stop when old non-actionable cards dominate.
- Maintain `consecutive_old_non_actionable` counter; increment when a card is any of:
- `already_replied`
- `skipped_duplicate` (runtime dedupe hit)
- older than run time window and not actionable
- Reset this counter to `0` whenever an actionable unreplied card is found.
- Trigger early exit when:
- `consecutive_old_non_actionable >= 20`, or
- one full scroll round yields zero actionable cards and all seen cards in that round are old non-actionable cards.
- Always prefer correctness:
- if card time/order looks ambiguous, do not early-exit; continue scanning.
For each discovered card, extract:
- notifier nickname
- event type: `评论了你的笔记` / `回复了你的评论` / `在评论中@了你`
- notifier text snippet
- your original-comment snippet if shown on card
- card timestamp text
- card thumbnail/content entry for opening note detail.
## 4) Enter Note Detail And Collect Context (Required Before Reply)
For each candidate card:
- Open the related note detail from notification card thumbnail/content.
- Collect note context:
- note title/main text
- hashtags
- note author nickname
- Collect thread context from detail comments:
- target commenter nickname and comment text
- target comment timestamp (if visible)
- If event is `回复了你的评论`:
- locate **your original comment** in the detail thread before generating reply.
- Prefer match by current account profile id from sidebar `我 -> /user/profile/...`.
- Fallback: match with the original-comment snippet shown in notification card.
- If event is `在评论中@了你`:
- locate the mentioning comment in detail thread and capture mention sentence.
- Extract target identifiers:
- `note_id`
- `parent_comment_id` (the comment being replied to)
Unreplied decision (required):
- Determine whether current account already replied to this target comment thread.
- If already replied:
- mark `already_replied`
- skip send for this card.
- If not replied:
- continue normal flow.
Hard rule:
- Do not directly reply from notification-list `回复` when detail context is missing.
- Reply should be made after detail context is confirmed.
## 5) Comment Quality And Conditional Like
After detail context is confirmed, evaluate the target comment quality.
Quality is considered `satisfied` when comment is at least one of:
- specific and relevant to the note topic
- constructive question or useful follow-up
- polite and non-spam interaction
Quality is `not_satisfied` when comment is primarily:
- meaningless spam / repeated emojis only
- obvious ad diversion
- abusive or provocative without useful content
Like rule (target comment only):
- if quality is `satisfied`, click like (`赞`) on the target comment row once.
- if already liked, keep current state and do not toggle off.
- if quality is `not_satisfied`, skip like.
Like is independent from reply mode:
- `safe_mode` and `live_mode` can both perform this conditional like action.
## 6) Reply Drafting Rules
- Use short, natural Chinese.
- Prefer one sentence, <= 35 Chinese characters.
- Must be specific to note/comment context, avoid template spam.
- If asked a concrete question and confidence is high, answer directly.
- If context is unclear, ask one short clarifying follow-up.
- Skip and mark `needs_manual_review` for toxic/abusive/high-risk content.
Accuracy-confidence gate (required):
- Before finalizing each reply, assign confidence:
- `high`: question intent is clear and answer can be supported by current note/thread context.
- `medium`: intent is mostly clear, but some details are uncertain.
- `low`: key context is missing/ambiguous, likely to answer inaccurately.
- Default principle:
- if you can answer clearly, answer directly (do not overuse DM handoff).
- If confidence is `medium`:
- provide a cautious but useful direct answer, avoid absolute claims.
- If confidence is `low`:
- prefer private-message handoff reply inviting user to DM you.
DM handoff style:
- concise, polite, no over-commitment
- explicitly state you may explain more accurately in private chat
- example templates:
- `这个细节怕说不准,方便私信我,我给你详细说下。`
- `这块情况有点细,私信我一下,我一对一给你回复更准确。`
- `我先不乱说,私信我我按你的情况详细答你。`
## 7) Online Replied-State Check And Runtime Dedupe
Before final send in `live_mode`, compute:
- `note_id`: from note detail URL or note container attribute.
- `parent_comment_id`: the comment being replied to (from comment row context/network metadata).
- `reply_hash`: SHA-256 of normalized reply text.
Normalization for `reply_hash`:
- trim leading/trailing spaces
- collapse repeated internal spaces
- normalize full-width/half-width punctuation when possible
- remove leading `回复 xxx :` prefix if present
Key format (runtime):
- `target_key = note_id + \"|\" + parent_comment_id`
- `dedupe_key = note_id + \"|\" + parent_comment_id + \"|\" + reply_hash`
Online check rule (required):
- For target `parent_comment_id`, inspect thread replies in detail page.
- If not enough replies are visible, click expand controls:
- `展开` / `更多` / `查看回复` / `全部回复` (with limits).
- Limits:
- `max_expand_rounds` (default `8`)
- `max_scan_replies` (default `200`)
- `max_expand_seconds` (default `20`)
- Decision:
- `already_replied`: my reply is visible in thread -> skip send.
- `not_replied`: checked within limits and no my reply found -> continue.
- `unknown`: cannot determine confidently -> mark `needs_manual_review`, skip auto-send.
Runtime dedupe rule:
- In current run only:
- if `target_key` or `dedupe_key` already seen, mark `skipped_duplicate`.
- otherwise continue.
## 8) Reply Execution
- In note detail comments, click the target comment row's `回复`.
- Confirm bottom editor shows `回复 <nickname>` and quoted context.
- Type the generated reply.
- Reply mode:
- `safe_mode` (default): do not click final `发送`; keep as draft/plan only.
- `live_mode`: click `发送`.
- When user request is explicit batch intent such as `自动回复所有未回复`, treat this as `live_mode`.
## 9) Verify Success (live_mode)
At least one must be observed:
- `评论成功` toast appears, or
- new reply appears in thread, or
- editor exits reply state and send button resets.
If verification fails:
- mark this item as `sent_unknown`
- do not auto-retry automatically in current run.
## 10) Reliability And Recovery
- Re-snapshot after every major step:
- tab switch
- note-detail open/close
- reply-mode change
- On click interception/stale refs:
- wait briefly
- snapshot again
- retry once
- If detail overlay blocks notification operations:
- close detail first, then continue next card.
## 11) Return Report (Batch)
Return compact structured results per processed item:
- note url/title
- note_id
- parent_comment_id
- dedupe_key
- event type
- notifier nickname
- notifier comment
- your original comment (if applicable)
- comment_quality (`satisfied` or `not_satisfied`)
- liked (`yes`, `no`, `already_liked`, `like_failed`)
- generated reply text
- mode (`safe_mode` or `live_mode`)
- status (`replied`, `already_replied`, `planned`, `skipped_duplicate`, `needs_manual_review`, `sent_unknown`, `failed`)
- failure reason if any
Return batch summary:
- total cards scanned
- actionable cards
- unreplied cards detected
- replies sent
- already-replied skipped
- duplicate skipped
- failed count
## Boundaries
- Do not send private messages, follow/unfollow, or modify account settings.
- Do not like unrelated comments; only like the current target comment when quality is `satisfied`.
- Do not mass-reply with repeated text.
- In `safe_mode`, never click final send.
- Stop and notify user if login state is lost or safety/risk prompts appear.
- Do not bypass online replied-state check when state is `unknown`.
## Script
- `scripts/run_reply_notifications.py`
- End-to-end runner with strict login gate, `评论和@` scanning, note-detail context extraction, online replied-state checks with expand limits, runtime dedupe, conditional like, and safe/live reply modes.
- Exit codes:
- `0`: flow completed (including no-actionable cases)
- `1`: execution failure
- `2`: not logged in (requires user login and rerun)
@@ -0,0 +1,7 @@
interface:
display_name: "XHS Reply Notifications"
short_description: "Auto-reply unreplied 评论和@ notifications with context, likes, and online reply-state checks."
default_prompt: "Use $xiaohongshu-reply-notifications with auto per-thread Playwright session via /Users/xd/java/xhs/tools/pw and prefer running python3 scripts/run_reply_notifications.py as the main entrypoint (safe mode by default, live mode only when user explicitly asks to auto-reply all unreplied); execute browser steps only through /Users/xd/java/xhs/tools/pw, enforce strict login gate, scan 通知 > 评论和@ in newest-to-oldest order with early-exit on consecutive old non-actionable items, enter note detail for full context before reply, check online replied-state for each target parent_comment_id with bounded expand rounds before sending, conditionally like high-quality target comments, and apply runtime-only dedupe keys target_key(note_id|parent_comment_id) and dedupe_key(note_id|parent_comment_id|reply_hash)."
policy:
allow_implicit_invocation: true
File diff suppressed because it is too large Load Diff