@chbo297/infoflow 2026.3.18 → 2026.5.4
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/README.md +24 -528
- package/dist/index.js +21 -0
- package/dist/src/accounts.js +110 -0
- package/dist/src/actions.js +386 -0
- package/dist/src/bot.js +1010 -0
- package/dist/src/channel.js +385 -0
- package/dist/src/infoflow-req-parse.js +394 -0
- package/dist/src/logging.js +102 -0
- package/dist/src/markdown-local-images.js +65 -0
- package/dist/src/media.js +318 -0
- package/dist/src/monitor.js +145 -0
- package/dist/src/reply-dispatcher.js +301 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/send.js +820 -0
- package/dist/src/sent-message-store.js +190 -0
- package/dist/src/targets.js +90 -0
- package/dist/src/types.js +4 -0
- package/dist/src/ws-receiver.js +378 -0
- package/openclaw.plugin.json +194 -0
- package/package.json +18 -3
- package/scripts/deploy.sh +215 -0
- package/src/accounts.ts +25 -3
- package/src/actions.ts +9 -3
- package/src/bot.ts +63 -20
- package/src/channel.ts +64 -45
- package/src/infoflow-req-parse.ts +2 -2
- package/src/infoflow-sdk.d.ts +12 -0
- package/src/monitor.ts +21 -2
- package/src/reply-dispatcher.ts +2 -5
- package/src/types.ts +11 -0
- package/src/ws-receiver.ts +482 -0
- package/tsconfig.build.json +6 -0
package/openclaw.plugin.json
CHANGED
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
"encodingAESKey": { "type": "string" },
|
|
13
13
|
"appKey": { "type": "string" },
|
|
14
14
|
"appSecret": { "type": "string" },
|
|
15
|
+
"connectionMode": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["webhook", "websocket"],
|
|
18
|
+
"default": "webhook",
|
|
19
|
+
"description": "消息接收模式:webhook(默认)或 websocket"
|
|
20
|
+
},
|
|
21
|
+
"wsGateway": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"default": "infoflow-open-gateway.weiyun.baidu.com",
|
|
24
|
+
"description": "WebSocket 网关域名,仅 websocket 模式使用"
|
|
25
|
+
},
|
|
26
|
+
"wsConnectDomain": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "WebSocket 连接域名,用于实际 WS 握手(不填则使用服务端返回的地址,内网环境可配置此项)"
|
|
29
|
+
},
|
|
15
30
|
"robotName": { "type": "string" },
|
|
16
31
|
"robotId": {
|
|
17
32
|
"type": "string",
|
|
@@ -64,6 +79,21 @@
|
|
|
64
79
|
"encodingAESKey": { "type": "string" },
|
|
65
80
|
"appKey": { "type": "string" },
|
|
66
81
|
"appSecret": { "type": "string" },
|
|
82
|
+
"connectionMode": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"enum": ["webhook", "websocket"],
|
|
85
|
+
"default": "webhook",
|
|
86
|
+
"description": "消息接收模式:webhook(默认)或 websocket"
|
|
87
|
+
},
|
|
88
|
+
"wsGateway": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"default": "infoflow-open-gateway.weiyun.baidu.com",
|
|
91
|
+
"description": "WebSocket 网关域名,仅 websocket 模式使用"
|
|
92
|
+
},
|
|
93
|
+
"wsConnectDomain": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "WebSocket 连接域名,用于实际 WS 握手(不填则使用服务端返回的地址,内网环境可配置此项)"
|
|
96
|
+
},
|
|
67
97
|
"robotName": { "type": "string" },
|
|
68
98
|
"robotId": {
|
|
69
99
|
"type": "string",
|
|
@@ -109,5 +139,169 @@
|
|
|
109
139
|
},
|
|
110
140
|
"defaultAccount": { "type": "string" }
|
|
111
141
|
}
|
|
142
|
+
},
|
|
143
|
+
"channelConfigs": {
|
|
144
|
+
"infoflow": {
|
|
145
|
+
"label": "Infoflow",
|
|
146
|
+
"schema": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"properties": {
|
|
150
|
+
"apiHost": { "type": "string" },
|
|
151
|
+
"enabled": { "type": "boolean" },
|
|
152
|
+
"name": { "type": "string" },
|
|
153
|
+
"checkToken": { "type": "string" },
|
|
154
|
+
"encodingAESKey": { "type": "string" },
|
|
155
|
+
"appKey": { "type": "string" },
|
|
156
|
+
"appSecret": { "type": "string" },
|
|
157
|
+
"connectionMode": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"enum": ["webhook", "websocket"],
|
|
160
|
+
"default": "webhook",
|
|
161
|
+
"description": "消息接收模式:webhook(默认)或 websocket"
|
|
162
|
+
},
|
|
163
|
+
"wsGateway": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"default": "infoflow-open-gateway.weiyun.baidu.com",
|
|
166
|
+
"description": "WebSocket 网关域名,仅 websocket 模式使用"
|
|
167
|
+
},
|
|
168
|
+
"wsConnectDomain": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "WebSocket 连接域名,用于实际 WS 握手(不填则使用服务端返回的地址,内网环境可配置此项)"
|
|
171
|
+
},
|
|
172
|
+
"robotName": { "type": "string" },
|
|
173
|
+
"robotId": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "不用填,该字段自动生成,用来兼容如流服务的某些特性,是如流机器人实际id(根据robotName在收到@时自动发现,用于忽略自己发出的消息)"
|
|
176
|
+
},
|
|
177
|
+
"appAgentId": {
|
|
178
|
+
"type": "number",
|
|
179
|
+
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
|
180
|
+
},
|
|
181
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
182
|
+
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
183
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
184
|
+
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
185
|
+
"requireMention": { "type": "boolean" },
|
|
186
|
+
"replyMode": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"],
|
|
189
|
+
"default": "mention-and-watch"
|
|
190
|
+
},
|
|
191
|
+
"followUp": { "type": "boolean", "default": true },
|
|
192
|
+
"followUpWindow": { "type": "number", "default": 300 },
|
|
193
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
194
|
+
"watchRegex": { "type": "array", "items": { "type": "string" } },
|
|
195
|
+
"groups": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"additionalProperties": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"properties": {
|
|
200
|
+
"replyMode": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
203
|
+
},
|
|
204
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
205
|
+
"watchRegex": { "type": "array", "items": { "type": "string" } },
|
|
206
|
+
"followUp": { "type": "boolean" },
|
|
207
|
+
"followUpWindow": { "type": "number" },
|
|
208
|
+
"systemPrompt": { "type": "string" }
|
|
209
|
+
},
|
|
210
|
+
"additionalProperties": false
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"accounts": {
|
|
214
|
+
"type": "object",
|
|
215
|
+
"additionalProperties": {
|
|
216
|
+
"type": "object",
|
|
217
|
+
"properties": {
|
|
218
|
+
"enabled": { "type": "boolean" },
|
|
219
|
+
"name": { "type": "string" },
|
|
220
|
+
"checkToken": { "type": "string" },
|
|
221
|
+
"encodingAESKey": { "type": "string" },
|
|
222
|
+
"appKey": { "type": "string" },
|
|
223
|
+
"appSecret": { "type": "string" },
|
|
224
|
+
"connectionMode": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"enum": ["webhook", "websocket"],
|
|
227
|
+
"default": "webhook",
|
|
228
|
+
"description": "消息接收模式:webhook(默认)或 websocket"
|
|
229
|
+
},
|
|
230
|
+
"wsGateway": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"default": "infoflow-open-gateway.weiyun.baidu.com",
|
|
233
|
+
"description": "WebSocket 网关域名,仅 websocket 模式使用"
|
|
234
|
+
},
|
|
235
|
+
"wsConnectDomain": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "WebSocket 连接域名,用于实际 WS 握手(不填则使用服务端返回的地址,内网环境可配置此项)"
|
|
238
|
+
},
|
|
239
|
+
"robotName": { "type": "string" },
|
|
240
|
+
"robotId": {
|
|
241
|
+
"type": "string",
|
|
242
|
+
"description": "不用填,该字段自动生成,用来兼容如流服务的某些特性,是如流机器人实际id(根据robotName在收到@时自动发现,用于忽略自己发出的消息)"
|
|
243
|
+
},
|
|
244
|
+
"appAgentId": {
|
|
245
|
+
"type": "number",
|
|
246
|
+
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
|
247
|
+
},
|
|
248
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
249
|
+
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
250
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
251
|
+
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
252
|
+
"requireMention": { "type": "boolean" },
|
|
253
|
+
"replyMode": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
256
|
+
},
|
|
257
|
+
"followUp": { "type": "boolean" },
|
|
258
|
+
"followUpWindow": { "type": "number" },
|
|
259
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
260
|
+
"watchRegex": { "type": "array", "items": { "type": "string" } },
|
|
261
|
+
"groups": {
|
|
262
|
+
"type": "object",
|
|
263
|
+
"additionalProperties": {
|
|
264
|
+
"type": "object",
|
|
265
|
+
"properties": {
|
|
266
|
+
"replyMode": {
|
|
267
|
+
"type": "string",
|
|
268
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
269
|
+
},
|
|
270
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
271
|
+
"watchRegex": { "type": "array", "items": { "type": "string" } },
|
|
272
|
+
"followUp": { "type": "boolean" },
|
|
273
|
+
"followUpWindow": { "type": "number" },
|
|
274
|
+
"systemPrompt": { "type": "string" }
|
|
275
|
+
},
|
|
276
|
+
"additionalProperties": false
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"defaultAccount": { "type": "string" }
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
"uiHints": {
|
|
286
|
+
"channels.infoflow.connectionMode": {
|
|
287
|
+
"help": "推荐默认 webhook;仅在企业网络/网关可达性受限时切换到 websocket。"
|
|
288
|
+
},
|
|
289
|
+
"channels.infoflow.wsGateway": {
|
|
290
|
+
"help": "仅 connectionMode=websocket 时生效。通常保持默认网关域名。"
|
|
291
|
+
},
|
|
292
|
+
"channels.infoflow.wsConnectDomain": {
|
|
293
|
+
"help": "可选。用于内网或专线环境覆盖 WS 实际握手域名。"
|
|
294
|
+
},
|
|
295
|
+
"channels.infoflow.appAgentId": {
|
|
296
|
+
"help": "用于私聊消息撤回(delete)。请填写如流后台应用ID。"
|
|
297
|
+
},
|
|
298
|
+
"channels.infoflow.allowFrom": {
|
|
299
|
+
"help": "dmPolicy=allowlist 时生效;建议使用 infoflow: 前缀标识来源。"
|
|
300
|
+
},
|
|
301
|
+
"channels.infoflow.groupAllowFrom": {
|
|
302
|
+
"help": "groupPolicy=allowlist 时生效;限制可触发群消息处理的来源。"
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
112
306
|
}
|
|
113
307
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chbo297/infoflow",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.5.4",
|
|
4
4
|
"description": "OpenClaw Infoflow (如流) channel plugin for Baidu enterprise messaging",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.
|
|
6
|
+
"main": "dist/index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"openclaw",
|
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
"url": "https://github.com/chbo297/openclaw-infoflow"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"
|
|
22
|
+
"@baidu/infoflow-sdk-nodejs": ">=0.1.0",
|
|
23
|
+
"openclaw": ">=2026.5.4"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"@baidu/infoflow-sdk-nodejs": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
23
29
|
},
|
|
24
30
|
"openclaw": {
|
|
25
31
|
"extensions": [
|
|
@@ -37,5 +43,14 @@
|
|
|
37
43
|
"npmSpec": "@chbo297/infoflow",
|
|
38
44
|
"defaultChoice": "npm"
|
|
39
45
|
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.build.json",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.1.5"
|
|
40
55
|
}
|
|
41
56
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 部署 openclaw-infoflow 插件到本地 OpenClaw 并重启 gateway
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
PLUGIN_ID="infoflow"
|
|
7
|
+
PLUGIN_DIR="$HOME/.openclaw/extensions/$PLUGIN_ID"
|
|
8
|
+
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
11
|
+
|
|
12
|
+
echo "==> 同步插件文件到 $PLUGIN_DIR"
|
|
13
|
+
mkdir -p "$PLUGIN_DIR"
|
|
14
|
+
rsync -av --delete "$PROJECT_DIR/" "$PLUGIN_DIR/" \
|
|
15
|
+
--exclude node_modules \
|
|
16
|
+
--exclude dist \
|
|
17
|
+
--exclude .git \
|
|
18
|
+
--exclude scripts
|
|
19
|
+
|
|
20
|
+
echo "==> 链接 openclaw peer dependency(build 前确保可解析)"
|
|
21
|
+
if [ -n "$OPENCLAW_DIR" ] && [ -d "$OPENCLAW_DIR" ]; then
|
|
22
|
+
OPENCLAW_GLOBAL="$OPENCLAW_DIR"
|
|
23
|
+
elif command -v pnpm >/dev/null 2>&1; then
|
|
24
|
+
OPENCLAW_GLOBAL="$(pnpm root -g 2>/dev/null)/openclaw"
|
|
25
|
+
else
|
|
26
|
+
OPENCLAW_GLOBAL="$(npm root -g 2>/dev/null)/openclaw"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [ -d "$OPENCLAW_GLOBAL" ]; then
|
|
30
|
+
mkdir -p "$PLUGIN_DIR/node_modules"
|
|
31
|
+
rm -rf "$PLUGIN_DIR/node_modules/openclaw"
|
|
32
|
+
ln -s "$OPENCLAW_GLOBAL" "$PLUGIN_DIR/node_modules/openclaw"
|
|
33
|
+
echo " ✓ 已链接 $OPENCLAW_GLOBAL -> $PLUGIN_DIR/node_modules/openclaw"
|
|
34
|
+
else
|
|
35
|
+
echo " ✗ 找不到全局 openclaw 安装,尝试使用 which openclaw 推断..."
|
|
36
|
+
OPENCLAW_BIN="$(which openclaw 2>/dev/null)"
|
|
37
|
+
if [ -n "$OPENCLAW_BIN" ]; then
|
|
38
|
+
# pnpm global shim (macOS): extract the openclaw.mjs path from the shim.
|
|
39
|
+
OPENCLAW_SHIM_BASEDIR="$(cd "$(dirname "$OPENCLAW_BIN")" && pwd)"
|
|
40
|
+
OPENCLAW_MJS_REL="$(grep -Eo 'global/[^ ]+/\\.pnpm/openclaw@[^ ]+/node_modules/openclaw/openclaw\\.mjs' "$OPENCLAW_BIN" | head -1)"
|
|
41
|
+
if [ -n "$OPENCLAW_MJS_REL" ]; then
|
|
42
|
+
OPENCLAW_MJS_ABS="$OPENCLAW_SHIM_BASEDIR/$OPENCLAW_MJS_REL"
|
|
43
|
+
OPENCLAW_GLOBAL="$(dirname "$OPENCLAW_MJS_ABS")"
|
|
44
|
+
else
|
|
45
|
+
# npm layout fallback
|
|
46
|
+
OPENCLAW_GLOBAL="$(cd "$(dirname "$OPENCLAW_BIN")/.." && pwd)/lib/node_modules/openclaw"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ -d "$OPENCLAW_GLOBAL" ]; then
|
|
50
|
+
mkdir -p "$PLUGIN_DIR/node_modules"
|
|
51
|
+
rm -rf "$PLUGIN_DIR/node_modules/openclaw"
|
|
52
|
+
ln -s "$OPENCLAW_GLOBAL" "$PLUGIN_DIR/node_modules/openclaw"
|
|
53
|
+
echo " ✓ 已链接 $OPENCLAW_GLOBAL -> $PLUGIN_DIR/node_modules/openclaw"
|
|
54
|
+
else
|
|
55
|
+
echo " ✗ 无法找到 openclaw 安装目录,跳过链接(插件可能无法加载/无法编译)"
|
|
56
|
+
fi
|
|
57
|
+
else
|
|
58
|
+
echo " ✗ openclaw 未安装,跳过链接(插件可能无法加载/无法编译)"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo "==> 安装依赖"
|
|
63
|
+
cd "$PLUGIN_DIR" && npm install --silent
|
|
64
|
+
|
|
65
|
+
WEBSOCKET_ENABLED="false"
|
|
66
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
67
|
+
WEBSOCKET_ENABLED="$(node -e "
|
|
68
|
+
const fs = require('fs');
|
|
69
|
+
try {
|
|
70
|
+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
71
|
+
const section = cfg?.channels?.infoflow ?? {};
|
|
72
|
+
const topMode = section?.connectionMode;
|
|
73
|
+
const accounts = section?.accounts && typeof section.accounts === 'object' ? Object.values(section.accounts) : [];
|
|
74
|
+
const accountWebsocket = accounts.some((acc) => acc && typeof acc === 'object' && acc.connectionMode === 'websocket');
|
|
75
|
+
const enabled = topMode === 'websocket' || accountWebsocket;
|
|
76
|
+
process.stdout.write(enabled ? 'true' : 'false');
|
|
77
|
+
} catch {
|
|
78
|
+
process.stdout.write('false');
|
|
79
|
+
}
|
|
80
|
+
")"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ "$WEBSOCKET_ENABLED" = "true" ]; then
|
|
84
|
+
echo "==> 检测到 websocket 模式,确保安装 @baidu/infoflow-sdk-nodejs"
|
|
85
|
+
BAIDU_NPM_REGISTRY="${BAIDU_NPM_REGISTRY:-${NPM_CONFIG_REGISTRY_BAIDU:-http://registry.npm.baidu-int.com}}"
|
|
86
|
+
|
|
87
|
+
# 校验方式用 import()(与插件运行时一致)。注意:一些包可能禁止访问 package.json 子路径。
|
|
88
|
+
if node --input-type=module -e "import('@baidu/infoflow-sdk-nodejs').then(()=>process.exit(0)).catch(()=>process.exit(1))"; then
|
|
89
|
+
echo " ✓ @baidu/infoflow-sdk-nodejs 已可用"
|
|
90
|
+
else
|
|
91
|
+
echo " - 使用私有源安装: $BAIDU_NPM_REGISTRY"
|
|
92
|
+
# 重要:npm 11 对 peerDependencies + --no-save 的行为在某些场景下不会落盘安装。
|
|
93
|
+
# 这里改为写入部署目录的 optionalDependencies(不会影响仓库,下一次 rsync 会覆盖),确保依赖真的安装到 node_modules。
|
|
94
|
+
npm_config_registry="$BAIDU_NPM_REGISTRY" npm install --save-optional --registry "$BAIDU_NPM_REGISTRY" @baidu/infoflow-sdk-nodejs
|
|
95
|
+
|
|
96
|
+
if node --input-type=module -e "import('@baidu/infoflow-sdk-nodejs').then(()=>process.exit(0)).catch(()=>process.exit(1))"; then
|
|
97
|
+
echo " ✓ 运行时依赖校验通过:@baidu/infoflow-sdk-nodejs"
|
|
98
|
+
else
|
|
99
|
+
echo " ✗ @baidu/infoflow-sdk-nodejs 仍不可用(websocket 模式必须)。"
|
|
100
|
+
echo " 请确认私有源、网络与鉴权后重试:"
|
|
101
|
+
echo " npm_config_registry=$BAIDU_NPM_REGISTRY npm install --save-optional --registry $BAIDU_NPM_REGISTRY @baidu/infoflow-sdk-nodejs"
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
else
|
|
106
|
+
echo "==> 当前非 websocket 模式,跳过 @baidu/infoflow-sdk-nodejs 安装"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
echo "==> 构建插件(确保 build 完成)"
|
|
110
|
+
cd "$PLUGIN_DIR"
|
|
111
|
+
|
|
112
|
+
# 1) Prefer package.json scripts.build if present
|
|
113
|
+
if node -e "const p=require('./package.json'); process.exit(p?.scripts?.build ? 0 : 1)"; then
|
|
114
|
+
echo " - 检测到 scripts.build,执行 npm run build"
|
|
115
|
+
npm run build
|
|
116
|
+
else
|
|
117
|
+
# 2) Fallback to tsc when tsconfig.json exists
|
|
118
|
+
if [ -f "$PLUGIN_DIR/tsconfig.json" ]; then
|
|
119
|
+
BUILD_TSCONFIG="tsconfig.json"
|
|
120
|
+
if [ -f "$PLUGIN_DIR/tsconfig.build.json" ]; then
|
|
121
|
+
BUILD_TSCONFIG="tsconfig.build.json"
|
|
122
|
+
fi
|
|
123
|
+
echo " - 未检测到 scripts.build,回退执行 TypeScript 编译(npx -p typescript tsc -p $BUILD_TSCONFIG)"
|
|
124
|
+
npx -y -p typescript tsc -p "$BUILD_TSCONFIG"
|
|
125
|
+
else
|
|
126
|
+
echo " ✗ 未检测到 scripts.build 且不存在 tsconfig.json,无法确认已完成编译"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# 3) Post-build sanity check: outDir defaults to dist, but honor tsconfig.json if present.
|
|
132
|
+
OUT_DIR="dist"
|
|
133
|
+
if [ -f "$PLUGIN_DIR/tsconfig.json" ]; then
|
|
134
|
+
OUT_DIR_FROM_TSCONFIG="$(node -e "
|
|
135
|
+
const fs = require('fs');
|
|
136
|
+
const cfg = JSON.parse(fs.readFileSync('tsconfig.json','utf8'));
|
|
137
|
+
const outDir = cfg?.compilerOptions?.outDir;
|
|
138
|
+
if (typeof outDir === 'string' && outDir.trim()) process.stdout.write(outDir.trim());
|
|
139
|
+
")"
|
|
140
|
+
if [ -n "$OUT_DIR_FROM_TSCONFIG" ]; then
|
|
141
|
+
OUT_DIR="$OUT_DIR_FROM_TSCONFIG"
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
if [ ! -d "$PLUGIN_DIR/$OUT_DIR" ]; then
|
|
146
|
+
echo " ✗ 构建完成但未发现产物目录:$PLUGIN_DIR/$OUT_DIR"
|
|
147
|
+
echo " 请检查 tsconfig.json 的 outDir 或 build 脚本是否输出到其它目录"
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
echo " ✓ 已检测到构建产物目录:$PLUGIN_DIR/$OUT_DIR"
|
|
151
|
+
|
|
152
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
153
|
+
echo " ✗ 未找到 $CONFIG_FILE,请先完成 OpenClaw 初始化后再部署"
|
|
154
|
+
exit 1
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
echo "==> 更新 OpenClaw 配置"
|
|
158
|
+
node -e "
|
|
159
|
+
const fs = require('fs');
|
|
160
|
+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
161
|
+
const id = '$PLUGIN_ID';
|
|
162
|
+
|
|
163
|
+
// 1. plugins.entries: 确保插件已启用
|
|
164
|
+
cfg.plugins = cfg.plugins ?? {};
|
|
165
|
+
cfg.plugins.entries = cfg.plugins.entries ?? {};
|
|
166
|
+
if (!cfg.plugins.entries[id]) {
|
|
167
|
+
cfg.plugins.entries[id] = { enabled: true };
|
|
168
|
+
console.log(' + 已添加 plugins.entries.' + id);
|
|
169
|
+
} else if (!cfg.plugins.entries[id].enabled) {
|
|
170
|
+
cfg.plugins.entries[id].enabled = true;
|
|
171
|
+
console.log(' + 已启用 plugins.entries.' + id);
|
|
172
|
+
} else {
|
|
173
|
+
console.log(' ✓ plugins.entries.' + id + ' 已存在');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 2. plugins.allow: 如果配置了白名单,确保插件在列表中
|
|
177
|
+
if (Array.isArray(cfg.plugins.allow)) {
|
|
178
|
+
if (!cfg.plugins.allow.includes(id)) {
|
|
179
|
+
cfg.plugins.allow.push(id);
|
|
180
|
+
console.log(' + 已添加到 plugins.allow');
|
|
181
|
+
} else {
|
|
182
|
+
console.log(' ✓ plugins.allow 已包含 ' + id);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
console.log(' - plugins.allow 未配置,跳过');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 2) + '\n');
|
|
189
|
+
"
|
|
190
|
+
|
|
191
|
+
echo "==> 检查 OpenClaw gateway 运行状态"
|
|
192
|
+
GATEWAY_STATUS="$(openclaw gateway status 2>/dev/null || true)"
|
|
193
|
+
DID_RESTART="false"
|
|
194
|
+
if echo "$GATEWAY_STATUS" | rg -q "Runtime: running"; then
|
|
195
|
+
echo "==> gateway 当前运行中,执行重启"
|
|
196
|
+
openclaw gateway restart
|
|
197
|
+
DID_RESTART="true"
|
|
198
|
+
else
|
|
199
|
+
echo "==> gateway 当前未运行,按要求跳过启动/重启"
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
echo "==> 检查启动状态"
|
|
203
|
+
sleep 2
|
|
204
|
+
if [ "$DID_RESTART" = "true" ]; then
|
|
205
|
+
# Restart 之后才做运行态确认,避免误匹配其它 openclaw 进程。
|
|
206
|
+
if openclaw gateway status 2>/dev/null | rg -q "Runtime: running"; then
|
|
207
|
+
echo "✓ gateway 重启完成"
|
|
208
|
+
else
|
|
209
|
+
echo "✗ gateway 重启后未处于 running 状态,请查看状态与日志:openclaw gateway status"
|
|
210
|
+
exit 1
|
|
211
|
+
fi
|
|
212
|
+
else
|
|
213
|
+
# 未重启/未启动时,仅输出当前状态
|
|
214
|
+
openclaw gateway status 2>/dev/null || true
|
|
215
|
+
fi
|
package/src/accounts.ts
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
* Handles multi-account support with config merging.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
7
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/core";
|
|
8
|
+
import type {
|
|
9
|
+
InfoflowAccountConfig,
|
|
10
|
+
InfoflowConnectionMode,
|
|
11
|
+
ResolvedInfoflowAccount,
|
|
12
|
+
} from "./types.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_INFOFLOW_WS_GATEWAY = "infoflow-open-gateway.weiyun.baidu.com";
|
|
8
15
|
|
|
9
16
|
// ---------------------------------------------------------------------------
|
|
10
17
|
// Config Access Helpers
|
|
@@ -62,6 +69,9 @@ function mergeInfoflowAccountConfig(
|
|
|
62
69
|
accountId: string,
|
|
63
70
|
): {
|
|
64
71
|
apiHost: string;
|
|
72
|
+
connectionMode?: InfoflowConnectionMode;
|
|
73
|
+
wsGateway?: string;
|
|
74
|
+
wsConnectDomain?: string;
|
|
65
75
|
checkToken: string;
|
|
66
76
|
encodingAESKey: string;
|
|
67
77
|
appKey: string;
|
|
@@ -80,6 +90,9 @@ function mergeInfoflowAccountConfig(
|
|
|
80
90
|
const account = raw.accounts?.[accountId] ?? {};
|
|
81
91
|
return { ...base, ...account } as {
|
|
82
92
|
apiHost: string;
|
|
93
|
+
connectionMode?: InfoflowConnectionMode;
|
|
94
|
+
wsGateway?: string;
|
|
95
|
+
wsConnectDomain?: string;
|
|
83
96
|
checkToken: string;
|
|
84
97
|
encodingAESKey: string;
|
|
85
98
|
appKey: string;
|
|
@@ -121,8 +134,14 @@ export function resolveInfoflowAccount(params: {
|
|
|
121
134
|
const encodingAESKey = merged.encodingAESKey ?? "";
|
|
122
135
|
const appKey = merged.appKey ?? "";
|
|
123
136
|
const appSecret = merged.appSecret ?? "";
|
|
137
|
+
const effectiveConnectionMode: InfoflowConnectionMode =
|
|
138
|
+
merged.connectionMode ?? "webhook";
|
|
139
|
+
const wsGateway = merged.wsGateway?.trim() || DEFAULT_INFOFLOW_WS_GATEWAY;
|
|
140
|
+
const wsConnectDomain = merged.wsConnectDomain?.trim() || undefined;
|
|
124
141
|
const configured =
|
|
125
|
-
|
|
142
|
+
effectiveConnectionMode === "websocket"
|
|
143
|
+
? Boolean(appKey) && Boolean(appSecret)
|
|
144
|
+
: Boolean(checkToken) && Boolean(encodingAESKey) && Boolean(appKey) && Boolean(appSecret);
|
|
126
145
|
|
|
127
146
|
return {
|
|
128
147
|
accountId,
|
|
@@ -133,6 +152,9 @@ export function resolveInfoflowAccount(params: {
|
|
|
133
152
|
enabled: merged.enabled,
|
|
134
153
|
name: merged.name,
|
|
135
154
|
apiHost,
|
|
155
|
+
connectionMode: effectiveConnectionMode,
|
|
156
|
+
wsGateway,
|
|
157
|
+
wsConnectDomain,
|
|
136
158
|
checkToken,
|
|
137
159
|
encodingAESKey,
|
|
138
160
|
appKey,
|
package/src/actions.ts
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* @all and @user mentions in group messages.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
8
|
-
|
|
7
|
+
import type {
|
|
8
|
+
ChannelMessageActionAdapter,
|
|
9
|
+
ChannelMessageActionName,
|
|
10
|
+
} from "openclaw/plugin-sdk";
|
|
11
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
12
|
+
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
9
13
|
import { resolveInfoflowAccount } from "./accounts.js";
|
|
10
14
|
import { logVerbose } from "./logging.js";
|
|
11
15
|
import { prepareInfoflowImageBase64, sendInfoflowImageMessage } from "./media.js";
|
|
@@ -29,7 +33,9 @@ const RECALL_PARTIAL_HINT =
|
|
|
29
33
|
"Some recalls failed. Send a brief reply stating only the failure reason(s).";
|
|
30
34
|
|
|
31
35
|
export const infoflowMessageActions: ChannelMessageActionAdapter = {
|
|
32
|
-
|
|
36
|
+
describeMessageTool: () => ({
|
|
37
|
+
actions: ["send", "delete"] satisfies readonly ChannelMessageActionName[],
|
|
38
|
+
}),
|
|
33
39
|
|
|
34
40
|
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
|
35
41
|
|