@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/CLAUDE.md +135 -0
  3. package/COLLABORATION_REPORT.md +209 -0
  4. package/PROJECT_GUIDE.md +355 -0
  5. package/README.md +158 -66
  6. package/docs/dev-guide.md +63 -50
  7. package/docs/qa-feature-list.md +452 -0
  8. package/docs/webhook-guide.md +178 -0
  9. package/index.ts +28 -2
  10. package/openclaw.plugin.json +131 -21
  11. package/package.json +16 -3
  12. package/scripts/deploy.sh +66 -7
  13. package/scripts/postinstall.cjs +80 -0
  14. package/skills/infoflow-dev/SKILL.md +2 -2
  15. package/skills/infoflow-dev/references/api.md +1 -1
  16. package/src/adapter/inbound/webhook-parser.ts +27 -5
  17. package/src/adapter/inbound/ws-receiver.ts +304 -43
  18. package/src/adapter/outbound/markdown-local-images.ts +80 -0
  19. package/src/adapter/outbound/reply-dispatcher.ts +146 -65
  20. package/src/adapter/outbound/target-resolver.ts +4 -3
  21. package/src/channel/accounts.ts +97 -22
  22. package/src/channel/channel.ts +456 -12
  23. package/src/channel/media.ts +20 -6
  24. package/src/channel/monitor.ts +8 -3
  25. package/src/channel/outbound.ts +358 -21
  26. package/src/channel/streaming.ts +740 -0
  27. package/src/commands/changelog.ts +80 -0
  28. package/src/commands/doctor.ts +545 -0
  29. package/src/commands/logs.ts +449 -0
  30. package/src/commands/version.ts +20 -0
  31. package/src/compat/openclaw-sdk.ts +218 -0
  32. package/src/handler/message-handler.ts +673 -166
  33. package/src/logging.ts +1 -1
  34. package/src/runtime.ts +1 -1
  35. package/src/security/dm-policy.ts +1 -4
  36. package/src/security/group-policy.ts +174 -51
  37. package/src/tools/actions/index.ts +15 -13
  38. package/src/tools/cron/relay.ts +1154 -0
  39. package/src/tools/hooks/index.ts +13 -1
  40. package/src/tools/index.ts +714 -32
  41. package/src/types.ts +144 -25
  42. package/src/utils/audio/g722/dct_tables.ts +381 -0
  43. package/src/utils/audio/g722/decoder.ts +919 -0
  44. package/src/utils/audio/g722/defs.ts +105 -0
  45. package/src/utils/audio/g722/hd-parser.ts +247 -0
  46. package/src/utils/audio/g722/huff_tables.ts +240 -0
  47. package/src/utils/audio/g722/index.ts +78 -0
  48. package/src/utils/audio/g722/output_decoded.pcm +0 -0
  49. package/src/utils/audio/g722/output_decoded.wav +0 -0
  50. package/src/utils/audio/g722/tables.ts +173 -0
  51. package/src/utils/audio/g722/test_api.ts +31 -0
  52. package/src/utils/audio/g722/test_voice.hd +0 -0
  53. package/src/utils/bos/im-bos-client.ts +219 -0
  54. package/src/utils/group-agent-cache.ts +142 -0
  55. package/src/utils/token-adapter.ts +120 -51
@@ -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": "webhook",
19
+ "default": "websocket",
20
20
  "description": "消息接收方式:webhook(HTTP 回调)或 websocket(长连接)"
21
21
  },
22
22
  "wsGateway": {
23
23
  "type": "string",
24
- "description": "WebSocket Gateway 域名(仅 websocket 模式使用)"
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
- "default": "mention-and-watch"
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": { "type": "string" },
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": { "type": "string" },
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": "webhook"
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": { "type": "string" },
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": { "type": "string" },
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
- "messageFormat": {
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.9",
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": "^0.1.5"
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
- PLUGIN_DIR="$HOME/.openclaw/extensions/infoflow"
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
- pkill -f "openclaw" 2>/dev/null || true
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://apiin.im.baidu.com/api/v1/robot/msg/groupmsgsend
26
+ POST http://api.im.baidu.com/api/v1/robot/msg/groupmsgsend
27
27
  ```
28
28
 
29
29
  ### 发私聊端点
30
30
  ```
31
- POST http://apiin.im.baidu.com/api/v1/app/message/send
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://apiin.im.baidu.com/api/v1/robot/msg/groupmsgsend`
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/plugin-sdk";
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;