@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.
@@ -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.18",
3
+ "version": "2026.5.4",
4
4
  "description": "OpenClaw Infoflow (如流) channel plugin for Baidu enterprise messaging",
5
5
  "type": "module",
6
- "main": "index.ts",
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
- "openclaw": ">=2026.3.2"
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 { DEFAULT_ACCOUNT_ID, normalizeAccountId, type OpenClawConfig } from "openclaw/plugin-sdk";
7
- import type { InfoflowAccountConfig, ResolvedInfoflowAccount } from "./types.js";
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
- Boolean(checkToken) && Boolean(encodingAESKey) && Boolean(appKey) && Boolean(appSecret);
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 { ChannelMessageActionAdapter, ChannelMessageActionName } from "openclaw/plugin-sdk";
8
- import { extractToolSend, jsonResult, readStringParam } from "openclaw/plugin-sdk";
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
- listActions: (): ChannelMessageActionName[] => ["send", "delete"],
36
+ describeMessageTool: () => ({
37
+ actions: ["send", "delete"] satisfies readonly ChannelMessageActionName[],
38
+ }),
33
39
 
34
40
  extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
35
41