@core-workspace/infoflow-openclaw-plugin 2026.3.9 → 2026.3.27-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/CLAUDE.md +135 -0
- package/COLLABORATION_REPORT.md +209 -0
- package/PROJECT_GUIDE.md +355 -0
- package/README.md +158 -66
- package/docs/dev-guide.md +63 -50
- package/docs/qa-feature-list.md +452 -0
- package/docs/webhook-guide.md +178 -0
- package/index.ts +28 -2
- package/openclaw.plugin.json +131 -21
- package/package.json +16 -3
- package/scripts/deploy.sh +66 -7
- package/scripts/postinstall.cjs +80 -0
- package/skills/infoflow-dev/SKILL.md +2 -2
- package/skills/infoflow-dev/references/api.md +1 -1
- package/src/adapter/inbound/webhook-parser.ts +27 -5
- package/src/adapter/inbound/ws-receiver.ts +304 -43
- package/src/adapter/outbound/markdown-local-images.ts +80 -0
- package/src/adapter/outbound/reply-dispatcher.ts +146 -65
- package/src/adapter/outbound/target-resolver.ts +4 -3
- package/src/channel/accounts.ts +97 -22
- package/src/channel/channel.ts +456 -12
- package/src/channel/media.ts +20 -6
- package/src/channel/monitor.ts +8 -3
- package/src/channel/outbound.ts +358 -21
- package/src/channel/streaming.ts +740 -0
- package/src/commands/changelog.ts +80 -0
- package/src/commands/doctor.ts +545 -0
- package/src/commands/logs.ts +449 -0
- package/src/commands/version.ts +20 -0
- package/src/compat/openclaw-sdk.ts +218 -0
- package/src/handler/message-handler.ts +673 -166
- package/src/logging.ts +1 -1
- package/src/runtime.ts +1 -1
- package/src/security/dm-policy.ts +1 -4
- package/src/security/group-policy.ts +174 -51
- package/src/tools/actions/index.ts +15 -13
- package/src/tools/cron/relay.ts +1154 -0
- package/src/tools/hooks/index.ts +13 -1
- package/src/tools/index.ts +714 -32
- package/src/types.ts +144 -25
- package/src/utils/audio/g722/dct_tables.ts +381 -0
- package/src/utils/audio/g722/decoder.ts +919 -0
- package/src/utils/audio/g722/defs.ts +105 -0
- package/src/utils/audio/g722/hd-parser.ts +247 -0
- package/src/utils/audio/g722/huff_tables.ts +240 -0
- package/src/utils/audio/g722/index.ts +78 -0
- package/src/utils/audio/g722/output_decoded.pcm +0 -0
- package/src/utils/audio/g722/output_decoded.wav +0 -0
- package/src/utils/audio/g722/tables.ts +173 -0
- package/src/utils/audio/g722/test_api.ts +31 -0
- package/src/utils/audio/g722/test_voice.hd +0 -0
- package/src/utils/bos/im-bos-client.ts +219 -0
- package/src/utils/group-agent-cache.ts +142 -0
- package/src/utils/token-adapter.ts +120 -51
package/openclaw.plugin.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "infoflow",
|
|
2
|
+
"id": "infoflow-openclaw-plugin",
|
|
3
3
|
"channels": ["infoflow"],
|
|
4
4
|
"skills": ["./skills"],
|
|
5
5
|
"configSchema": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
9
|
-
"apiHost": { "type": "string" },
|
|
10
|
-
"enabled": { "type": "boolean" },
|
|
9
|
+
"apiHost": { "type": "string", "default": "https://api.im.baidu.com" },
|
|
10
|
+
"enabled": { "type": "boolean", "default": true },
|
|
11
11
|
"name": { "type": "string" },
|
|
12
12
|
"checkToken": { "type": "string" },
|
|
13
13
|
"encodingAESKey": { "type": "string" },
|
|
@@ -16,32 +16,67 @@
|
|
|
16
16
|
"connectionMode": {
|
|
17
17
|
"type": "string",
|
|
18
18
|
"enum": ["webhook", "websocket"],
|
|
19
|
-
"default": "
|
|
19
|
+
"default": "websocket",
|
|
20
20
|
"description": "消息接收方式:webhook(HTTP 回调)或 websocket(长连接)"
|
|
21
21
|
},
|
|
22
22
|
"wsGateway": {
|
|
23
23
|
"type": "string",
|
|
24
|
-
"
|
|
24
|
+
"default": "infoflow-open-gateway.baidu.com",
|
|
25
|
+
"description": "WebSocket Gateway 域名,用于 Phase 1 端点分配请求(仅 websocket 模式使用)"
|
|
26
|
+
},
|
|
27
|
+
"wsConnectDomain": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "WebSocket 连接域名,用于 Phase 2 实际 WS 握手(不填则使用服务端返回的地址,内网环境可配置此项)"
|
|
25
30
|
},
|
|
26
31
|
"robotName": { "type": "string" },
|
|
32
|
+
"robotId": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "无需手动填写,自动发现。如流机器人实际ID(首次被@时从消息体中提取,用于忽略自身消息)"
|
|
35
|
+
},
|
|
27
36
|
"appAgentId": {
|
|
28
37
|
"type": "number",
|
|
29
38
|
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
|
30
39
|
},
|
|
31
|
-
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
40
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"], "default": "open" },
|
|
32
41
|
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
33
|
-
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
42
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"], "default": "open" },
|
|
34
43
|
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
35
|
-
"requireMention": { "type": "boolean" },
|
|
44
|
+
"requireMention": { "type": "boolean", "default": true },
|
|
36
45
|
"replyMode": {
|
|
37
46
|
"type": "string",
|
|
38
|
-
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
39
|
-
|
|
47
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
48
|
+
},
|
|
49
|
+
"groupSessionMode": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": ["group", "user"],
|
|
52
|
+
"default": "group",
|
|
53
|
+
"description": "群聊会话拆分模式:group=按群拆分,user=按群+人拆分"
|
|
40
54
|
},
|
|
41
55
|
"followUp": { "type": "boolean", "default": true },
|
|
42
56
|
"followUpWindow": { "type": "number", "default": 300 },
|
|
43
57
|
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
44
|
-
"watchRegex": {
|
|
58
|
+
"watchRegex": {
|
|
59
|
+
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }]
|
|
60
|
+
},
|
|
61
|
+
"defaultTo": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "定时任务转发的默认目标,例如 zhangsan 或 group:123456"
|
|
64
|
+
},
|
|
65
|
+
"privateDataDir": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"default": "plugins/infoflow-private",
|
|
68
|
+
"description": "插件私有数据目录,相对 OpenClaw stateDir"
|
|
69
|
+
},
|
|
70
|
+
"cronRelay": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": false,
|
|
73
|
+
"properties": {
|
|
74
|
+
"enabled": { "type": "boolean", "default": true },
|
|
75
|
+
"includeAlreadyDelivered": { "type": "boolean", "default": false },
|
|
76
|
+
"prefix": { "type": "string", "default": "【定时任务】" },
|
|
77
|
+
"pollIntervalMs": { "type": "number", "default": 2000 }
|
|
78
|
+
}
|
|
79
|
+
},
|
|
45
80
|
"groups": {
|
|
46
81
|
"type": "object",
|
|
47
82
|
"additionalProperties": {
|
|
@@ -51,8 +86,15 @@
|
|
|
51
86
|
"type": "string",
|
|
52
87
|
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
53
88
|
},
|
|
89
|
+
"groupSessionMode": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"enum": ["group", "user"],
|
|
92
|
+
"description": "群聊会话拆分模式:group=按群拆分,user=按群+人拆分"
|
|
93
|
+
},
|
|
54
94
|
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
55
|
-
"watchRegex": {
|
|
95
|
+
"watchRegex": {
|
|
96
|
+
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }]
|
|
97
|
+
},
|
|
56
98
|
"followUp": { "type": "boolean" },
|
|
57
99
|
"followUpWindow": { "type": "number" },
|
|
58
100
|
"systemPrompt": { "type": "string" }
|
|
@@ -65,8 +107,9 @@
|
|
|
65
107
|
"additionalProperties": {
|
|
66
108
|
"type": "object",
|
|
67
109
|
"properties": {
|
|
68
|
-
"enabled": { "type": "boolean" },
|
|
110
|
+
"enabled": { "type": "boolean", "default": true },
|
|
69
111
|
"name": { "type": "string" },
|
|
112
|
+
"apiHost": { "type": "string", "default": "https://api.im.baidu.com" },
|
|
70
113
|
"checkToken": { "type": "string" },
|
|
71
114
|
"encodingAESKey": { "type": "string" },
|
|
72
115
|
"appKey": { "type": "string" },
|
|
@@ -74,27 +117,51 @@
|
|
|
74
117
|
"connectionMode": {
|
|
75
118
|
"type": "string",
|
|
76
119
|
"enum": ["webhook", "websocket"],
|
|
77
|
-
"default": "
|
|
120
|
+
"default": "websocket"
|
|
78
121
|
},
|
|
79
|
-
"wsGateway": { "type": "string" },
|
|
122
|
+
"wsGateway": { "type": "string", "default": "infoflow-open-gateway.baidu.com" },
|
|
123
|
+
"wsConnectDomain": { "type": "string" },
|
|
80
124
|
"robotName": { "type": "string" },
|
|
125
|
+
"robotId": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"description": "无需手动填写,自动发现。如流机器人实际ID(首次被@时从消息体中提取,用于忽略自身消息)"
|
|
128
|
+
},
|
|
81
129
|
"appAgentId": {
|
|
82
130
|
"type": "number",
|
|
83
131
|
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
|
84
132
|
},
|
|
85
|
-
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
133
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"], "default": "open" },
|
|
86
134
|
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
87
|
-
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
135
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"], "default": "open" },
|
|
88
136
|
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
89
|
-
"requireMention": { "type": "boolean" },
|
|
137
|
+
"requireMention": { "type": "boolean", "default": true },
|
|
90
138
|
"replyMode": {
|
|
91
139
|
"type": "string",
|
|
92
140
|
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
93
141
|
},
|
|
142
|
+
"groupSessionMode": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"enum": ["group", "user"],
|
|
145
|
+
"description": "群聊会话拆分模式:group=按群拆分,user=按群+人拆分"
|
|
146
|
+
},
|
|
94
147
|
"followUp": { "type": "boolean" },
|
|
95
148
|
"followUpWindow": { "type": "number" },
|
|
96
149
|
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
97
|
-
"watchRegex": {
|
|
150
|
+
"watchRegex": {
|
|
151
|
+
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }]
|
|
152
|
+
},
|
|
153
|
+
"defaultTo": { "type": "string" },
|
|
154
|
+
"privateDataDir": { "type": "string", "default": "plugins/infoflow-private" },
|
|
155
|
+
"cronRelay": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"properties": {
|
|
159
|
+
"enabled": { "type": "boolean", "default": true },
|
|
160
|
+
"includeAlreadyDelivered": { "type": "boolean", "default": false },
|
|
161
|
+
"prefix": { "type": "string", "default": "【定时任务】" },
|
|
162
|
+
"pollIntervalMs": { "type": "number", "default": 2000 }
|
|
163
|
+
}
|
|
164
|
+
},
|
|
98
165
|
"groups": {
|
|
99
166
|
"type": "object",
|
|
100
167
|
"additionalProperties": {
|
|
@@ -104,14 +171,46 @@
|
|
|
104
171
|
"type": "string",
|
|
105
172
|
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
106
173
|
},
|
|
174
|
+
"groupSessionMode": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"enum": ["group", "user"],
|
|
177
|
+
"description": "群聊会话拆分模式:group=按群拆分,user=按群+人拆分"
|
|
178
|
+
},
|
|
107
179
|
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
108
|
-
"watchRegex": {
|
|
180
|
+
"watchRegex": {
|
|
181
|
+
"oneOf": [
|
|
182
|
+
{ "type": "string" },
|
|
183
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
184
|
+
]
|
|
185
|
+
},
|
|
109
186
|
"followUp": { "type": "boolean" },
|
|
110
187
|
"followUpWindow": { "type": "number" },
|
|
111
188
|
"systemPrompt": { "type": "string" }
|
|
112
189
|
},
|
|
113
190
|
"additionalProperties": false
|
|
114
191
|
}
|
|
192
|
+
},
|
|
193
|
+
"processingHint": {
|
|
194
|
+
"type": "boolean",
|
|
195
|
+
"default": true
|
|
196
|
+
},
|
|
197
|
+
"processingHintDelay": {
|
|
198
|
+
"type": "number",
|
|
199
|
+
"default": 5
|
|
200
|
+
},
|
|
201
|
+
"dmMessageFormat": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"enum": ["text", "markdown"],
|
|
204
|
+
"default": "text"
|
|
205
|
+
},
|
|
206
|
+
"groupMessageFormat": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"enum": ["text", "markdown"],
|
|
209
|
+
"default": "text"
|
|
210
|
+
},
|
|
211
|
+
"textChunkLimit": {
|
|
212
|
+
"type": "number",
|
|
213
|
+
"default": 1800
|
|
115
214
|
}
|
|
116
215
|
}
|
|
117
216
|
}
|
|
@@ -127,11 +226,22 @@
|
|
|
127
226
|
"default": 5,
|
|
128
227
|
"description": "发送处理中提示前的等待秒数,设为 0 则立即发送"
|
|
129
228
|
},
|
|
130
|
-
"
|
|
229
|
+
"dmMessageFormat": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"enum": ["text", "markdown"],
|
|
232
|
+
"default": "text",
|
|
233
|
+
"description": "单聊消息格式:text(纯文本)或 markdown(富文本)"
|
|
234
|
+
},
|
|
235
|
+
"groupMessageFormat": {
|
|
131
236
|
"type": "string",
|
|
132
237
|
"enum": ["text", "markdown"],
|
|
133
238
|
"default": "text",
|
|
134
239
|
"description": "群聊消息格式:text(纯文本)或 markdown(富文本,不支持引用回复)"
|
|
240
|
+
},
|
|
241
|
+
"textChunkLimit": {
|
|
242
|
+
"type": "number",
|
|
243
|
+
"default": 1800,
|
|
244
|
+
"description": "长消息自动拆分的字符上限"
|
|
135
245
|
}
|
|
136
246
|
}
|
|
137
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@core-workspace/infoflow-openclaw-plugin",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.27-beta.0",
|
|
4
4
|
"description": "OpenClaw Infoflow (如流) channel plugin for Baidu enterprise messaging",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"openclaw",
|
|
10
10
|
"openclaw-plugin",
|
|
11
11
|
"infoflow",
|
|
12
|
+
"infoflow-openclaw-plugin",
|
|
12
13
|
"baidu",
|
|
13
14
|
"chatbot",
|
|
14
15
|
"ai-agent",
|
|
@@ -21,15 +22,21 @@
|
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"openclaw": ">=2026.3.2"
|
|
23
24
|
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"openclaw": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
24
30
|
"dependencies": {
|
|
25
|
-
"@core-workspace/infoflow-sdk-nodejs": "
|
|
31
|
+
"@core-workspace/infoflow-sdk-nodejs": "2026.3.26-beta.1",
|
|
32
|
+
"@sinclair/typebox": "0.34.48"
|
|
26
33
|
},
|
|
27
34
|
"openclaw": {
|
|
28
35
|
"extensions": [
|
|
29
36
|
"./index.ts"
|
|
30
37
|
],
|
|
31
38
|
"channel": {
|
|
32
|
-
"id": "infoflow",
|
|
39
|
+
"id": "infoflow-openclaw-plugin",
|
|
33
40
|
"label": "Infoflow",
|
|
34
41
|
"selectionLabel": "Infoflow (如流)",
|
|
35
42
|
"docsPath": "/channels/infoflow",
|
|
@@ -40,5 +47,11 @@
|
|
|
40
47
|
"npmSpec": "@core-workspace/infoflow-openclaw-plugin",
|
|
41
48
|
"defaultChoice": "npm"
|
|
42
49
|
}
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"openclaw": "^2026.3.23"
|
|
43
56
|
}
|
|
44
57
|
}
|
package/scripts/deploy.sh
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
PLUGIN_ID="infoflow-openclaw-plugin"
|
|
7
|
+
PLUGIN_DIR="$HOME/.openclaw/extensions/$PLUGIN_ID"
|
|
8
|
+
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
|
|
7
9
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
10
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
9
11
|
|
|
@@ -16,17 +18,74 @@ rsync -av --delete "$PROJECT_DIR/" "$PLUGIN_DIR/" \
|
|
|
16
18
|
--exclude scripts
|
|
17
19
|
|
|
18
20
|
echo "==> 安装依赖"
|
|
19
|
-
cd "$PLUGIN_DIR" && npm install --silent
|
|
21
|
+
cd "$PLUGIN_DIR" && npm_config_registry=http://registry.npm.baidu-int.com npm install --silent
|
|
22
|
+
|
|
23
|
+
echo "==> 链接 openclaw peer dependency"
|
|
24
|
+
OPENCLAW_GLOBAL="$(npm root -g)/openclaw"
|
|
25
|
+
if [ -d "$OPENCLAW_GLOBAL" ]; then
|
|
26
|
+
mkdir -p "$PLUGIN_DIR/node_modules"
|
|
27
|
+
rm -rf "$PLUGIN_DIR/node_modules/openclaw"
|
|
28
|
+
ln -s "$OPENCLAW_GLOBAL" "$PLUGIN_DIR/node_modules/openclaw"
|
|
29
|
+
echo " ✓ 已链接 $OPENCLAW_GLOBAL -> $PLUGIN_DIR/node_modules/openclaw"
|
|
30
|
+
else
|
|
31
|
+
echo " ✗ 找不到全局 openclaw 安装,尝试使用 which openclaw 推断..."
|
|
32
|
+
OPENCLAW_BIN="$(which openclaw 2>/dev/null)"
|
|
33
|
+
if [ -n "$OPENCLAW_BIN" ]; then
|
|
34
|
+
OPENCLAW_GLOBAL="$(cd "$(dirname "$OPENCLAW_BIN")/.." && pwd)/lib/node_modules/openclaw"
|
|
35
|
+
if [ -d "$OPENCLAW_GLOBAL" ]; then
|
|
36
|
+
mkdir -p "$PLUGIN_DIR/node_modules"
|
|
37
|
+
rm -rf "$PLUGIN_DIR/node_modules/openclaw"
|
|
38
|
+
ln -s "$OPENCLAW_GLOBAL" "$PLUGIN_DIR/node_modules/openclaw"
|
|
39
|
+
echo " ✓ 已链接 $OPENCLAW_GLOBAL -> $PLUGIN_DIR/node_modules/openclaw"
|
|
40
|
+
else
|
|
41
|
+
echo " ✗ 无法找到 openclaw 安装目录,跳过链接(插件可能无法加载)"
|
|
42
|
+
fi
|
|
43
|
+
else
|
|
44
|
+
echo " ✗ openclaw 未安装,跳过链接"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "==> 更新 OpenClaw 配置"
|
|
49
|
+
node -e "
|
|
50
|
+
const fs = require('fs');
|
|
51
|
+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
52
|
+
const id = '$PLUGIN_ID';
|
|
53
|
+
|
|
54
|
+
// 1. plugins.entries: 确保插件已启用
|
|
55
|
+
cfg.plugins = cfg.plugins ?? {};
|
|
56
|
+
cfg.plugins.entries = cfg.plugins.entries ?? {};
|
|
57
|
+
if (!cfg.plugins.entries[id]) {
|
|
58
|
+
cfg.plugins.entries[id] = { enabled: true };
|
|
59
|
+
console.log(' + 已添加 plugins.entries.' + id);
|
|
60
|
+
} else if (!cfg.plugins.entries[id].enabled) {
|
|
61
|
+
cfg.plugins.entries[id].enabled = true;
|
|
62
|
+
console.log(' + 已启用 plugins.entries.' + id);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(' ✓ plugins.entries.' + id + ' 已存在');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2. plugins.allow: 如果配置了白名单,确保插件在列表中
|
|
68
|
+
if (Array.isArray(cfg.plugins.allow)) {
|
|
69
|
+
if (!cfg.plugins.allow.includes(id)) {
|
|
70
|
+
cfg.plugins.allow.push(id);
|
|
71
|
+
console.log(' + 已添加到 plugins.allow');
|
|
72
|
+
} else {
|
|
73
|
+
console.log(' ✓ plugins.allow 已包含 ' + id);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
console.log(' - plugins.allow 未配置,跳过');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 2) + '\n');
|
|
80
|
+
"
|
|
20
81
|
|
|
21
82
|
echo "==> 重启 OpenClaw gateway"
|
|
22
|
-
|
|
23
|
-
sleep 1
|
|
24
|
-
nohup openclaw gateway > /tmp/openclaw.log 2>&1 &
|
|
25
|
-
sleep 2
|
|
83
|
+
openclaw gateway restart
|
|
26
84
|
|
|
27
85
|
echo "==> 检查启动状态"
|
|
86
|
+
sleep 2
|
|
28
87
|
if pgrep -f "openclaw" > /dev/null; then
|
|
29
|
-
echo "✓ gateway 已启动(PID: $(pgrep -f openclaw | head -1))"
|
|
88
|
+
echo "✓ gateway 已启动(PID: $(pgrep -f 'openclaw' | head -1))"
|
|
30
89
|
echo " 日志: tail -f /tmp/openclaw.log"
|
|
31
90
|
else
|
|
32
91
|
echo "✗ gateway 启动失败,查看日志: cat /tmp/openclaw.log"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall.cjs
|
|
4
|
+
*
|
|
5
|
+
* Creates a symlink: node_modules/openclaw -> <global npm root>/openclaw
|
|
6
|
+
*
|
|
7
|
+
* Why: `openclaw` is a peerDependency (optional). When the openclaw gateway
|
|
8
|
+
* runs `npm install` for this plugin, npm does not install peerDeps.
|
|
9
|
+
* At runtime the plugin needs `openclaw/plugin-sdk/*` to resolve, so we
|
|
10
|
+
* link it from the global installation that is already running the gateway.
|
|
11
|
+
*
|
|
12
|
+
* No circular dependency risk: Node.js caches modules by resolved path.
|
|
13
|
+
* The gateway already holds openclaw in memory; the plugin just gets the
|
|
14
|
+
* same cached exports through the symlink.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
const { execSync, spawnSync } = require("child_process");
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
|
|
23
|
+
const PLUGIN_DIR = path.resolve(__dirname, "..");
|
|
24
|
+
const TARGET = path.join(PLUGIN_DIR, "node_modules", "openclaw");
|
|
25
|
+
|
|
26
|
+
// Already linked or installed — nothing to do.
|
|
27
|
+
if (fs.existsSync(TARGET)) {
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Try to locate the global openclaw package directory. */
|
|
32
|
+
function findGlobalOpenclaw() {
|
|
33
|
+
// Strategy 1: npm root -g
|
|
34
|
+
try {
|
|
35
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf8" }).trim();
|
|
36
|
+
const candidate = path.join(globalRoot, "openclaw");
|
|
37
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
38
|
+
} catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Strategy 2: resolve from `openclaw` binary on PATH
|
|
43
|
+
try {
|
|
44
|
+
const bin =
|
|
45
|
+
spawnSync("which", ["openclaw"], { encoding: "utf8" }).stdout?.trim() ||
|
|
46
|
+
spawnSync("where", ["openclaw"], { encoding: "utf8" }).stdout?.trim();
|
|
47
|
+
if (bin) {
|
|
48
|
+
// binary lives at <prefix>/bin/openclaw → package at <prefix>/lib/node_modules/openclaw
|
|
49
|
+
const prefix = path.resolve(path.dirname(bin), "..");
|
|
50
|
+
const candidate = path.join(prefix, "lib", "node_modules", "openclaw");
|
|
51
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const src = findGlobalOpenclaw();
|
|
61
|
+
if (!src) {
|
|
62
|
+
// Not fatal — openclaw may inject itself via other means at runtime.
|
|
63
|
+
console.log(
|
|
64
|
+
"[infoflow-openclaw-plugin] postinstall: openclaw not found globally, skipping symlink."
|
|
65
|
+
);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(path.join(PLUGIN_DIR, "node_modules"), { recursive: true });
|
|
71
|
+
fs.symlinkSync(src, TARGET, "junction"); // "junction" works on Windows too
|
|
72
|
+
console.log(
|
|
73
|
+
`[infoflow-openclaw-plugin] postinstall: linked ${src} -> ${TARGET}`
|
|
74
|
+
);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
// Non-fatal: e.g. permission error in some CI envs.
|
|
77
|
+
console.warn(
|
|
78
|
+
`[infoflow-openclaw-plugin] postinstall: failed to create symlink (${err.message})`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -23,12 +23,12 @@ Authorization: Bearer-<token> ← 注意是连字符,不是空格
|
|
|
23
23
|
|
|
24
24
|
### 发群消息端点
|
|
25
25
|
```
|
|
26
|
-
POST http://
|
|
26
|
+
POST http://api.im.baidu.com/api/v1/robot/msg/groupmsgsend
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### 发私聊端点
|
|
30
30
|
```
|
|
31
|
-
POST http://
|
|
31
|
+
POST http://api.im.baidu.com/api/v1/app/message/send
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
### 消息类型
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
### API 端点
|
|
10
10
|
|
|
11
|
-
- **生产环境**: `http://
|
|
11
|
+
- **生产环境**: `http://api.im.baidu.com/api/v1/robot/msg/groupmsgsend`
|
|
12
12
|
- **预发布环境**: `http://xplatform-preonline.dev.weiyun.baidu.com/open-plat/api/devp/v1/robot/msg/groupmsgsend`
|
|
13
13
|
|
|
14
14
|
### 权限要求
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createHash, createDecipheriv, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import type { IncomingMessage } from "node:http";
|
|
3
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
4
|
-
import { createDedupeCache } from "openclaw
|
|
3
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
4
|
+
import { createDedupeCache } from "../../compat/openclaw-sdk.js";
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
// Message deduplication
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
import { handlePrivateChatMessage, handleGroupChatMessage } from "../../handler/message-handler.js";
|
|
9
|
-
import type { ResolvedInfoflowAccount } from "../../types.js";
|
|
10
9
|
import { getInfoflowParseLog, formatInfoflowError, logVerbose } from "../../logging.js";
|
|
10
|
+
import type { ResolvedInfoflowAccount } from "../../types.js";
|
|
11
11
|
|
|
12
12
|
const DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
13
13
|
const DEDUP_MAX_SIZE = 1000;
|
|
@@ -67,6 +67,22 @@ export function recordSentMessageId(messageId: string | null): void {
|
|
|
67
67
|
messageCache.check(messageId);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Large-integer ID precision fix
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Matches large-integer ID fields in raw JSON text before parsing.
|
|
76
|
+
* Converts bare integers (16+ digits) to quoted strings in-place,
|
|
77
|
+
* preventing precision loss from JSON.parse for IDs exceeding
|
|
78
|
+
* Number.MAX_SAFE_INTEGER (2^53-1).
|
|
79
|
+
*/
|
|
80
|
+
const LARGE_INT_ID_RE = /("(?:messageid|msgid|MsgId|msgkey)")\s*:\s*(\d{16,})/g;
|
|
81
|
+
|
|
82
|
+
export function patchPreciseIds(rawText: string): string {
|
|
83
|
+
return rawText.replace(LARGE_INT_ID_RE, (_, key, val) => `${key}:"${val}"`);
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
// ---------------------------------------------------------------------------
|
|
71
87
|
// AES-ECB Decryption Utilities
|
|
72
88
|
// ---------------------------------------------------------------------------
|
|
@@ -313,7 +329,7 @@ function tryDecryptAndDispatch(params: DecryptDispatchParams): ParseResult {
|
|
|
313
329
|
// Parse as JSON first, then try fallback parser (XML for private)
|
|
314
330
|
let msgData: Record<string, unknown> | null = null;
|
|
315
331
|
try {
|
|
316
|
-
msgData = JSON.parse(decryptedContent) as Record<string, unknown>;
|
|
332
|
+
msgData = JSON.parse(patchPreciseIds(decryptedContent)) as Record<string, unknown>;
|
|
317
333
|
} catch {
|
|
318
334
|
if (fallbackParser) {
|
|
319
335
|
msgData = fallbackParser(decryptedContent);
|
|
@@ -326,6 +342,9 @@ function tryDecryptAndDispatch(params: DecryptDispatchParams): ParseResult {
|
|
|
326
342
|
return { handled: true, statusCode: 200, body: "success" };
|
|
327
343
|
}
|
|
328
344
|
|
|
345
|
+
getInfoflowParseLog().info(
|
|
346
|
+
`[infoflow:raw] ${chatType} decrypted: ${JSON.stringify(msgData).slice(0, 2000)}`,
|
|
347
|
+
);
|
|
329
348
|
target.statusSink?.({ lastInboundAt: Date.now() });
|
|
330
349
|
|
|
331
350
|
// Fire-and-forget with centralized error handling
|
|
@@ -357,7 +376,7 @@ function tryDecryptAndDispatch(params: DecryptDispatchParams): ParseResult {
|
|
|
357
376
|
function handlePrivateMessage(messageJsonStr: string, targets: WebhookTarget[]): ParseResult {
|
|
358
377
|
let messageJson: Record<string, unknown>;
|
|
359
378
|
try {
|
|
360
|
-
messageJson = JSON.parse(messageJsonStr) as Record<string, unknown>;
|
|
379
|
+
messageJson = JSON.parse(patchPreciseIds(messageJsonStr)) as Record<string, unknown>;
|
|
361
380
|
} catch {
|
|
362
381
|
getInfoflowParseLog().error(`[infoflow] private: invalid messageJson`);
|
|
363
382
|
return { handled: true, statusCode: 400, body: "invalid messageJson" };
|
|
@@ -431,3 +450,6 @@ export const _parseXmlMessage = parseXmlMessage;
|
|
|
431
450
|
export function _resetMessageCache(): void {
|
|
432
451
|
messageCache.clear();
|
|
433
452
|
}
|
|
453
|
+
|
|
454
|
+
/** @internal */
|
|
455
|
+
export const _patchPreciseIds = patchPreciseIds;
|