gangvy 5 месяцев назад
Сommit
f2e2665664

+ 76 - 0
README.md

@@ -0,0 +1,76 @@
+# OpenClaw WeChat Skill
+
+微信智能体的 OpenClaw 技能集合。通过 GEWE API 实现微信消息收发、联系人管理,支持按来源(个人/群聊)分目录存储消息和基础自动应答。
+
+## 目录结构
+
+```
+openclaw-wechat-skill/
+├── __config/
+│   └── wechat-credentials.template.json
+├── wechat/
+│   ├── wechat-check-online/       # 检查微信在线状态
+│   ├── wechat-send-text/          # 发送文字消息
+│   ├── wechat-get-messages/       # 获取消息列表(支持增量)
+│   ├── wechat-get-conversations/  # 获取会话列表
+│   ├── wechat-get-contact-list/   # 获取通讯录列表
+│   └── wechat-get-contact-detail/ # 获取联系人详情
+├── deploy-to-openclaw.ps1
+└── README.md
+```
+
+## 自动应答编排 (Workflow)
+
+```
+workflows/
+├── wechat-auto-reply.workflow.json   # 核心编排定义
+└── pipeline.md                       # 流程图 + 回复策略文档
+```
+
+龙虾通过 workflow 知道自己要做什么:
+
+```
+每10秒轮询:
+1. wechat-check-online           → 确认微信在线
+2. wechat-get-messages(since)    → 增量拉取新消息
+3. 分类消息来源:
+   ├── 个人私聊 (wxid_xxx)       → AI生成个性化回复 → wechat-send-text
+   └── 群聊 (xxx@chatroom)      → 仅被@或关键词触发时回复
+4. 忽略: 系统消息/公众号/表情/语音等
+```
+
+### 回复策略
+- **个人私聊**: 每条文字消息都AI回复,图片/语音回复确认
+- **群聊**: 仅被@或包含关键词("帮我"/"请问")时回复,避免刷屏
+- **忽略列表**: system/emoji/voice + weixin/fmessage/gh_*
+
+详见 [`workflows/pipeline.md`](workflows/pipeline.md)
+
+## 消息存储
+
+后端自动按来源持久化消息到文件:
+- 个人消息: `data/messages/personal/{wxid}.jsonl`
+- 群聊消息: `data/messages/group/{chatroom_id}.jsonl`
+
+## 客户部署(一步到位)
+
+```powershell
+# 预览(不实际部署)
+.\deploy-to-openclaw.ps1 -DryRun
+
+# 正式部署技能 + workflow + 凭证
+.\deploy-to-openclaw.ps1
+```
+
+运行后自动完成:
+- 6个技能 → `~/.openclaw/skills/`
+- 自动应答编排 → `~/.openclaw/workflows/`
+- 凭证配置 → `~/.openclaw/wechat-credentials.json`(已预设后端地址)
+
+**客户无需额外配置**,后端统一托管在 `http://8.138.37.248/api/wechat-agent`。
+
+部署完成后龙虾即可开始工作:轮询消息 → 分类来源 → AI生成回复 → 自动发送。
+
+## 版本
+
+v1.0.0 (2026-04-12) - 初始版本,6个核心技能 + 自动应答编排

+ 4 - 0
__config/wechat-credentials.template.json

@@ -0,0 +1,4 @@
+{
+  "wechatApiBase": "http://8.138.37.248/api/wechat-agent",
+  "_comment": "wechatApiBase: 你的微信Agent后端API地址,每个客户部署不同的地址"
+}

+ 69 - 0
deploy-to-openclaw.ps1

@@ -0,0 +1,69 @@
+param(
+    [string]$SkillsRoot = "$env:USERPROFILE\.openclaw\skills",
+    [string]$SourceRoot = $PSScriptRoot,
+    [switch]$DryRun
+)
+$ErrorActionPreference = "Stop"
+$categories = @("wechat")
+Write-Host "=== OpenClaw WeChat Skill Deploy v1.0.0 ==="
+Write-Host "Source: $SourceRoot"
+Write-Host "Target: $SkillsRoot"
+if ($DryRun) { Write-Host "[DRY RUN]" -ForegroundColor Yellow }
+if (-not $DryRun -and -not (Test-Path $SkillsRoot)) {
+    New-Item -ItemType Directory -Path $SkillsRoot -Force | Out-Null
+}
+$deployed = 0; $skipped = 0; $errors = 0
+foreach ($cat in $categories) {
+    $catPath = Join-Path $SourceRoot $cat
+    if (-not (Test-Path $catPath)) { continue }
+    Write-Host "--- $cat ---" -ForegroundColor Cyan
+    foreach ($skillDir in (Get-ChildItem -Directory -Path $catPath)) {
+        $n = $skillDir.Name
+        $sm = Join-Path $skillDir.FullName "SKILL.md"
+        $ac = Join-Path $skillDir.FullName "api-config.json"
+        if (-not (Test-Path $sm) -or -not (Test-Path $ac)) { $skipped++; continue }
+        try { $null = Get-Content $ac -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $errors++; continue }
+        $dest = Join-Path $SkillsRoot $n
+        if ($DryRun) {
+            Write-Host "  [$( if (Test-Path $dest){'UPDATE'}else{'NEW'} )] $n"
+        } else {
+            if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
+            New-Item -ItemType Directory -Path $dest -Force | Out-Null
+            Get-ChildItem -File -Path $skillDir.FullName | ForEach-Object { Copy-Item $_.FullName -Destination $dest }
+            Write-Host "  [OK] $n" -ForegroundColor Green
+        }
+        $deployed++
+    }
+}
+Write-Host "Result: $deployed deployed, $skipped skipped, $errors errors"
+
+# Deploy workflows
+$wfSource = Join-Path $SourceRoot "workflows"
+$wfTarget = Join-Path (Split-Path $SkillsRoot -Parent) "workflows"
+if (Test-Path $wfSource) {
+    Write-Host "--- workflows ---" -ForegroundColor Cyan
+    if (-not $DryRun) {
+        if (-not (Test-Path $wfTarget)) { New-Item -ItemType Directory -Path $wfTarget -Force | Out-Null }
+        Get-ChildItem -File -Path $wfSource | ForEach-Object {
+            Copy-Item $_.FullName -Destination $wfTarget -Force
+            Write-Host "  [OK] $($_.Name)" -ForegroundColor Green
+        }
+    } else {
+        Get-ChildItem -File -Path $wfSource | ForEach-Object { Write-Host "  [WORKFLOW] $($_.Name)" }
+    }
+}
+
+if (-not $DryRun) {
+    # Copy credentials template
+    $tpl = Join-Path $SourceRoot "__config\wechat-credentials.template.json"
+    $dst = Join-Path (Split-Path $SkillsRoot -Parent) "wechat-credentials.template.json"
+    if (Test-Path $tpl) { Copy-Item $tpl -Destination $dst -Force; Write-Host "Template: $dst" }
+
+    # Create default credentials if not exists
+    $credFile = Join-Path (Split-Path $SkillsRoot -Parent) "wechat-credentials.json"
+    if (-not (Test-Path $credFile)) {
+        Copy-Item $tpl -Destination $credFile -Force
+        Write-Host "[!] Created $credFile - please edit wechatApiBase to your server address" -ForegroundColor Yellow
+    }
+    Write-Host "Done!" -ForegroundColor Green
+}

+ 41 - 0
wechat/wechat-check-online/SKILL.md

@@ -0,0 +1,41 @@
+---
+name: wechat-check-online
+description: 检查微信是否在线。用于确认设备登录状态,是所有操作的前置检查。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-check-online
+
+## 功能用途
+
+检查当前微信账号是否在线,返回在线状态布尔值。是消息收发和联系人操作的前置检查。
+
+**核心业务场景:**
+- 在执行任何微信操作前确认设备在线
+- 定期健康检查,掉线时触发重连
+
+## 入参规则
+
+无需参数(appId 已在后端配置)。
+
+## 接口调用方式
+
+- **请求方法**: POST
+- **接口地址**: `{{wechatApiBase}}/login/check-online`
+
+```json
+{}
+```
+
+## 关键响应字段
+
+| 字段 | 业务用途 |
+|------|---------|
+| ret | 200 表示请求成功 |
+| data | true=在线, false=离线 |
+
+## 依赖要求
+
+- 需要配置 `wechatApiBase`(微信Agent后端地址)
+- 微信账号需先完成扫码登录

+ 60 - 0
wechat/wechat-check-online/api-config.json

@@ -0,0 +1,60 @@
+{
+  "name": "wechat-check-online",
+  "displayName": "微信在线状态检查",
+  "description": "检查微信账号是否在线,返回在线状态。是消息收发和联系人操作的前置检查。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "POST",
+    "url": "{{wechatApiBase}}/login/check-online",
+    "headers": {
+      "Content-Type": "application/json"
+    }
+  },
+  "parameters": {
+    "type": "object",
+    "required": [],
+    "properties": {}
+  },
+  "requestTransform": {
+    "description": "无需参数,发送空 body",
+    "template": {}
+  },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": {
+        "type": "integer",
+        "description": "200 表示请求成功"
+      },
+      "msg": {
+        "type": "string",
+        "description": "操作结果描述"
+      },
+      "data": {
+        "type": "boolean",
+        "description": "true=在线, false=离线"
+      }
+    }
+  },
+  "usageExamples": [
+    {
+      "name": "检查微信在线状态",
+      "input": {},
+      "description": "检查当前微信是否在线,返回 data=true 表示在线"
+    }
+  ],
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 10000,
+  "retry": {
+    "maxAttempts": 2,
+    "delay": 1000,
+    "backoffMultiplier": 2
+  }
+}

+ 39 - 0
wechat/wechat-get-contact-detail/SKILL.md

@@ -0,0 +1,39 @@
+---
+name: wechat-get-contact-detail
+description: 获取微信好友或群聊的详细信息,含昵称、头像、签名、地区等。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-get-contact-detail
+
+## 功能用途
+
+根据wxid批量获取联系人详细信息,包括昵称、头像、签名、地区等。
+
+## 入参规则
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| wxids | string[] | ✅ | wxid数组,支持批量查询 |
+
+## 接口调用方式
+
+- **请求方法**: POST
+- **接口地址**: `{{wechatApiBase}}/contacts/detail`
+
+```json
+{ "wxids": ["wxid_abc123"] }
+```
+
+## 关键响应字段
+
+| 字段 | 说明 |
+|------|------|
+| data[].userName | wxid |
+| data[].nickName | 昵称 |
+| data[].sex | 性别 1=男 2=女 |
+| data[].signature | 个性签名 |
+| data[].province | 省份 |
+| data[].city | 城市 |
+| data[].bigHeadImgUrl | 头像URL |

+ 56 - 0
wechat/wechat-get-contact-detail/api-config.json

@@ -0,0 +1,56 @@
+{
+  "name": "wechat-get-contact-detail",
+  "displayName": "获取联系人详细信息",
+  "description": "根据wxid批量获取联系人详细信息,含昵称、头像、签名、地区等。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "POST",
+    "url": "{{wechatApiBase}}/contacts/detail",
+    "headers": { "Content-Type": "application/json" }
+  },
+  "parameters": {
+    "type": "object",
+    "required": ["wxids"],
+    "properties": {
+      "wxids": {
+        "type": "array",
+        "items": { "type": "string" },
+        "description": "wxid数组,支持批量查询"
+      }
+    }
+  },
+  "requestTransform": {
+    "template": { "wxids": "{{wxids}}" }
+  },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": { "type": "integer" },
+      "data": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "properties": {
+            "userName": { "type": "string", "description": "wxid" },
+            "nickName": { "type": "string", "description": "昵称" },
+            "sex": { "type": "integer", "description": "1=男 2=女" },
+            "signature": { "type": "string" },
+            "province": { "type": "string" },
+            "city": { "type": "string" },
+            "bigHeadImgUrl": { "type": "string", "description": "头像URL" },
+            "alias": { "type": "string", "description": "微信号" }
+          }
+        }
+      }
+    }
+  },
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 15000
+}

+ 29 - 0
wechat/wechat-get-contact-list/SKILL.md

@@ -0,0 +1,29 @@
+---
+name: wechat-get-contact-list
+description: 获取微信通讯录列表,返回好友、群聊、公众号的wxid列表。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-get-contact-list
+
+## 功能用途
+
+获取当前微信账号的完整通讯录,按好友、群聊、公众号分类返回wxid列表。
+
+## 入参规则
+
+无需参数。
+
+## 接口调用方式
+
+- **请求方法**: POST
+- **接口地址**: `{{wechatApiBase}}/contacts/list`
+
+## 关键响应字段
+
+| 字段 | 说明 |
+|------|------|
+| data.friends | 好友wxid数组 |
+| data.chatrooms | 群聊ID数组 |
+| data.ghs | 公众号ID数组 |

+ 36 - 0
wechat/wechat-get-contact-list/api-config.json

@@ -0,0 +1,36 @@
+{
+  "name": "wechat-get-contact-list",
+  "displayName": "获取微信通讯录列表",
+  "description": "获取好友、群聊、公众号的wxid列表。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "POST",
+    "url": "{{wechatApiBase}}/contacts/list",
+    "headers": { "Content-Type": "application/json" }
+  },
+  "parameters": { "type": "object", "required": [], "properties": {} },
+  "requestTransform": { "template": {} },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": { "type": "integer" },
+      "data": {
+        "type": "object",
+        "properties": {
+          "friends": { "type": "array", "description": "好友wxid数组" },
+          "chatrooms": { "type": "array", "description": "群聊ID数组" },
+          "ghs": { "type": "array", "description": "公众号ID数组" }
+        }
+      }
+    }
+  },
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 15000
+}

+ 48 - 0
wechat/wechat-get-conversations/SKILL.md

@@ -0,0 +1,48 @@
+---
+name: wechat-get-conversations
+description: 获取微信会话列表(按联系人分组),包含最近消息摘要。用于发现活跃对话和待回复用户。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-get-conversations
+
+## 功能用途
+
+获取按联系人分组的会话列表,每个会话包含最近一条消息摘要、时间和联系人信息。
+
+**核心业务场景:**
+- 发现有新消息的活跃会话
+- 获取所有待回复的对话
+- 了解当前的沟通全貌
+
+## 调用链(Workflow)
+
+```
+本步: wechat-get-conversations → 获取活跃会话列表
+下游: wechat-get-messages(wxid) → 获取某会话的详细消息
+下游: wechat-send-text(wxid, reply) → 回复某会话
+```
+
+## 入参规则
+
+无需参数。
+
+## 接口调用方式
+
+- **请求方法**: GET
+- **接口地址**: `{{wechatApiBase}}/conversations`
+
+## 关键响应字段
+
+| 字段 | 业务用途 |
+|------|---------|
+| data[].wxid | 联系人wxid → 用于后续查询或回复 |
+| data[].nickName | 联系人昵称 |
+| data[].lastMessage | 最近消息摘要 |
+| data[].lastTime | 最近消息时间(ISO) |
+| data[].lastType | 最近消息类型 |
+
+## 依赖要求
+
+- 需要有消息记录才会返回会话数据

+ 56 - 0
wechat/wechat-get-conversations/api-config.json

@@ -0,0 +1,56 @@
+{
+  "name": "wechat-get-conversations",
+  "displayName": "获取微信会话列表",
+  "description": "获取按联系人分组的会话列表,含最近消息摘要,用于发现活跃对话。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "GET",
+    "url": "{{wechatApiBase}}/conversations",
+    "headers": {}
+  },
+  "parameters": {
+    "type": "object",
+    "required": [],
+    "properties": {}
+  },
+  "requestTransform": {
+    "description": "无需参数"
+  },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": { "type": "integer", "description": "200表示成功" },
+      "data": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "properties": {
+            "wxid": { "type": "string", "description": "联系人wxid" },
+            "nickName": { "type": "string", "description": "联系人昵称" },
+            "lastMessage": { "type": "string", "description": "最近消息摘要" },
+            "lastTime": { "type": "string", "description": "最近消息时间ISO" },
+            "lastType": { "type": "string", "description": "最近消息类型" },
+            "direction": { "type": "string", "description": "最近消息方向" }
+          }
+        }
+      }
+    }
+  },
+  "usageExamples": [
+    {
+      "name": "查看所有活跃会话",
+      "input": {},
+      "description": "获取当前所有有消息记录的会话"
+    }
+  ],
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 10000,
+  "retry": { "maxAttempts": 2, "delay": 1000, "backoffMultiplier": 2 }
+}

+ 67 - 0
wechat/wechat-get-messages/SKILL.md

@@ -0,0 +1,67 @@
+---
+name: wechat-get-messages
+description: 获取微信消息列表,支持按联系人wxid过滤、按时间增量拉取。用于轮询新消息实现自动应答。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-get-messages
+
+## 功能用途
+
+从后端获取微信消息列表,支持按联系人过滤和增量拉取(since 参数),是自动应答流程的数据入口。
+
+**核心业务场景:**
+- 定时轮询新消息,获取用户发来的内容
+- 按联系人 wxid 查询历史对话
+- 增量拉取(通过 since 参数只获取新消息)
+
+## 调用链(Workflow)
+
+```
+本步: wechat-get-messages(since, wxid) → 获取新消息列表
+下游: 解析消息 → wechat-send-text(fromWxid, reply) → 自动回复
+```
+
+## 入参规则
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| limit | integer | ❌ | 返回条数上限,默认50,最大200 |
+| wxid | string | ❌ | 按联系人 wxid 过滤(好友或群聊ID) |
+| direction | string | ❌ | 按方向过滤:sent=已发送,received=已接收 |
+| since | string | ❌ | ISO 时间戳,只返回此时间之后的消息(增量拉取) |
+
+## 接口调用方式
+
+- **请求方法**: GET
+- **接口地址**: `{{wechatApiBase}}/messages?limit=50&wxid=&direction=&since=`
+
+```
+GET {{wechatApiBase}}/messages?limit=20&since=2026-04-11T00:00:00.000Z
+```
+
+## 关键响应字段
+
+| 字段 | 业务用途 |
+|------|---------|
+| data[].id | 消息唯一ID |
+| data[].direction | sent/received,判断消息方向 |
+| data[].fromWxid | 发送者 wxid → 用于回复目标 |
+| data[].toWxid | 接收者 wxid |
+| data[].nickName | 发送者昵称 |
+| data[].type | 消息类型:text/image/voice/link/emoji 等 |
+| data[].content | 消息内容(文字/图片base64/摘要) |
+| data[].timestamp | ISO 时间戳 → 用于下次增量拉取的 since |
+| total | 消息总数 |
+
+## 消息存储说明
+
+后端按来源自动将消息持久化到文件:
+- 个人消息: `data/messages/personal/{wxid}.jsonl`
+- 群聊消息: `data/messages/group/{chatroom_id}.jsonl`
+
+## 依赖要求
+
+- 微信账号需在线且已设置回调地址(`wechat-set-callback`)
+- 需要有消息推送才会有数据

+ 88 - 0
wechat/wechat-get-messages/api-config.json

@@ -0,0 +1,88 @@
+{
+  "name": "wechat-get-messages",
+  "displayName": "获取微信消息列表",
+  "description": "获取微信消息列表,支持按联系人过滤和增量拉取(since),是自动应答的数据入口。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "GET",
+    "url": "{{wechatApiBase}}/messages",
+    "headers": {}
+  },
+  "parameters": {
+    "type": "object",
+    "required": [],
+    "properties": {
+      "limit": {
+        "type": "integer",
+        "description": "返回条数上限,默认50,最大200",
+        "default": 50
+      },
+      "wxid": {
+        "type": "string",
+        "description": "按联系人wxid过滤",
+        "default": ""
+      },
+      "direction": {
+        "type": "string",
+        "description": "按方向过滤:sent或received",
+        "enum": ["sent", "received", ""],
+        "default": ""
+      },
+      "since": {
+        "type": "string",
+        "description": "ISO时间戳,只返回此时间之后的消息",
+        "default": ""
+      }
+    }
+  },
+  "requestTransform": {
+    "description": "参数拼接为query string",
+    "queryParams": {
+      "limit": "{{limit}}",
+      "wxid": "{{wxid}}",
+      "direction": "{{direction}}",
+      "since": "{{since}}"
+    }
+  },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": { "type": "integer", "description": "200表示成功" },
+      "data": {
+        "type": "array",
+        "description": "消息列表(时间倒序)",
+        "items": {
+          "type": "object",
+          "properties": {
+            "id": { "type": "string" },
+            "direction": { "type": "string" },
+            "type": { "type": "string" },
+            "fromWxid": { "type": "string" },
+            "toWxid": { "type": "string" },
+            "nickName": { "type": "string" },
+            "content": { "type": "string" },
+            "timestamp": { "type": "string" }
+          }
+        }
+      },
+      "total": { "type": "integer" }
+    }
+  },
+  "usageExamples": [
+    {
+      "name": "增量拉取新消息",
+      "input": { "since": "2026-04-11T10:00:00.000Z", "direction": "received" },
+      "description": "获取指定时间之后收到的所有新消息"
+    }
+  ],
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 15000,
+  "retry": { "maxAttempts": 2, "delay": 1000, "backoffMultiplier": 2 }
+}

+ 57 - 0
wechat/wechat-send-text/SKILL.md

@@ -0,0 +1,57 @@
+---
+name: wechat-send-text
+description: 向指定微信用户或群聊发送文字消息。支持个人好友和群聊,是自动应答的核心技能。
+version: 1.0.0
+author: wechat-agent
+---
+
+# wechat-send-text
+
+## 功能用途
+
+向指定的微信好友或群聊发送文字消息,是实现自动应答的核心技能。
+
+**核心业务场景:**
+- 收到用户消息后自动回复
+- 主动推送通知给好友或群聊
+- 群聊中 @某人 发送消息
+
+## 调用链(Workflow)
+
+```
+上游: wechat-get-messages → 获取新消息,提取 fromWxid 作为回复目标
+本步: wechat-send-text(toWxid, content) → 发送文字回复
+```
+
+## 入参规则
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| toWxid | string | ✅ | 接收方 wxid(好友wxid 或 群聊id,如 `12345@chatroom`) |
+| content | string | ✅ | 文字消息内容 |
+| ats | string | ❌ | 群聊中 @的用户 wxid,多个用逗号分隔 |
+
+## 接口调用方式
+
+- **请求方法**: POST
+- **接口地址**: `{{wechatApiBase}}/message/send-text`
+
+```json
+{
+  "toWxid": "wxid_xxx",
+  "content": "你好!",
+  "ats": ""
+}
+```
+
+## 关键响应字段
+
+| 字段 | 业务用途 |
+|------|---------|
+| ret | 200 表示发送成功 |
+| msg | 操作结果描述 |
+
+## 依赖要求
+
+- 微信账号必须在线(先调用 `wechat-check-online` 确认)
+- toWxid 必须是有效的好友或群聊 ID

+ 73 - 0
wechat/wechat-send-text/api-config.json

@@ -0,0 +1,73 @@
+{
+  "name": "wechat-send-text",
+  "displayName": "微信发送文字消息",
+  "description": "向指定微信用户或群聊发送文字消息,是自动应答的核心技能。",
+  "category": "wechat",
+  "version": "1.0.0",
+  "endpoint": {
+    "method": "POST",
+    "url": "{{wechatApiBase}}/message/send-text",
+    "headers": {
+      "Content-Type": "application/json"
+    }
+  },
+  "parameters": {
+    "type": "object",
+    "required": ["toWxid", "content"],
+    "properties": {
+      "toWxid": {
+        "type": "string",
+        "description": "接收方 wxid(好友wxid 或 群聊id,如 12345@chatroom)"
+      },
+      "content": {
+        "type": "string",
+        "description": "文字消息内容"
+      },
+      "ats": {
+        "type": "string",
+        "description": "群聊中 @的用户 wxid,多个用逗号分隔",
+        "default": ""
+      }
+    }
+  },
+  "requestTransform": {
+    "description": "直接透传参数",
+    "template": {
+      "toWxid": "{{toWxid}}",
+      "content": "{{content}}",
+      "ats": "{{ats}}"
+    }
+  },
+  "response": {
+    "type": "object",
+    "properties": {
+      "ret": { "type": "integer", "description": "200 表示发送成功" },
+      "msg": { "type": "string", "description": "操作结果描述" }
+    }
+  },
+  "usageExamples": [
+    {
+      "name": "回复好友消息",
+      "input": { "toWxid": "wxid_abc123", "content": "收到,我马上处理!" },
+      "description": "向好友发送文字回复"
+    },
+    {
+      "name": "群聊发消息",
+      "input": { "toWxid": "12345678@chatroom", "content": "大家好!", "ats": "" },
+      "description": "在群聊中发送文字消息"
+    }
+  ],
+  "tokenConfig": {
+    "type": "config",
+    "configFile": "~/.openclaw/wechat-credentials.json",
+    "tokenField": "wechatApiBase",
+    "currentToken": "http://8.138.37.248/api/wechat-agent",
+    "resolutionOrder": ["configFile", "currentToken"]
+  },
+  "timeout": 15000,
+  "retry": {
+    "maxAttempts": 2,
+    "delay": 1000,
+    "backoffMultiplier": 2
+  }
+}

+ 86 - 0
workflows/pipeline.md

@@ -0,0 +1,86 @@
+# 微信自动应答 Pipeline
+
+## 执行流程图
+
+```
+┌──────────────────────────────────┐
+│         定时触发 (每10秒)          │
+└───────────────┬──────────────────┘
+                │
+                ▼
+┌──────────────────────────────────┐
+│  Stage 1: 检查在线状态             │
+│  skill: wechat-check-online      │
+│  → data=true → 继续               │
+│  → data=false → 停止等待重连       │
+└───────────────┬──────────────────┘
+                │
+                ▼
+┌──────────────────────────────────┐
+│  Stage 2: 增量拉取新消息           │
+│  skill: wechat-get-messages      │
+│  input: since=上次检查时间         │
+│         direction=received       │
+│  → 返回新消息数组                  │
+└───────────────┬──────────────────┘
+                │
+                ▼
+┌──────────────────────────────────┐
+│  Stage 3: 遍历每条新消息           │
+│  过滤: 跳过系统消息/表情/自己发的   │
+│  过滤: 跳过 weixin/fmessage 等    │
+└───────┬───────────────┬──────────┘
+        │               │
+        ▼               ▼
+┌──────────────┐ ┌──────────────┐
+│  个人私聊     │ │  群聊         │
+│  fromWxid    │ │  @chatroom   │
+│  不含@chatroom│ │              │
+└──────┬───────┘ └──────┬───────┘
+       │                │
+       ▼                ▼
+┌──────────────┐ ┌──────────────────┐
+│ AI生成回复    │ │ 检查是否被@或关键词│
+│ 友好个性化    │ │ 触发才回复        │
+└──────┬───────┘ └──────┬───────────┘
+       │                │
+       ▼                ▼
+┌──────────────────────────────────┐
+│  Stage 4: 发送回复                 │
+│  skill: wechat-send-text         │
+│  input: toWxid, content          │
+└──────────────────────────────────┘
+```
+
+## 消息来源分类规则
+
+| fromWxid 格式 | 来源类型 | 存储目录 | 回复策略 |
+|--------------|---------|---------|---------|
+| `wxid_xxx` | 个人私聊 | `personal/{wxid}.jsonl` | 每条消息都回复 |
+| `xxx@chatroom` | 群聊 | `group/{chatroom}.jsonl` | 仅被@或关键词触发时回复 |
+| `gh_xxx` | 公众号 | 忽略 | 不回复 |
+| `weixin`/`fmessage` | 系统 | 忽略 | 不回复 |
+
+## 回复策略
+
+### 个人私聊
+- 文字消息 → AI生成个性化回复
+- 图片/语音/视频 → 回复确认("收到图片~")
+- 名片/位置/链接 → 回复确认
+
+### 群聊
+- 被@时 → AI生成回复
+- 包含关键词("帮我"/"请问")→ AI生成回复
+- 其他消息 → 不回复,避免刷屏
+
+### 忽略列表
+- 消息类型: system, emoji, location, video, voice
+- 来源wxid: weixin, fmessage, medianote, gh_* (公众号)
+
+## 状态管理
+
+| 状态变量 | 说明 |
+|---------|------|
+| `lastCheckTime` | 上次轮询时间(ISO),用于 since 增量拉取 |
+
+每次轮询完成后更新 lastCheckTime = 当前时间,下次只拉新消息。

+ 118 - 0
workflows/wechat-auto-reply.workflow.json

@@ -0,0 +1,118 @@
+{
+  "name": "wechat-auto-reply",
+  "displayName": "微信自动应答",
+  "description": "定时轮询新消息,根据来源(个人/群聊)分类处理,生成智能回复并发送。",
+  "version": "1.0.0",
+
+  "trigger": {
+    "type": "polling",
+    "intervalSeconds": 10,
+    "description": "每10秒轮询一次新消息"
+  },
+
+  "state": {
+    "lastCheckTime": {
+      "type": "string",
+      "description": "上次检查的时间戳(ISO),用于增量拉取",
+      "default": ""
+    }
+  },
+
+  "stages": [
+    {
+      "id": "check-online",
+      "name": "检查在线状态",
+      "skill": "wechat-check-online",
+      "input": {},
+      "onFailure": "stop",
+      "condition": "每次轮询的第一步",
+      "expect": { "data": true }
+    },
+    {
+      "id": "fetch-new-messages",
+      "name": "增量拉取新消息",
+      "skill": "wechat-get-messages",
+      "input": {
+        "since": "{{state.lastCheckTime}}",
+        "direction": "received",
+        "limit": 50
+      },
+      "output": {
+        "messages": "data",
+        "description": "收到的新消息数组"
+      },
+      "postAction": "更新 state.lastCheckTime 为当前时间"
+    },
+    {
+      "id": "classify-and-reply",
+      "name": "分类处理并回复",
+      "type": "forEach",
+      "items": "{{stages.fetch-new-messages.output.messages}}",
+      "filter": {
+        "description": "过滤掉系统消息和自己发的消息",
+        "conditions": [
+          "item.type != 'system'",
+          "item.direction == 'received'"
+        ]
+      },
+      "steps": [
+        {
+          "id": "classify-source",
+          "name": "判断消息来源",
+          "type": "condition",
+          "rules": [
+            {
+              "if": "item.fromWxid ends with '@chatroom'",
+              "then": { "sourceType": "group", "replyTo": "item.fromWxid" }
+            },
+            {
+              "else": true,
+              "then": { "sourceType": "personal", "replyTo": "item.fromWxid" }
+            }
+          ]
+        },
+        {
+          "id": "generate-reply",
+          "name": "AI生成回复内容",
+          "type": "llm",
+          "systemPrompt": "你是一个微信智能助手。请根据用户发来的消息生成简短、友好的回复。如果是群聊消息,回复要简洁。如果无法理解内容,回复"收到,稍后回复您"。不要回复系统消息、表情、图片等非文字内容。",
+          "userPrompt": "来源类型: {{classify-source.sourceType}}\n发送者: {{item.nickName}} ({{item.fromWxid}})\n消息类型: {{item.type}}\n消息内容: {{item.content}}\n\n请生成回复:",
+          "output": { "replyContent": "llm_response" }
+        },
+        {
+          "id": "send-reply",
+          "name": "发送回复",
+          "skill": "wechat-send-text",
+          "input": {
+            "toWxid": "{{classify-source.replyTo}}",
+            "content": "{{generate-reply.replyContent}}"
+          }
+        }
+      ]
+    }
+  ],
+
+  "replyRules": {
+    "description": "回复策略规则(龙虾参考)",
+    "personalChat": {
+      "description": "个人私聊",
+      "behavior": "收到文字消息后,用AI生成个性化回复;收到图片/语音/视频等仅回复确认。",
+      "examples": [
+        { "received": "你好", "reply": "你好!有什么可以帮你的吗?" },
+        { "received": "[图片]", "reply": "收到图片,我看看~" },
+        { "received": "明天几点开会", "reply": "让我帮你查一下,稍等。" }
+      ]
+    },
+    "groupChat": {
+      "description": "群聊",
+      "behavior": "仅在被@或关键词触发时回复,避免刷屏。回复简洁。",
+      "triggerKeywords": ["@助手", "@bot", "帮我", "请问"],
+      "examples": [
+        { "received": "@助手 明天天气怎么样", "reply": "明天多云转晴,适合出行!" },
+        { "received": "随便聊天", "reply": null, "note": "不回复,不相关" }
+      ]
+    },
+    "ignoreTypes": ["system", "emoji", "location", "video", "voice"],
+    "ignoreWxids": ["weixin", "fmessage", "medianote", "gh_"]
+  }
+}